mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 23:35:43 +00:00
fix: all implemented fifo v2
This commit is contained in:
@@ -11,12 +11,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/database"
|
"gitlab.com/mbugroup/lti-api.git/internal/database"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -61,13 +59,7 @@ func main() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
db := database.Connect(config.DBHost, config.DBName)
|
db := database.Connect(config.DBHost, config.DBName)
|
||||||
|
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
|
||||||
stockAllocRepo := commonRepo.NewStockAllocationRepository(db)
|
|
||||||
fifoSvc := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, nil)
|
|
||||||
fifoStockV2Svc := commonSvc.NewFifoStockV2Service(db, nil)
|
fifoStockV2Svc := commonSvc.NewFifoStockV2Service(db, nil)
|
||||||
if err := registerAdjustmentFIFO(fifoSvc); err != nil {
|
|
||||||
log.Fatalf("failed to register adjustment fifo config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
adjustments, err := loadAdjustments(ctx, db, ids)
|
adjustments, err := loadAdjustments(ctx, db, ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -185,7 +177,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"PLAN adj=%d lane=STOCKABLE function=%s total=%.3f remove_qty=%.3f action=reverse_stock+delete\n",
|
"PLAN adj=%d lane=STOCKABLE function=%s total=%.3f remove_qty=%.3f action=reflow_to_zero+delete\n",
|
||||||
adj.ID,
|
adj.ID,
|
||||||
route.FunctionCode,
|
route.FunctionCode,
|
||||||
adj.TotalQty,
|
adj.TotalQty,
|
||||||
@@ -198,16 +190,25 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
err = db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
if removeQty > 0 {
|
if err := tx.WithContext(ctx).
|
||||||
if err := fifoSvc.AdjustStockableQuantity(ctx, commonSvc.StockAdjustRequest{
|
Table("adjustment_stocks").
|
||||||
StockableKey: fifo.StockableKeyAdjustmentIn,
|
Where("id = ?", adj.ID).
|
||||||
StockableID: adj.ID,
|
Updates(map[string]any{
|
||||||
ProductWarehouseID: adj.ProductWarehouseID,
|
"total_qty": 0,
|
||||||
Quantity: -removeQty,
|
"total_used": 0,
|
||||||
Tx: tx,
|
}).Error; err != nil {
|
||||||
}); err != nil {
|
return fmt.Errorf("set stockable qty to zero: %w", err)
|
||||||
return fmt.Errorf("reverse stockable quantity: %w", err)
|
}
|
||||||
}
|
|
||||||
|
reflowReq := commonSvc.FifoStockV2ReflowRequest{
|
||||||
|
FlagGroupCode: route.FlagGroupCode,
|
||||||
|
ProductWarehouseID: adj.ProductWarehouseID,
|
||||||
|
AsOf: &adj.CreatedAt,
|
||||||
|
IdempotencyKey: fmt.Sprintf("delete-adjustment-stockable-%d-%d", adj.ID, time.Now().UnixNano()),
|
||||||
|
Tx: tx,
|
||||||
|
}
|
||||||
|
if _, err := fifoStockV2Svc.Reflow(ctx, reflowReq); err != nil {
|
||||||
|
return fmt.Errorf("reflow stockable to zero: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := hardDeleteStockableAllocations(ctx, tx, fifo.StockableKeyAdjustmentIn.String(), adj.ID); err != nil {
|
if err := hardDeleteStockableAllocations(ctx, tx, fifo.StockableKeyAdjustmentIn.String(), adj.ID); err != nil {
|
||||||
@@ -243,39 +244,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerAdjustmentFIFO(fifoSvc commonSvc.FifoService) error {
|
|
||||||
if err := fifoSvc.RegisterStockable(fifo.StockableConfig{
|
|
||||||
Key: fifo.StockableKeyAdjustmentIn,
|
|
||||||
Table: "adjustment_stocks",
|
|
||||||
Columns: fifo.StockableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
TotalQuantity: "total_qty",
|
|
||||||
TotalUsedQuantity: "total_used",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
OrderBy: []string{"created_at ASC", "id ASC"},
|
|
||||||
}); err != nil && !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fifoSvc.RegisterUsable(fifo.UsableConfig{
|
|
||||||
Key: fifo.UsableKeyAdjustmentOut,
|
|
||||||
Table: "adjustment_stocks",
|
|
||||||
Columns: fifo.UsableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
UsageQuantity: "usage_qty",
|
|
||||||
PendingQuantity: "pending_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
OrderBy: []string{"created_at ASC", "id ASC"},
|
|
||||||
}); err != nil && !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func modeLabel(apply bool) string {
|
func modeLabel(apply bool) string {
|
||||||
if apply {
|
if apply {
|
||||||
return "APPLY"
|
return "APPLY"
|
||||||
|
|||||||
@@ -178,14 +178,35 @@ func main() {
|
|||||||
successApply++
|
successApply++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orphanPopulationRows := int64(0)
|
||||||
|
syncedPopulationQtyRows := int64(0)
|
||||||
|
syncedPopulationUsedRows := int64(0)
|
||||||
|
if rowsOrphan, rowsQty, rowsUsed, err := resyncProjectFlockPopulation(ctx, db, projectFlockKandangID); err != nil {
|
||||||
|
fmt.Printf("FAIL population_resync project_flock_kandang_id=%d error=%v\n", projectFlockKandangID, err)
|
||||||
|
failedApply++
|
||||||
|
} else {
|
||||||
|
orphanPopulationRows = rowsOrphan
|
||||||
|
syncedPopulationQtyRows = rowsQty
|
||||||
|
syncedPopulationUsedRows = rowsUsed
|
||||||
|
fmt.Printf(
|
||||||
|
"SYNC project_flock_populations orphan_marked=%d qty_synced=%d used_synced=%d\n",
|
||||||
|
orphanPopulationRows,
|
||||||
|
syncedPopulationQtyRows,
|
||||||
|
syncedPopulationUsedRows,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"Summary: planned=%d skipped_pw=%d failed_resolve=%d applied=%d failed_apply=%d\n",
|
"Summary: planned=%d skipped_pw=%d failed_resolve=%d applied=%d failed_apply=%d population_orphan=%d population_qty_synced=%d population_used_synced=%d\n",
|
||||||
len(targets),
|
len(targets),
|
||||||
skippedPW,
|
skippedPW,
|
||||||
failedResolve,
|
failedResolve,
|
||||||
successApply,
|
successApply,
|
||||||
failedApply,
|
failedApply,
|
||||||
|
orphanPopulationRows,
|
||||||
|
syncedPopulationQtyRows,
|
||||||
|
syncedPopulationUsedRows,
|
||||||
)
|
)
|
||||||
if failedResolve > 0 || failedApply > 0 {
|
if failedResolve > 0 || failedApply > 0 {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -379,3 +400,66 @@ func resolveFlagGroupsByProductWarehouse(ctx context.Context, db *gorm.DB, produ
|
|||||||
}
|
}
|
||||||
return groups, nil
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resyncProjectFlockPopulation(ctx context.Context, db *gorm.DB, projectFlockKandangID uint) (int64, int64, int64, error) {
|
||||||
|
if projectFlockKandangID == 0 {
|
||||||
|
return 0, 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
orphanResult := db.WithContext(ctx).Exec(`
|
||||||
|
UPDATE project_flock_populations pfp
|
||||||
|
SET deleted_at = NOW(),
|
||||||
|
updated_at = NOW()
|
||||||
|
FROM project_chickins pc
|
||||||
|
WHERE pfp.project_chickin_id = pc.id
|
||||||
|
AND pc.project_flock_kandang_id = ?
|
||||||
|
AND pc.deleted_at IS NOT NULL
|
||||||
|
AND pfp.deleted_at IS NULL
|
||||||
|
`, projectFlockKandangID)
|
||||||
|
if orphanResult.Error != nil {
|
||||||
|
return 0, 0, 0, orphanResult.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
qtyResult := db.WithContext(ctx).Exec(`
|
||||||
|
UPDATE project_flock_populations p
|
||||||
|
SET total_qty = GREATEST(COALESCE(pc.usage_qty, 0), 0),
|
||||||
|
updated_at = NOW()
|
||||||
|
FROM project_chickins pc
|
||||||
|
WHERE p.project_chickin_id = pc.id
|
||||||
|
AND pc.project_flock_kandang_id = ?
|
||||||
|
AND pc.deleted_at IS NULL
|
||||||
|
AND p.deleted_at IS NULL
|
||||||
|
`, projectFlockKandangID)
|
||||||
|
if qtyResult.Error != nil {
|
||||||
|
return 0, 0, 0, qtyResult.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
usedResult := db.WithContext(ctx).Exec(`
|
||||||
|
WITH scoped AS (
|
||||||
|
SELECT pfp.id, pfp.total_qty
|
||||||
|
FROM project_flock_populations pfp
|
||||||
|
JOIN project_chickins pc ON pc.id = pfp.project_chickin_id
|
||||||
|
WHERE pc.project_flock_kandang_id = ?
|
||||||
|
AND pc.deleted_at IS NULL
|
||||||
|
AND pfp.deleted_at IS NULL
|
||||||
|
),
|
||||||
|
alloc AS (
|
||||||
|
SELECT sa.stockable_id, SUM(sa.qty) AS used_qty
|
||||||
|
FROM stock_allocations sa
|
||||||
|
WHERE sa.stockable_type = 'PROJECT_FLOCK_POPULATION'
|
||||||
|
AND sa.status = 'ACTIVE'
|
||||||
|
GROUP BY sa.stockable_id
|
||||||
|
)
|
||||||
|
UPDATE project_flock_populations p
|
||||||
|
SET total_used_qty = LEAST(COALESCE(a.used_qty, 0), GREATEST(s.total_qty, 0)),
|
||||||
|
updated_at = NOW()
|
||||||
|
FROM scoped s
|
||||||
|
LEFT JOIN alloc a ON a.stockable_id = s.id
|
||||||
|
WHERE p.id = s.id
|
||||||
|
`, projectFlockKandangID)
|
||||||
|
if usedResult.Error != nil {
|
||||||
|
return 0, 0, 0, usedResult.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return orphanResult.RowsAffected, qtyResult.RowsAffected, usedResult.RowsAffected, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
rAdjustmentStock "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/repositories"
|
rAdjustmentStock "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/repositories"
|
||||||
sAdjustment "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/services"
|
sAdjustment "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/services"
|
||||||
@@ -17,7 +16,6 @@ import (
|
|||||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AdjustmentModule struct{}
|
type AdjustmentModule struct{}
|
||||||
@@ -31,50 +29,14 @@ func (AdjustmentModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validat
|
|||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
productRepo := rproduct.NewProductRepository(db)
|
productRepo := rproduct.NewProductRepository(db)
|
||||||
adjustmentStockRepo := rAdjustmentStock.NewAdjustmentStockRepository(db)
|
adjustmentStockRepo := rAdjustmentStock.NewAdjustmentStockRepository(db)
|
||||||
stockAllocRepo := commonRepo.NewStockAllocationRepository(db)
|
|
||||||
|
|
||||||
fifoService := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)
|
|
||||||
fifoStockV2Service := commonSvc.NewFifoStockV2Service(db, utils.Log)
|
fifoStockV2Service := commonSvc.NewFifoStockV2Service(db, utils.Log)
|
||||||
|
|
||||||
err := fifoService.RegisterStockable(fifo.StockableConfig{
|
|
||||||
Key: fifo.StockableKeyAdjustmentIn,
|
|
||||||
Table: "adjustment_stocks",
|
|
||||||
Columns: fifo.StockableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
TotalQuantity: "total_qty",
|
|
||||||
TotalUsedQuantity: "total_used",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
OrderBy: []string{"created_at ASC", "id ASC"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to register ADJUSTMENT_IN as Stockable: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fifoService.RegisterUsable(fifo.UsableConfig{
|
|
||||||
Key: fifo.UsableKeyAdjustmentOut,
|
|
||||||
Table: "adjustment_stocks",
|
|
||||||
Columns: fifo.UsableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
UsageQuantity: "usage_qty",
|
|
||||||
PendingQuantity: "pending_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
OrderBy: []string{"created_at ASC", "id ASC"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to register ADJUSTMENT_OUT as Usable: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
adjustmentService := sAdjustment.NewAdjustmentService(
|
adjustmentService := sAdjustment.NewAdjustmentService(
|
||||||
productRepo,
|
productRepo,
|
||||||
stockLogsRepo,
|
stockLogsRepo,
|
||||||
warehouseRepo,
|
warehouseRepo,
|
||||||
productWarehouseRepo,
|
productWarehouseRepo,
|
||||||
adjustmentStockRepo,
|
adjustmentStockRepo,
|
||||||
fifoService,
|
|
||||||
fifoStockV2Service,
|
fifoStockV2Service,
|
||||||
validate,
|
validate,
|
||||||
projectFlockKandangRepo,
|
projectFlockKandangRepo,
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ type adjustmentService struct {
|
|||||||
ProductRepo productRepo.ProductRepository
|
ProductRepo productRepo.ProductRepository
|
||||||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||||||
AdjustmentStockRepository adjustmentStockRepo.AdjustmentStockRepository
|
AdjustmentStockRepository adjustmentStockRepo.AdjustmentStockRepository
|
||||||
FifoSvc common.FifoService
|
|
||||||
FifoStockV2Svc common.FifoStockV2Service
|
FifoStockV2Svc common.FifoStockV2Service
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +54,6 @@ func NewAdjustmentService(
|
|||||||
warehouseRepo warehouseRepo.WarehouseRepository,
|
warehouseRepo warehouseRepo.WarehouseRepository,
|
||||||
productWarehouseRepo ProductWarehouse.ProductWarehouseRepository,
|
productWarehouseRepo ProductWarehouse.ProductWarehouseRepository,
|
||||||
adjustmentStockRepo adjustmentStockRepo.AdjustmentStockRepository,
|
adjustmentStockRepo adjustmentStockRepo.AdjustmentStockRepository,
|
||||||
fifoSvc common.FifoService,
|
|
||||||
fifoStockV2Svc common.FifoStockV2Service,
|
fifoStockV2Svc common.FifoStockV2Service,
|
||||||
validate *validator.Validate,
|
validate *validator.Validate,
|
||||||
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository,
|
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository,
|
||||||
@@ -69,7 +67,6 @@ func NewAdjustmentService(
|
|||||||
ProductRepo: productRepo,
|
ProductRepo: productRepo,
|
||||||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||||
AdjustmentStockRepository: adjustmentStockRepo,
|
AdjustmentStockRepository: adjustmentStockRepo,
|
||||||
FifoSvc: fifoSvc,
|
|
||||||
FifoStockV2Svc: fifoStockV2Svc,
|
FifoStockV2Svc: fifoStockV2Svc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TransferModule struct{}
|
type TransferModule struct{}
|
||||||
@@ -43,7 +42,6 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
kandangRepo := rKandang.NewKandangRepository(db)
|
kandangRepo := rKandang.NewKandangRepository(db)
|
||||||
nonstockRepo := rNonstock.NewNonstockRepository(db)
|
nonstockRepo := rNonstock.NewNonstockRepository(db)
|
||||||
documentRepo := commonRepo.NewDocumentRepository(db)
|
documentRepo := commonRepo.NewDocumentRepository(db)
|
||||||
stockAllocRepo := commonRepo.NewStockAllocationRepository(db)
|
|
||||||
expenseRepository := expenseRepo.NewExpenseRepository(db)
|
expenseRepository := expenseRepo.NewExpenseRepository(db)
|
||||||
expenseRealizationRepo := expenseRepo.NewExpenseRealizationRepository(db)
|
expenseRealizationRepo := expenseRepo.NewExpenseRealizationRepository(db)
|
||||||
|
|
||||||
@@ -69,7 +67,6 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
validate,
|
validate,
|
||||||
)
|
)
|
||||||
|
|
||||||
fifoService := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)
|
|
||||||
fifoStockV2Service := commonSvc.NewFifoStockV2Service(db, utils.Log)
|
fifoStockV2Service := commonSvc.NewFifoStockV2Service(db, utils.Log)
|
||||||
expenseBridge := sTransfer.NewTransferExpenseBridge(
|
expenseBridge := sTransfer.NewTransferExpenseBridge(
|
||||||
db,
|
db,
|
||||||
@@ -79,39 +76,7 @@ func (TransferModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
expenseServiceInstance,
|
expenseServiceInstance,
|
||||||
)
|
)
|
||||||
|
|
||||||
err = fifoService.RegisterStockable(fifo.StockableConfig{
|
transferService := sTransfer.NewTransferService(validate, stockTransferRepo, stockTransferDetailRepo, stockTransferDeliveryRepo, StockTransferDeliveryItemRepo, stockLogsRepo, productWarehouseRepo, supplierRepo, warehouseRepo, projectFlockKandangRepo, documentSvc, fifoStockV2Service, expenseBridge)
|
||||||
Key: fifo.StockableKeyStockTransferIn,
|
|
||||||
Table: "stock_transfer_details",
|
|
||||||
Columns: fifo.StockableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "dest_product_warehouse_id",
|
|
||||||
TotalQuantity: "total_qty",
|
|
||||||
TotalUsedQuantity: "total_used",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
OrderBy: []string{"created_at ASC", "id ASC"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fifoService.RegisterUsable(fifo.UsableConfig{
|
|
||||||
Key: fifo.UsableKeyStockTransferOut,
|
|
||||||
Table: "stock_transfer_details",
|
|
||||||
Columns: fifo.UsableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "source_product_warehouse_id",
|
|
||||||
UsageQuantity: "usage_qty",
|
|
||||||
PendingQuantity: "pending_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
OrderBy: []string{"created_at ASC", "id ASC"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
transferService := sTransfer.NewTransferService(validate, stockTransferRepo, stockTransferDetailRepo, stockTransferDeliveryRepo, StockTransferDeliveryItemRepo, stockLogsRepo, productWarehouseRepo, supplierRepo, warehouseRepo, projectFlockKandangRepo, documentSvc, fifoService, fifoStockV2Service, expenseBridge)
|
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
TransferRoutes(router, userService, transferService)
|
TransferRoutes(router, userService, transferService)
|
||||||
|
|||||||
@@ -44,12 +44,11 @@ type transferService struct {
|
|||||||
WarehouseRepo warehouseRepo.WarehouseRepository
|
WarehouseRepo warehouseRepo.WarehouseRepository
|
||||||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||||||
DocumentSvc commonSvc.DocumentService
|
DocumentSvc commonSvc.DocumentService
|
||||||
FifoSvc commonSvc.FifoService
|
|
||||||
FifoStockV2Svc commonSvc.FifoStockV2Service
|
FifoStockV2Svc commonSvc.FifoStockV2Service
|
||||||
ExpenseBridge TransferExpenseBridge
|
ExpenseBridge TransferExpenseBridge
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, supplierRepo rSupplier.SupplierRepository, warehouseRepo warehouseRepo.WarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, documentSvc commonSvc.DocumentService, fifoSvc commonSvc.FifoService, fifoStockV2Svc commonSvc.FifoStockV2Service, expenseBridge TransferExpenseBridge) TransferService {
|
func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTransfer.StockTransferRepository, stockTransferDetailRepo rStockTransfer.StockTransferDetailRepository, stockTransferDeliveryRepo rStockTransfer.StockTransferDeliveryRepository, stockTransferDeliveryItemRepo rStockTransfer.StockTransferDeliveryItemRepository, stockLogsRepo rStockLogs.StockLogRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, supplierRepo rSupplier.SupplierRepository, warehouseRepo warehouseRepo.WarehouseRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, documentSvc commonSvc.DocumentService, fifoStockV2Svc commonSvc.FifoStockV2Service, expenseBridge TransferExpenseBridge) TransferService {
|
||||||
return &transferService{
|
return &transferService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
@@ -63,7 +62,6 @@ func NewTransferService(validate *validator.Validate, stockTransferRepo rStockTr
|
|||||||
WarehouseRepo: warehouseRepo,
|
WarehouseRepo: warehouseRepo,
|
||||||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||||
DocumentSvc: documentSvc,
|
DocumentSvc: documentSvc,
|
||||||
FifoSvc: fifoSvc,
|
|
||||||
FifoStockV2Svc: fifoStockV2Svc,
|
FifoStockV2Svc: fifoStockV2Svc,
|
||||||
ExpenseBridge: expenseBridge,
|
ExpenseBridge: expenseBridge,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
|||||||
|
|
||||||
for idx, chickin := range newChikins {
|
for idx, chickin := range newChikins {
|
||||||
desiredQty := chickinQtyMap[uint(idx)]
|
desiredQty := chickinQtyMap[uint(idx)]
|
||||||
if err := s.ConsumeChickinStocks(c.Context(), dbTransaction, chickin, desiredQty, actorID); err != nil {
|
if err := s.StageChickinStocks(c.Context(), dbTransaction, chickin, desiredQty, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +368,7 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if chickin.UsageQty > 0 {
|
if chickin.UsageQty > 0 || chickin.PendingUsageQty > 0 {
|
||||||
if err := s.ReleaseChickinStocks(c.Context(), s.Repository.DB(), chickin, actorID); err != nil {
|
if err := s.ReleaseChickinStocks(c.Context(), s.Repository.DB(), chickin, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -479,6 +479,20 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, chickin := range chickins {
|
for _, chickin := range chickins {
|
||||||
|
approvedQty := chickin.UsageQty
|
||||||
|
if approvedQty <= 0 {
|
||||||
|
approvedQty = chickin.PendingUsageQty
|
||||||
|
}
|
||||||
|
if approvedQty < 0 {
|
||||||
|
approvedQty = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.ConsumeChickinStocks(c.Context(), dbTransaction, &chickin, approvedQty, actorID); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to finalize usage qty for chickin %d", chickin.Id))
|
||||||
|
}
|
||||||
|
chickin.UsageQty = approvedQty
|
||||||
|
chickin.PendingUsageQty = 0
|
||||||
|
|
||||||
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(c.Context(), chickin.Id)
|
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(c.Context(), chickin.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to check population for chickin %d", chickin.Id))
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to check population for chickin %d", chickin.Id))
|
||||||
@@ -510,19 +524,13 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to create population for chickin %d", chickin.Id))
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to create population for chickin %d", chickin.Id))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := chickinRepoTx.PatchOne(c.Context(), chickin.Id, map[string]any{
|
|
||||||
"pending_usage_qty": 0,
|
|
||||||
}, nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to reset pending usage qty for chickin %d", chickin.Id))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.ReplenishChickinStocks(c.Context(), dbTransaction, &chickin, sourcePW, population, actorID); err != nil {
|
if err := s.ReplenishChickinStocks(c.Context(), dbTransaction, &chickin, sourcePW, population, actorID); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to replenish stock for chickin %d", chickin.Id))
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to replenish stock for chickin %d", chickin.Id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if action == entity.ApprovalActionRejected {
|
if action == entity.ApprovalActionRejected {
|
||||||
chickins, err := chickinRepoTx.GetPendingByProjectFlockKandangID(c.Context(), approvableID)
|
chickins, err := chickinRepoTx.GetByProjectFlockKandangID(c.Context(), approvableID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get pending chickins for rejection %d", approvableID))
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get pending chickins for rejection %d", approvableID))
|
||||||
}
|
}
|
||||||
@@ -532,6 +540,17 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, chickin := range chickins {
|
for _, chickin := range chickins {
|
||||||
|
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(c.Context(), chickin.Id)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to check population for chickin %d", chickin.Id))
|
||||||
|
}
|
||||||
|
if populationExists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if chickin.UsageQty <= 0 && chickin.PendingUsageQty <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.ReleaseChickinStocks(c.Context(), dbTransaction, &chickin, actorID); err != nil {
|
if err := s.ReleaseChickinStocks(c.Context(), dbTransaction, &chickin, actorID); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to release stock for rejected chickin %d: %v", chickin.Id, err))
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to release stock for rejected chickin %d: %v", chickin.Id, err))
|
||||||
@@ -612,6 +631,20 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
|
|||||||
return s.Repository.UpdateUsageFields(ctx, tx, chickin.Id, desiredQty, 0)
|
return s.Repository.UpdateUsageFields(ctx, tx, chickin.Id, desiredQty, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *chickinService) StageChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, desiredQty float64, actorID uint) error {
|
||||||
|
if chickin == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if tx == nil {
|
||||||
|
return errors.New("transaction is required")
|
||||||
|
}
|
||||||
|
if desiredQty < 0 {
|
||||||
|
return errors.New("desired quantity must be zero or greater")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Repository.UpdateUsageFields(ctx, tx, chickin.Id, 0, desiredQty)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *chickinService) ReplenishChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, targetPW *entity.ProductWarehouse, population *entity.ProjectFlockPopulation, actorID uint) error {
|
func (s *chickinService) ReplenishChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, targetPW *entity.ProductWarehouse, population *entity.ProjectFlockPopulation, actorID uint) error {
|
||||||
if chickin == nil || targetPW == nil || population == nil {
|
if chickin == nil || targetPW == nil || population == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package transfer_layings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -14,7 +13,6 @@ import (
|
|||||||
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
||||||
sTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
sTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
|
|
||||||
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
@@ -34,45 +32,7 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
|
|||||||
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
||||||
productWarehouseRepo := rInventory.NewProductWarehouseRepository(db)
|
productWarehouseRepo := rInventory.NewProductWarehouseRepository(db)
|
||||||
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||||
|
fifoStockV2Service := commonSvc.NewFifoStockV2Service(db, utils.Log)
|
||||||
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
|
|
||||||
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
|
||||||
|
|
||||||
// daftarin jadi stockable
|
|
||||||
if err := fifoService.RegisterStockable(fifo.StockableConfig{
|
|
||||||
Key: fifo.StockableKeyTransferToLayingIn,
|
|
||||||
Table: "laying_transfer_targets",
|
|
||||||
Columns: fifo.StockableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
TotalQuantity: "total_qty",
|
|
||||||
TotalUsedQuantity: "total_used",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
OrderBy: []string{"created_at ASC", "id ASC"},
|
|
||||||
}); err != nil {
|
|
||||||
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
panic(fmt.Sprintf("failed to register transfer to laying stockable workflow: %v", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// daftarin jadi usable
|
|
||||||
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
|
||||||
Key: fifo.UsableKeyTransferToLayingOut,
|
|
||||||
Table: "laying_transfer_sources",
|
|
||||||
Columns: fifo.UsableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
UsageQuantity: "usage_qty",
|
|
||||||
PendingQuantity: "pending_usage_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
OrderBy: []string{"created_at ASC", "id ASC"},
|
|
||||||
}); err != nil {
|
|
||||||
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
panic(fmt.Sprintf("failed to register transfer to laying usable workflow: %v", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||||
@@ -90,7 +50,7 @@ func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, val
|
|||||||
productWarehouseRepo,
|
productWarehouseRepo,
|
||||||
warehouseRepo,
|
warehouseRepo,
|
||||||
approvalService,
|
approvalService,
|
||||||
fifoService,
|
fifoStockV2Service,
|
||||||
validate,
|
validate,
|
||||||
)
|
)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|||||||
+9
-9
@@ -12,12 +12,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
chickinOutFunctionCode = "CHICKIN_OUT"
|
transferLayingInFunctionCode = "TRANSFER_TO_LAYING_IN"
|
||||||
chickinUsableLane = "USABLE"
|
transferLayingStockableLane = "STOCKABLE"
|
||||||
chickinSourceTable = "project_chickins"
|
transferLayingSourceTable = "laying_transfer_targets"
|
||||||
)
|
)
|
||||||
|
|
||||||
func reflowChickinScope(
|
func reflowTransferLayingScope(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
fifoStockV2Svc commonSvc.FifoStockV2Service,
|
fifoStockV2Svc commonSvc.FifoStockV2Service,
|
||||||
tx *gorm.DB,
|
tx *gorm.DB,
|
||||||
@@ -34,7 +34,7 @@ func reflowChickinScope(
|
|||||||
return fmt.Errorf("product warehouse id is required")
|
return fmt.Errorf("product warehouse id is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
flagGroupCode, err := resolveChickinFlagGroupByProductWarehouse(ctx, tx, productWarehouseID)
|
flagGroupCode, err := resolveTransferLayingFlagGroupByProductWarehouse(ctx, tx, productWarehouseID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func reflowChickinScope(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveChickinFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, productWarehouseID uint) (string, error) {
|
func resolveTransferLayingFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB, productWarehouseID uint) (string, error) {
|
||||||
type row struct {
|
type row struct {
|
||||||
FlagGroupCode string `gorm:"column:flag_group_code"`
|
FlagGroupCode string `gorm:"column:flag_group_code"`
|
||||||
}
|
}
|
||||||
@@ -62,9 +62,9 @@ func resolveChickinFlagGroupByProductWarehouse(ctx context.Context, tx *gorm.DB,
|
|||||||
Select("rr.flag_group_code").
|
Select("rr.flag_group_code").
|
||||||
Joins("JOIN fifo_stock_v2_flag_groups fg ON fg.code = rr.flag_group_code AND fg.is_active = TRUE").
|
Joins("JOIN fifo_stock_v2_flag_groups fg ON fg.code = rr.flag_group_code AND fg.is_active = TRUE").
|
||||||
Where("rr.is_active = TRUE").
|
Where("rr.is_active = TRUE").
|
||||||
Where("rr.lane = ?", chickinUsableLane).
|
Where("rr.lane = ?", transferLayingStockableLane).
|
||||||
Where("rr.function_code = ?", chickinOutFunctionCode).
|
Where("rr.function_code = ?", transferLayingInFunctionCode).
|
||||||
Where("rr.source_table = ?", chickinSourceTable).
|
Where("rr.source_table = ?", transferLayingSourceTable).
|
||||||
Where(`
|
Where(`
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/validations"
|
||||||
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -51,7 +50,7 @@ type transferLayingService struct {
|
|||||||
WarehouseRepo rWarehouse.WarehouseRepository
|
WarehouseRepo rWarehouse.WarehouseRepository
|
||||||
StockLogRepo rStockLogs.StockLogRepository
|
StockLogRepo rStockLogs.StockLogRepository
|
||||||
ApprovalService commonSvc.ApprovalService
|
ApprovalService commonSvc.ApprovalService
|
||||||
FifoSvc commonSvc.FifoService
|
FifoStockV2Svc commonSvc.FifoStockV2Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransferLayingService(
|
func NewTransferLayingService(
|
||||||
@@ -64,7 +63,7 @@ func NewTransferLayingService(
|
|||||||
productWarehouseRepo rInventory.ProductWarehouseRepository,
|
productWarehouseRepo rInventory.ProductWarehouseRepository,
|
||||||
warehouseRepo rWarehouse.WarehouseRepository,
|
warehouseRepo rWarehouse.WarehouseRepository,
|
||||||
approvalService commonSvc.ApprovalService,
|
approvalService commonSvc.ApprovalService,
|
||||||
fifoSvc commonSvc.FifoService,
|
fifoStockV2Svc commonSvc.FifoStockV2Service,
|
||||||
validate *validator.Validate,
|
validate *validator.Validate,
|
||||||
) TransferLayingService {
|
) TransferLayingService {
|
||||||
return &transferLayingService{
|
return &transferLayingService{
|
||||||
@@ -80,7 +79,7 @@ func NewTransferLayingService(
|
|||||||
WarehouseRepo: warehouseRepo,
|
WarehouseRepo: warehouseRepo,
|
||||||
StockLogRepo: rStockLogs.NewStockLogRepository(repo.DB()),
|
StockLogRepo: rStockLogs.NewStockLogRepository(repo.DB()),
|
||||||
ApprovalService: approvalService,
|
ApprovalService: approvalService,
|
||||||
FifoSvc: fifoSvc,
|
FifoStockV2Svc: fifoStockV2Svc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,7 +743,6 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
repoTx := s.Repository.WithTx(dbTransaction)
|
repoTx := s.Repository.WithTx(dbTransaction)
|
||||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
stockAllocationRepo := commonRepo.NewStockAllocationRepository(dbTransaction)
|
|
||||||
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
||||||
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
|
targetRepoTx := repository.NewLayingTransferTargetRepository(dbTransaction)
|
||||||
stockLogRepoTx := rStockLogs.NewStockLogRepository(dbTransaction)
|
stockLogRepoTx := rStockLogs.NewStockLogRepository(dbTransaction)
|
||||||
@@ -771,6 +769,9 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if action == entity.ApprovalActionApproved {
|
if action == entity.ApprovalActionApproved {
|
||||||
|
if s.FifoStockV2Svc == nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "FIFO v2 service is not available")
|
||||||
|
}
|
||||||
|
|
||||||
sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), approvableID)
|
sources, err := sourceRepoTx.GetByLayingTransferId(c.Context(), approvableID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -792,58 +793,70 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
totalSourceRequested += source.RequestedQty
|
totalSourceRequested += source.RequestedQty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceBeforeUsage := make(map[uint]float64, len(sources))
|
||||||
|
affectedPW := make(map[uint]struct{})
|
||||||
|
|
||||||
for _, source := range sources {
|
for _, source := range sources {
|
||||||
if source.ProductWarehouseId == nil {
|
if source.ProductWarehouseId == nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse tidak ditemukan untuk transfer %d", approvableID))
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Source product warehouse tidak ditemukan untuk transfer %d", approvableID))
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceShare := (source.RequestedQty / totalSourceRequested) * totalTargetQty
|
sourceShare := 0.0
|
||||||
|
if totalSourceRequested > 0 {
|
||||||
consumeResult, err := s.FifoSvc.Consume(c.Context(), commonSvc.StockConsumeRequest{
|
sourceShare = (source.RequestedQty / totalSourceRequested) * totalTargetQty
|
||||||
UsableKey: fifo.UsableKeyTransferToLayingOut,
|
|
||||||
UsableID: source.Id,
|
|
||||||
ProductWarehouseID: *source.ProductWarehouseId,
|
|
||||||
Quantity: sourceShare,
|
|
||||||
AllowPending: false,
|
|
||||||
Tx: dbTransaction,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal consume FIFO stock: %v", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceBeforeUsage[source.Id] = source.UsageQty
|
||||||
|
|
||||||
if err := sourceRepoTx.PatchOne(c.Context(), source.Id, map[string]interface{}{
|
if err := sourceRepoTx.PatchOne(c.Context(), source.Id, map[string]interface{}{
|
||||||
"usage_qty": source.UsageQty + consumeResult.UsageQuantity,
|
"usage_qty": sourceShare,
|
||||||
"pending_usage_qty": consumeResult.PendingQuantity,
|
"pending_usage_qty": 0,
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty")
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update source usage qty")
|
||||||
}
|
}
|
||||||
|
|
||||||
targetShares := distributeProportionalWithRounding(targets, totalTargetQty, sourceShare)
|
affectedPW[*source.ProductWarehouseId] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
for i, target := range targets {
|
for _, target := range targets {
|
||||||
roundedQty := math.Round(targetShares[i])
|
if target.ProductWarehouseId == nil {
|
||||||
if roundedQty <= 0 {
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Target product warehouse tidak ditemukan untuk transfer %d", approvableID))
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
mappingAllocation := &entity.StockAllocation{
|
if err := targetRepoTx.PatchOne(c.Context(), target.Id, map[string]interface{}{
|
||||||
StockableType: fifo.UsableKeyTransferToLayingOut.String(),
|
"total_qty": target.TotalQty,
|
||||||
StockableId: source.Id,
|
}, nil); err != nil {
|
||||||
UsableType: fifo.StockableKeyTransferToLayingIn.String(),
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update target total qty")
|
||||||
UsableId: target.Id,
|
}
|
||||||
ProductWarehouseId: *source.ProductWarehouseId,
|
affectedPW[*target.ProductWarehouseId] = struct{}{}
|
||||||
Qty: roundedQty,
|
}
|
||||||
Status: entity.StockAllocationStatusActive,
|
|
||||||
}
|
for pwID := range affectedPW {
|
||||||
if err := stockAllocationRepo.CreateOne(c.Context(), mappingAllocation, nil); err != nil {
|
asOfCopy := transfer.TransferDate
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal create mapping allocation source→target")
|
if err := reflowTransferLayingScope(c.Context(), s.FifoStockV2Svc, dbTransaction, pwID, &asOfCopy); err != nil {
|
||||||
}
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gagal reflow FIFO stock transfer laying (pw=%d): %v", pwID, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, source := range sources {
|
||||||
|
if source.ProductWarehouseId == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
refreshedSource, err := sourceRepoTx.GetByID(c.Context(), source.Id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal refresh source transfer setelah reflow")
|
||||||
|
}
|
||||||
|
|
||||||
|
usageDelta := refreshedSource.UsageQty - sourceBeforeUsage[source.Id]
|
||||||
|
if usageDelta <= 0 {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
stockLogDecrease := &entity.StockLog{
|
stockLogDecrease := &entity.StockLog{
|
||||||
ProductWarehouseId: *source.ProductWarehouseId,
|
ProductWarehouseId: *source.ProductWarehouseId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
Increase: 0,
|
Increase: 0,
|
||||||
Decrease: sourceShare,
|
Decrease: usageDelta,
|
||||||
LoggableType: string(utils.StockLogTypeTransferLaying),
|
LoggableType: string(utils.StockLogTypeTransferLaying),
|
||||||
LoggableId: approvableID,
|
LoggableId: approvableID,
|
||||||
Notes: fmt.Sprintf("TL #%s", transfer.TransferNumber),
|
Notes: fmt.Sprintf("TL #%s", transfer.TransferNumber),
|
||||||
@@ -867,26 +880,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
|
|
||||||
for _, target := range targets {
|
for _, target := range targets {
|
||||||
if target.ProductWarehouseId == nil {
|
if target.ProductWarehouseId == nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Target product warehouse tidak ditemukan untuk transfer %d", approvableID))
|
continue
|
||||||
}
|
|
||||||
|
|
||||||
note := fmt.Sprintf("Transfer to Laying #%s", transfer.TransferNumber)
|
|
||||||
_, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
|
|
||||||
StockableKey: fifo.StockableKeyTransferToLayingIn,
|
|
||||||
StockableID: target.Id,
|
|
||||||
ProductWarehouseID: *target.ProductWarehouseId,
|
|
||||||
Quantity: target.TotalQty,
|
|
||||||
Note: ¬e,
|
|
||||||
Tx: dbTransaction,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Gagal replenish stock ke target warehouse: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := targetRepoTx.PatchOne(c.Context(), target.Id, map[string]interface{}{
|
|
||||||
"total_qty": target.TotalQty,
|
|
||||||
}, nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal update target total qty")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stockLogIncrease := &entity.StockLog{
|
stockLogIncrease := &entity.StockLog{
|
||||||
|
|||||||
@@ -256,35 +256,13 @@ func (s *purchaseService) GetOne(c *fiber.Ctx, id uint) (*entity.Purchase, error
|
|||||||
s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", id, err)
|
s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", id, err)
|
||||||
}
|
}
|
||||||
if len(purchase.Items) > 0 {
|
if len(purchase.Items) > 0 {
|
||||||
itemIDs := make([]uint, 0, len(purchase.Items))
|
lockedIDs, err := s.resolveChickinLockedItemIDs(c.Context(), s.PurchaseRepo.DB(), purchase.Items)
|
||||||
for i := range purchase.Items {
|
if err != nil {
|
||||||
if purchase.Items[i].Id == 0 {
|
return nil, err
|
||||||
continue
|
|
||||||
}
|
|
||||||
itemIDs = append(itemIDs, purchase.Items[i].Id)
|
|
||||||
}
|
}
|
||||||
if len(itemIDs) > 0 {
|
for i := range purchase.Items {
|
||||||
var usedIDs []uint
|
if _, ok := lockedIDs[purchase.Items[i].Id]; ok {
|
||||||
if err := s.PurchaseRepo.DB().WithContext(c.Context()).
|
purchase.Items[i].HasChickin = true
|
||||||
Model(&entity.StockAllocation{}).
|
|
||||||
Distinct("stockable_id").
|
|
||||||
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
|
|
||||||
fifo.StockableKeyPurchaseItems.String(),
|
|
||||||
itemIDs,
|
|
||||||
fifo.UsableKeyProjectChickin.String(),
|
|
||||||
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
|
|
||||||
).
|
|
||||||
Pluck("stockable_id", &usedIDs).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
usedSet := make(map[uint]struct{}, len(usedIDs))
|
|
||||||
for _, id := range usedIDs {
|
|
||||||
usedSet[id] = struct{}{}
|
|
||||||
}
|
|
||||||
for i := range purchase.Items {
|
|
||||||
if _, ok := usedSet[purchase.Items[i].Id]; ok {
|
|
||||||
purchase.Items[i].HasChickin = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -532,48 +510,31 @@ func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *valid
|
|||||||
}
|
}
|
||||||
|
|
||||||
if action == entity.ApprovalActionApproved {
|
if action == entity.ApprovalActionApproved {
|
||||||
itemIDs := make([]uint, 0, len(purchase.Items))
|
|
||||||
itemByID := make(map[uint]entity.PurchaseItem, len(purchase.Items))
|
itemByID := make(map[uint]entity.PurchaseItem, len(purchase.Items))
|
||||||
for i := range purchase.Items {
|
for i := range purchase.Items {
|
||||||
if purchase.Items[i].Id == 0 {
|
if purchase.Items[i].Id == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
itemIDs = append(itemIDs, purchase.Items[i].Id)
|
|
||||||
itemByID[purchase.Items[i].Id] = purchase.Items[i]
|
itemByID[purchase.Items[i].Id] = purchase.Items[i]
|
||||||
}
|
}
|
||||||
if len(itemIDs) > 0 {
|
lockedIDs, err := s.resolveChickinLockedItemIDs(ctx, s.PurchaseRepo.DB(), purchase.Items)
|
||||||
var usedIDs []uint
|
if err != nil {
|
||||||
if err := s.PurchaseRepo.DB().WithContext(ctx).
|
return nil, err
|
||||||
Model(&entity.StockAllocation{}).
|
}
|
||||||
Distinct("stockable_id").
|
if len(lockedIDs) > 0 {
|
||||||
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
|
for _, payload := range req.Items {
|
||||||
fifo.StockableKeyPurchaseItems.String(),
|
if payload.PurchaseItemID == 0 || payload.Qty == nil {
|
||||||
itemIDs,
|
continue
|
||||||
fifo.UsableKeyProjectChickin.String(),
|
|
||||||
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
|
|
||||||
).
|
|
||||||
Pluck("stockable_id", &usedIDs).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(usedIDs) > 0 {
|
|
||||||
usedSet := make(map[uint]struct{}, len(usedIDs))
|
|
||||||
for _, id := range usedIDs {
|
|
||||||
usedSet[id] = struct{}{}
|
|
||||||
}
|
}
|
||||||
for _, payload := range req.Items {
|
if _, locked := lockedIDs[payload.PurchaseItemID]; !locked {
|
||||||
if payload.PurchaseItemID == 0 || payload.Qty == nil {
|
continue
|
||||||
continue
|
}
|
||||||
}
|
item, ok := itemByID[payload.PurchaseItemID]
|
||||||
if _, used := usedSet[payload.PurchaseItemID]; !used {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
item, ok := itemByID[payload.PurchaseItemID]
|
if *payload.Qty != item.SubQty {
|
||||||
if !ok {
|
return nil, utils.BadRequest("Purchase sudah chickin, qty tidak bisa diubah")
|
||||||
continue
|
|
||||||
}
|
|
||||||
if *payload.Qty != item.SubQty {
|
|
||||||
return nil, utils.BadRequest("Purchase sudah chickin, qty tidak bisa diubah")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -827,49 +788,32 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if action == entity.ApprovalActionApproved {
|
if action == entity.ApprovalActionApproved {
|
||||||
itemIDs := make([]uint, 0, len(purchase.Items))
|
|
||||||
itemByID := make(map[uint]entity.PurchaseItem, len(purchase.Items))
|
itemByID := make(map[uint]entity.PurchaseItem, len(purchase.Items))
|
||||||
for i := range purchase.Items {
|
for i := range purchase.Items {
|
||||||
if purchase.Items[i].Id == 0 {
|
if purchase.Items[i].Id == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
itemIDs = append(itemIDs, purchase.Items[i].Id)
|
|
||||||
itemByID[purchase.Items[i].Id] = purchase.Items[i]
|
itemByID[purchase.Items[i].Id] = purchase.Items[i]
|
||||||
}
|
}
|
||||||
if len(itemIDs) > 0 {
|
lockedIDs, err := s.resolveChickinLockedItemIDs(ctx, s.PurchaseRepo.DB(), purchase.Items)
|
||||||
var usedIDs []uint
|
if err != nil {
|
||||||
if err := s.PurchaseRepo.DB().WithContext(ctx).
|
return nil, err
|
||||||
Model(&entity.StockAllocation{}).
|
}
|
||||||
Distinct("stockable_id").
|
if len(lockedIDs) > 0 {
|
||||||
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
|
for _, payload := range req.Items {
|
||||||
fifo.StockableKeyPurchaseItems.String(),
|
if _, used := lockedIDs[payload.PurchaseItemID]; !used {
|
||||||
itemIDs,
|
continue
|
||||||
fifo.UsableKeyProjectChickin.String(),
|
|
||||||
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
|
|
||||||
).
|
|
||||||
Pluck("stockable_id", &usedIDs).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(usedIDs) > 0 {
|
|
||||||
usedSet := make(map[uint]struct{}, len(usedIDs))
|
|
||||||
for _, id := range usedIDs {
|
|
||||||
usedSet[id] = struct{}{}
|
|
||||||
}
|
}
|
||||||
for _, payload := range req.Items {
|
item, ok := itemByID[payload.PurchaseItemID]
|
||||||
if _, used := usedSet[payload.PurchaseItemID]; !used {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
item, ok := itemByID[payload.PurchaseItemID]
|
receivedQty := item.SubQty
|
||||||
if !ok {
|
if payload.ReceivedQty != nil {
|
||||||
continue
|
receivedQty = *payload.ReceivedQty
|
||||||
}
|
}
|
||||||
receivedQty := item.SubQty
|
if receivedQty != item.TotalQty {
|
||||||
if payload.ReceivedQty != nil {
|
return nil, utils.BadRequest("Purchase sudah chickin, qty penerimaan tidak bisa diubah")
|
||||||
receivedQty = *payload.ReceivedQty
|
|
||||||
}
|
|
||||||
if receivedQty != item.TotalQty {
|
|
||||||
return nil, utils.BadRequest("Purchase sudah chickin, qty penerimaan tidak bisa diubah")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1437,28 +1381,12 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transactionErr := s.PurchaseRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
transactionErr := s.PurchaseRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
itemIDs := make([]uint, 0, len(itemsToDelete))
|
lockedIDs, err := s.resolveChickinLockedItemIDs(ctx, tx, itemsToDelete)
|
||||||
for _, item := range itemsToDelete {
|
if err != nil {
|
||||||
if item.Id == 0 {
|
return err
|
||||||
continue
|
|
||||||
}
|
|
||||||
itemIDs = append(itemIDs, item.Id)
|
|
||||||
}
|
}
|
||||||
if len(itemIDs) > 0 {
|
if len(lockedIDs) > 0 {
|
||||||
var count int64
|
return utils.BadRequest("Purchase already chickin, failed to delete purchase")
|
||||||
if err := tx.Model(&entity.StockAllocation{}).
|
|
||||||
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
|
|
||||||
fifo.StockableKeyPurchaseItems.String(),
|
|
||||||
itemIDs,
|
|
||||||
fifo.UsableKeyProjectChickin.String(),
|
|
||||||
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
|
|
||||||
).
|
|
||||||
Count(&count).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
return utils.BadRequest("Purchase already chickin, failed to delete purchase")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.rollbackPurchaseStock(ctx, tx, itemsToDelete, note, actorID); err != nil {
|
if err := s.rollbackPurchaseStock(ctx, tx, itemsToDelete, note, actorID); err != nil {
|
||||||
@@ -1957,6 +1885,67 @@ func (s *purchaseService) applyTravelDocumentURLs(ctx context.Context, purchase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectPurchaseItemIDs(items []entity.PurchaseItem) []uint {
|
||||||
|
itemIDs := make([]uint, 0, len(items))
|
||||||
|
for i := range items {
|
||||||
|
if items[i].Id == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
itemIDs = append(itemIDs, items[i].Id)
|
||||||
|
}
|
||||||
|
return itemIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *purchaseService) resolveChickinLockedItemIDs(ctx context.Context, db *gorm.DB, items []entity.PurchaseItem) (map[uint]struct{}, error) {
|
||||||
|
itemIDs := collectPurchaseItemIDs(items)
|
||||||
|
return s.resolveChickinLockedItemIDsByItemID(ctx, db, itemIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *purchaseService) resolveChickinLockedItemIDsByItemID(ctx context.Context, db *gorm.DB, itemIDs []uint) (map[uint]struct{}, error) {
|
||||||
|
locked := make(map[uint]struct{})
|
||||||
|
if len(itemIDs) == 0 {
|
||||||
|
return locked, nil
|
||||||
|
}
|
||||||
|
if db == nil {
|
||||||
|
return nil, errors.New("database is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var allocationLockedIDs []uint
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Model(&entity.StockAllocation{}).
|
||||||
|
Distinct("stockable_id").
|
||||||
|
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
|
||||||
|
fifo.StockableKeyPurchaseItems.String(),
|
||||||
|
itemIDs,
|
||||||
|
fifo.UsableKeyProjectChickin.String(),
|
||||||
|
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
|
||||||
|
).
|
||||||
|
Pluck("stockable_id", &allocationLockedIDs).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, itemID := range allocationLockedIDs {
|
||||||
|
locked[itemID] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var conversionLockedIDs []uint
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Table("purchase_items pi").
|
||||||
|
Distinct("pi.id").
|
||||||
|
Joins("JOIN project_chickins pc ON pc.product_warehouse_id = pi.product_warehouse_id AND pc.deleted_at IS NULL").
|
||||||
|
Joins("JOIN project_flock_populations pfp ON pfp.project_chickin_id = pc.id AND pfp.deleted_at IS NULL").
|
||||||
|
Where("pi.id IN ?", itemIDs).
|
||||||
|
Where("pi.project_flock_kandang_id IS NOT NULL").
|
||||||
|
Where("pc.project_flock_kandang_id = pi.project_flock_kandang_id").
|
||||||
|
Pluck("pi.id", &conversionLockedIDs).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, itemID := range conversionLockedIDs {
|
||||||
|
locked[itemID] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return locked, nil
|
||||||
|
}
|
||||||
|
|
||||||
func collectPFKIDsFromPurchase(p *entity.Purchase) []uint {
|
func collectPFKIDsFromPurchase(p *entity.Purchase) []uint {
|
||||||
seen := make(map[uint]struct{})
|
seen := make(map[uint]struct{})
|
||||||
ids := make([]uint, 0)
|
ids := make([]uint, 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user