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

This commit is contained in:
ragilap
2025-12-08 12:12:21 +07:00
parent f8e0614d50
commit fc9197d00a
3 changed files with 63 additions and 8 deletions
@@ -39,7 +39,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
ProductWarehouseID: "product_warehouse_id", ProductWarehouseID: "product_warehouse_id",
UsageQuantity: "usage_qty", UsageQuantity: "usage_qty",
PendingQuantity: "pending_qty", PendingQuantity: "pending_qty",
CreatedAt: "created_at", CreatedAt: "id",
}, },
}); err != nil { }); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "already registered") { if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
+17
View File
@@ -21,6 +21,7 @@ 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"
utils "gitlab.com/mbugroup/lti-api.git/internal/utils" utils "gitlab.com/mbugroup/lti-api.git/internal/utils"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -36,6 +37,7 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
expenseRepository := expenseRepo.NewExpenseRepository(db) expenseRepository := expenseRepo.NewExpenseRepository(db)
expenseRealizationRepo := expenseRepo.NewExpenseRealizationRepository(db) expenseRealizationRepo := expenseRepo.NewExpenseRealizationRepository(db)
projectFlockKandangRepository := projectFlockKandangRepo.NewProjectFlockKandangRepository(db) projectFlockKandangRepository := projectFlockKandangRepo.NewProjectFlockKandangRepository(db)
stockAllocRepo := commonRepo.NewStockAllocationRepository(db)
approvalRepo := commonRepo.NewApprovalRepository(db) approvalRepo := commonRepo.NewApprovalRepository(db)
approvalService := commonSvc.NewApprovalService(approvalRepo) approvalService := commonSvc.NewApprovalService(approvalRepo)
@@ -61,6 +63,20 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
expenseServiceInstance, 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( purchaseService := service.NewPurchaseService(
validate, validate,
purchaseRepo, purchaseRepo,
@@ -71,6 +87,7 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
projectFlockKandangRepository, projectFlockKandangRepository,
approvalService, approvalService,
expenseBridge, expenseBridge,
fifoService,
) )
userRepo := rUser.NewUserRepository(db) userRepo := rUser.NewUserRepository(db)
@@ -21,6 +21,7 @@ import (
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/validations" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/validations"
"gitlab.com/mbugroup/lti-api.git/internal/utils" "gitlab.com/mbugroup/lti-api.git/internal/utils"
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" 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/go-playground/validator/v10"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@@ -40,7 +41,8 @@ type PurchaseService interface {
} }
const ( const (
priceTolerance = 0.0001 priceTolerance = 0.0001
purchaseStockableKey = fifo.StockableKey("PURCHASE_ITEMS")
) )
type purchaseService struct { type purchaseService struct {
@@ -54,6 +56,7 @@ type purchaseService struct {
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
ApprovalSvc commonSvc.ApprovalService ApprovalSvc commonSvc.ApprovalService
ExpenseBridge PurchaseExpenseBridge ExpenseBridge PurchaseExpenseBridge
FifoSvc commonSvc.FifoService
approvalWorkflow approvalutils.ApprovalWorkflowKey approvalWorkflow approvalutils.ApprovalWorkflowKey
} }
@@ -72,6 +75,7 @@ func NewPurchaseService(
projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository,
approvalSvc commonSvc.ApprovalService, approvalSvc commonSvc.ApprovalService,
expenseBridge PurchaseExpenseBridge, expenseBridge PurchaseExpenseBridge,
fifoSvc commonSvc.FifoService,
) PurchaseService { ) PurchaseService {
return &purchaseService{ return &purchaseService{
Log: utils.Log, Log: utils.Log,
@@ -84,6 +88,7 @@ func NewPurchaseService(
ProjectFlockKandangRepo: projectFlockKandangRepo, ProjectFlockKandangRepo: projectFlockKandangRepo,
ApprovalSvc: approvalSvc, ApprovalSvc: approvalSvc,
ExpenseBridge: expenseBridge, ExpenseBridge: expenseBridge,
FifoSvc: fifoSvc,
approvalWorkflow: utils.ApprovalWorkflowPurchase, approvalWorkflow: utils.ApprovalWorkflowPurchase,
} }
} }
@@ -712,6 +717,9 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
if warehouseID == 0 { if warehouseID == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Warehouse must be specified for item %d", payload.PurchaseItemID)) 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 var receivedQty float64
if payload.ReceivedQty != nil { if payload.ReceivedQty != nil {
@@ -798,6 +806,11 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
deltas := make(map[uint]float64) deltas := make(map[uint]float64)
affected := make(map[uint]struct{}) affected := make(map[uint]struct{})
updates := make([]rPurchase.PurchaseReceivingUpdate, 0, len(prepared)) updates := make([]rPurchase.PurchaseReceivingUpdate, 0, len(prepared))
fifoAdds := make([]struct {
itemID uint
pwID uint
qty float64
}, 0, len(prepared))
for _, prep := range prepared { for _, prep := range prepared {
item := prep.item item := prep.item
@@ -811,21 +824,29 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
var newPWID *uint var newPWID *uint
clearPW := false clearPW := false
// Always ensure PW when qty > 0 so stockable has target.
if prep.receivedQty > 0 { if prep.receivedQty > 0 {
pwID, err := pwRepoTx.EnsureProductWarehouse(c.Context(), uint(item.ProductId), prep.warehouseID, purchase.CreatedBy) pwID, err := pwRepoTx.EnsureProductWarehouse(c.Context(), uint(item.ProductId), prep.warehouseID, purchase.CreatedBy)
if err != nil { if err != nil {
return err return err
} }
newPWID = &pwID newPWID = &pwID
deltas[pwID] += prep.receivedQty } else if oldPWID != nil {
affected[pwID] = struct{}{} newPWID = oldPWID
} else {
clearPW = true clearPW = true
} }
if oldPWID != nil { deltaQty := prep.receivedQty - item.TotalQty
deltas[*oldPWID] -= item.TotalQty switch {
affected[*oldPWID] = struct{}{} 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 dateCopy := prep.receivedDate
@@ -861,6 +882,23 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
return err 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 return nil
}) })
if transactionErr != nil { if transactionErr != nil {