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
+17
View File
@@ -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)
@@ -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 {