From fc9197d00a670e24450a315101ea1e5755df4036 Mon Sep 17 00:00:00 2001 From: ragilap Date: Mon, 8 Dec 2025 12:12:21 +0700 Subject: [PATCH] feat/BE/US-278/TASK-288,289-adjust schema database,Create trigger in expense module, add filter in warehouse linked to project flock, and implement fifo system --- .../modules/production/recordings/module.go | 2 +- internal/modules/purchases/module.go | 17 ++++++ .../purchases/services/purchase.service.go | 52 ++++++++++++++++--- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/internal/modules/production/recordings/module.go b/internal/modules/production/recordings/module.go index 341031e1..a19faa33 100644 --- a/internal/modules/production/recordings/module.go +++ b/internal/modules/production/recordings/module.go @@ -39,7 +39,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate ProductWarehouseID: "product_warehouse_id", UsageQuantity: "usage_qty", PendingQuantity: "pending_qty", - CreatedAt: "created_at", + CreatedAt: "id", }, }); err != nil { if !strings.Contains(strings.ToLower(err.Error()), "already registered") { diff --git a/internal/modules/purchases/module.go b/internal/modules/purchases/module.go index 60f68edc..ec1b24f7 100644 --- a/internal/modules/purchases/module.go +++ b/internal/modules/purchases/module.go @@ -21,6 +21,7 @@ import ( rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" utils "gitlab.com/mbugroup/lti-api.git/internal/utils" + "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" "gorm.io/gorm" ) @@ -36,6 +37,7 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate expenseRepository := expenseRepo.NewExpenseRepository(db) expenseRealizationRepo := expenseRepo.NewExpenseRealizationRepository(db) projectFlockKandangRepository := projectFlockKandangRepo.NewProjectFlockKandangRepository(db) + stockAllocRepo := commonRepo.NewStockAllocationRepository(db) approvalRepo := commonRepo.NewApprovalRepository(db) approvalService := commonSvc.NewApprovalService(approvalRepo) @@ -61,6 +63,20 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate expenseServiceInstance, ) + fifoService := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log) + _ = fifoService.RegisterStockable(fifo.StockableConfig{ + Key: fifo.StockableKey("PURCHASE_ITEMS"), + Table: "purchase_items", + Columns: fifo.StockableColumns{ + ID: "id", + ProductWarehouseID: "product_warehouse_id", + TotalQuantity: "total_qty", + TotalUsedQuantity: "total_used", + CreatedAt: "id", + }, + OrderBy: []string{"id ASC"}, + }) + purchaseService := service.NewPurchaseService( validate, purchaseRepo, @@ -71,6 +87,7 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate projectFlockKandangRepository, approvalService, expenseBridge, + fifoService, ) userRepo := rUser.NewUserRepository(db) diff --git a/internal/modules/purchases/services/purchase.service.go b/internal/modules/purchases/services/purchase.service.go index 6874fd8b..bbaa1b40 100644 --- a/internal/modules/purchases/services/purchase.service.go +++ b/internal/modules/purchases/services/purchase.service.go @@ -21,6 +21,7 @@ import ( validation "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" + "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" @@ -40,7 +41,8 @@ type PurchaseService interface { } const ( - priceTolerance = 0.0001 + priceTolerance = 0.0001 + purchaseStockableKey = fifo.StockableKey("PURCHASE_ITEMS") ) type purchaseService struct { @@ -54,6 +56,7 @@ type purchaseService struct { ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository ApprovalSvc commonSvc.ApprovalService ExpenseBridge PurchaseExpenseBridge + FifoSvc commonSvc.FifoService approvalWorkflow approvalutils.ApprovalWorkflowKey } @@ -72,6 +75,7 @@ func NewPurchaseService( projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, approvalSvc commonSvc.ApprovalService, expenseBridge PurchaseExpenseBridge, + fifoSvc commonSvc.FifoService, ) PurchaseService { return &purchaseService{ Log: utils.Log, @@ -84,6 +88,7 @@ func NewPurchaseService( ProjectFlockKandangRepo: projectFlockKandangRepo, ApprovalSvc: approvalSvc, ExpenseBridge: expenseBridge, + FifoSvc: fifoSvc, approvalWorkflow: utils.ApprovalWorkflowPurchase, } } @@ -712,6 +717,9 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation if warehouseID == 0 { return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Warehouse must be specified for item %d", payload.PurchaseItemID)) } + if payload.WarehouseID != nil && uint(item.WarehouseId) != warehouseID { + return nil, fiber.NewError(fiber.StatusBadRequest, "Receiving does not allow changing warehouse") + } var receivedQty float64 if payload.ReceivedQty != nil { @@ -798,6 +806,11 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation deltas := make(map[uint]float64) affected := make(map[uint]struct{}) updates := make([]rPurchase.PurchaseReceivingUpdate, 0, len(prepared)) + fifoAdds := make([]struct { + itemID uint + pwID uint + qty float64 + }, 0, len(prepared)) for _, prep := range prepared { item := prep.item @@ -811,21 +824,29 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation var newPWID *uint clearPW := false + // Always ensure PW when qty > 0 so stockable has target. if prep.receivedQty > 0 { pwID, err := pwRepoTx.EnsureProductWarehouse(c.Context(), uint(item.ProductId), prep.warehouseID, purchase.CreatedBy) if err != nil { return err } newPWID = &pwID - deltas[pwID] += prep.receivedQty - affected[pwID] = struct{}{} - } else { + } else if oldPWID != nil { + newPWID = oldPWID clearPW = true } - if oldPWID != nil { - deltas[*oldPWID] -= item.TotalQty - affected[*oldPWID] = struct{}{} + deltaQty := prep.receivedQty - item.TotalQty + switch { + case deltaQty > 0 && newPWID != nil: + fifoAdds = append(fifoAdds, struct { + itemID uint + pwID uint + qty float64 + }{itemID: item.Id, pwID: *newPWID, qty: deltaQty}) + case deltaQty < 0 && newPWID != nil: + deltas[*newPWID] += deltaQty // negative + affected[*newPWID] = struct{}{} } dateCopy := prep.receivedDate @@ -861,6 +882,23 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation return err } + if s.FifoSvc != nil { + for _, adj := range fifoAdds { + if adj.pwID == 0 || adj.qty <= 0 { + continue + } + if _, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{ + StockableKey: purchaseStockableKey, + StockableID: adj.itemID, + ProductWarehouseID: adj.pwID, + Quantity: adj.qty, + Tx: tx, + }); err != nil { + return err + } + } + } + return nil }) if transactionErr != nil {