add paired adjustment triger depletion adjustment

This commit is contained in:
ragilap
2026-03-17 11:02:37 +07:00
parent 131949874a
commit c9dee7d1c4
9 changed files with 839 additions and 381 deletions
@@ -2,12 +2,12 @@ package repositories
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@@ -15,9 +15,19 @@ import (
type AdjustmentStockRepository interface {
CreateOne(ctx context.Context, data *entity.AdjustmentStock, modifier func(*gorm.DB) *gorm.DB) error
GetByID(ctx context.Context, id uint, modifier func(*gorm.DB) *gorm.DB) (*entity.AdjustmentStock, error)
GetByIDForUpdate(ctx context.Context, id uint) (*entity.AdjustmentStock, error)
FindKandangIDByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (uint, error)
FindProductIDByProductWarehouseID(ctx context.Context, productWarehouseID uint) (uint, error)
FindRoutesByFunctionCode(ctx context.Context, productID uint, functionCode string) ([]AdjustmentRouteResolution, error)
FindOverconsumeRule(ctx context.Context, lane, flagGroupCode, functionCode string) (*bool, error)
LoadDownstreamDependencies(ctx context.Context, stockableType string, stockableIDs []uint) ([]AdjustmentDownstreamDependency, error)
FindAyamSourceProductWarehouse(ctx context.Context, warehouseID uint, projectFlockKandangID uint) (*entity.ProductWarehouse, error)
IsAyamProduct(ctx context.Context, productID uint) (bool, error)
CountActiveConsumeAllocationsByUsable(ctx context.Context, usableType string, usableID uint) (int64, error)
UpdateTotalQty(ctx context.Context, id uint, qty float64) error
UpdatePairedAdjustmentID(ctx context.Context, id uint, pairedID uint) error
DeleteStockLogsByAdjustmentID(ctx context.Context, adjustmentID uint) error
DeleteAdjustmentByID(ctx context.Context, id uint) error
ResyncProjectFlockPopulationUsage(ctx context.Context, projectFlockKandangID uint) error
FindHistory(ctx context.Context, filter AdjustmentHistoryFilter, modifier func(*gorm.DB) *gorm.DB) ([]*entity.AdjustmentStock, int64, error)
WithTx(tx *gorm.DB) AdjustmentStockRepository
DB() *gorm.DB
@@ -44,6 +54,13 @@ type AdjustmentHistoryFilter struct {
Limit int
}
type AdjustmentDownstreamDependency struct {
UsableType string `gorm:"column:usable_type"`
UsableID uint64 `gorm:"column:usable_id"`
FunctionCode string `gorm:"column:function_code"`
FlagGroupCode string `gorm:"column:flag_group_code"`
}
type adjustmentStockRepositoryImpl struct {
db *gorm.DB
}
@@ -73,6 +90,17 @@ func (r *adjustmentStockRepositoryImpl) GetByID(ctx context.Context, id uint, mo
return &record, nil
}
func (r *adjustmentStockRepositoryImpl) GetByIDForUpdate(ctx context.Context, id uint) (*entity.AdjustmentStock, error) {
var record entity.AdjustmentStock
if err := r.db.WithContext(ctx).
Clauses(clause.Locking{Strength: "UPDATE"}).
Where("id = ?", id).
Take(&record).Error; err != nil {
return nil, err
}
return &record, nil
}
func (r *adjustmentStockRepositoryImpl) FindKandangIDByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (uint, error) {
type pfkRow struct {
KandangID uint `gorm:"column:kandang_id"`
@@ -91,6 +119,21 @@ func (r *adjustmentStockRepositoryImpl) FindKandangIDByProjectFlockKandangID(ctx
return pfk.KandangID, nil
}
func (r *adjustmentStockRepositoryImpl) FindProductIDByProductWarehouseID(ctx context.Context, productWarehouseID uint) (uint, error) {
type productRow struct {
ProductID uint `gorm:"column:product_id"`
}
var row productRow
if err := r.db.WithContext(ctx).
Table("product_warehouses").
Select("product_id").
Where("id = ?", productWarehouseID).
Take(&row).Error; err != nil {
return 0, err
}
return row.ProductID, nil
}
func (r *adjustmentStockRepositoryImpl) FindRoutesByFunctionCode(
ctx context.Context,
productID uint,
@@ -122,37 +165,183 @@ func (r *adjustmentStockRepositoryImpl) FindRoutesByFunctionCode(
return rows, nil
}
func (r *adjustmentStockRepositoryImpl) FindOverconsumeRule(
func (r *adjustmentStockRepositoryImpl) LoadDownstreamDependencies(
ctx context.Context,
lane string,
flagGroupCode string,
functionCode string,
) (*bool, error) {
type selectedRow struct {
AllowOverconsume bool `gorm:"column:allow_overconsume"`
stockableType string,
stockableIDs []uint,
) ([]AdjustmentDownstreamDependency, error) {
if strings.TrimSpace(stockableType) == "" || len(stockableIDs) == 0 {
return nil, nil
}
var selected selectedRow
var rows []AdjustmentDownstreamDependency
err := r.db.WithContext(ctx).
Table("fifo_stock_v2_overconsume_rules").
Select("allow_overconsume").
Where("is_active = TRUE").
Where("lane = ?", lane).
Where("(flag_group_code IS NULL OR flag_group_code = ?)", flagGroupCode).
Where("(function_code IS NULL OR function_code = ?)", functionCode).
Order("CASE WHEN flag_group_code IS NULL THEN 1 ELSE 0 END ASC").
Order("CASE WHEN function_code IS NULL THEN 1 ELSE 0 END ASC").
Order("priority ASC, id ASC").
Limit(1).
Take(&selected).Error
Table("stock_allocations").
Select("usable_type, usable_id, COALESCE(function_code,'') AS function_code, COALESCE(flag_group_code,'') AS flag_group_code").
Where("stockable_type = ?", strings.ToUpper(strings.TrimSpace(stockableType))).
Where("stockable_id IN ?", stockableIDs).
Where("status = ?", entity.StockAllocationStatusActive).
Where("allocation_purpose = ?", entity.StockAllocationPurposeConsume).
Where("deleted_at IS NULL").
Where(
"(usable_type <> ? OR EXISTS (SELECT 1 FROM project_chickins pc WHERE pc.id = stock_allocations.usable_id AND pc.deleted_at IS NULL))",
"PROJECT_CHICKIN",
).
Group("usable_type, usable_id, function_code, flag_group_code").
Scan(&rows).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return &selected.AllowOverconsume, nil
return rows, nil
}
func (r *adjustmentStockRepositoryImpl) FindAyamSourceProductWarehouse(
ctx context.Context,
warehouseID uint,
projectFlockKandangID uint,
) (*entity.ProductWarehouse, error) {
var sourcePW entity.ProductWarehouse
err := r.db.WithContext(ctx).
Model(&entity.ProductWarehouse{}).
Where("project_flock_kandang_id = ?", projectFlockKandangID).
Where(`
EXISTS (
SELECT 1
FROM flags f
JOIN fifo_stock_v2_flag_members fm ON fm.flag_name = f.name AND fm.is_active = TRUE
WHERE f.flagable_type = ?
AND f.flagable_id = product_warehouses.product_id
AND fm.flag_group_code = ?
)
`, entity.FlagableTypeProduct, "AYAM").
Order(gorm.Expr("CASE WHEN warehouse_id = ? THEN 0 ELSE 1 END ASC", warehouseID)).
Order("id ASC").
Take(&sourcePW).Error
if err != nil {
return nil, err
}
return &sourcePW, nil
}
func (r *adjustmentStockRepositoryImpl) IsAyamProduct(ctx context.Context, productID uint) (bool, error) {
if productID == 0 {
return false, nil
}
var count int64
if err := r.db.WithContext(ctx).
Table("flags f").
Joins("JOIN fifo_stock_v2_flag_members fm ON fm.flag_name = f.name AND fm.flag_group_code = ? AND fm.is_active = TRUE", "AYAM").
Where("f.flagable_type = ?", entity.FlagableTypeProduct).
Where("f.flagable_id = ?", productID).
Count(&count).Error; err != nil {
return false, err
}
return count > 0, nil
}
func (r *adjustmentStockRepositoryImpl) CountActiveConsumeAllocationsByUsable(
ctx context.Context,
usableType string,
usableID uint,
) (int64, error) {
if strings.TrimSpace(usableType) == "" || usableID == 0 {
return 0, nil
}
var count int64
err := r.db.WithContext(ctx).
Table("stock_allocations").
Where("usable_type = ?", strings.ToUpper(strings.TrimSpace(usableType))).
Where("usable_id = ?", usableID).
Where("status = ?", entity.StockAllocationStatusActive).
Where("allocation_purpose = ?", entity.StockAllocationPurposeConsume).
Where("deleted_at IS NULL").
Count(&count).Error
if err != nil {
return 0, err
}
return count, nil
}
func (r *adjustmentStockRepositoryImpl) UpdateTotalQty(ctx context.Context, id uint, qty float64) error {
return r.db.WithContext(ctx).
Model(&entity.AdjustmentStock{}).
Where("id = ?", id).
Update("total_qty", qty).Error
}
func (r *adjustmentStockRepositoryImpl) UpdatePairedAdjustmentID(ctx context.Context, id uint, pairedID uint) error {
return r.db.WithContext(ctx).
Model(&entity.AdjustmentStock{}).
Where("id = ?", id).
Update("paired_adjustment_id", pairedID).Error
}
func (r *adjustmentStockRepositoryImpl) DeleteStockLogsByAdjustmentID(ctx context.Context, adjustmentID uint) error {
return r.db.WithContext(ctx).
Where("loggable_type = ? AND loggable_id = ?", string(utils.StockLogTypeAdjustment), adjustmentID).
Delete(&entity.StockLog{}).Error
}
func (r *adjustmentStockRepositoryImpl) DeleteAdjustmentByID(ctx context.Context, id uint) error {
return r.db.WithContext(ctx).
Where("id = ?", id).
Delete(&entity.AdjustmentStock{}).Error
}
func (r *adjustmentStockRepositoryImpl) ResyncProjectFlockPopulationUsage(ctx context.Context, projectFlockKandangID uint) error {
if projectFlockKandangID == 0 {
return nil
}
idsSubquery := `
SELECT pfp.id
FROM project_flock_populations pfp
JOIN project_chickins pc ON pc.id = pfp.project_chickin_id
WHERE pc.project_flock_kandang_id = ?
`
updateWithAlloc := `
UPDATE project_flock_populations p
SET total_used_qty = COALESCE(a.used, 0)
FROM (
SELECT stockable_id, SUM(qty) AS used
FROM stock_allocations
WHERE stockable_type = 'PROJECT_FLOCK_POPULATION'
AND status = 'ACTIVE'
AND allocation_purpose = 'CONSUME'
GROUP BY stockable_id
) a
WHERE p.id = a.stockable_id
AND p.id IN (` + idsSubquery + `)
`
resetMissing := `
UPDATE project_flock_populations p
SET total_used_qty = 0
WHERE p.id IN (` + idsSubquery + `)
AND NOT EXISTS (
SELECT 1
FROM stock_allocations sa
WHERE sa.stockable_type = 'PROJECT_FLOCK_POPULATION'
AND sa.status = 'ACTIVE'
AND sa.allocation_purpose = 'CONSUME'
AND sa.stockable_id = p.id
)
`
db := r.db.WithContext(ctx)
if err := db.Exec(updateWithAlloc, projectFlockKandangID).Error; err != nil {
return err
}
if err := db.Exec(resetMissing, projectFlockKandangID).Error; err != nil {
return err
}
return nil
}
func (r *adjustmentStockRepositoryImpl) FindHistory(