Merge branch 'development' of https://gitlab.com/mbugroup/lti-api into feat/BE/sso-adjustment

This commit is contained in:
ragilap
2026-01-24 19:03:52 +07:00
109 changed files with 6479 additions and 2422 deletions
@@ -36,6 +36,7 @@ type ExpenseReceivingPayload struct {
TransportPerItem *float64
ReceivedQty float64
ReceivedDate *time.Time
VehicleNumber *string
}
type groupedItem struct {
@@ -166,12 +167,21 @@ func (b *expenseBridge) markExpensesUpdated(ctx context.Context, expenseIDs map[
if actorID == 0 {
actorID = 1
}
svc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(b.db))
action := entity.ApprovalActionUpdated
approvalRepo := commonRepo.NewApprovalRepository(b.db)
svc := commonSvc.NewApprovalService(approvalRepo)
action := entity.ApprovalActionCreated
for id := range expenseIDs {
if _, err := svc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
latestApproval, err := approvalRepo.LatestByTarget(ctx, string(utils.ApprovalWorkflowExpense), uint(id), nil)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
if latestApproval == nil {
if _, err := svc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
return err
}
}
}
return nil
}
@@ -182,6 +192,22 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
}
ctx := c.Context()
filtered := make([]ExpenseReceivingPayload, 0, len(updates))
for _, upd := range updates {
if upd.SupplierID == 0 {
continue
}
if upd.TransportPerItem == nil || *upd.TransportPerItem <= 0 {
continue
}
if upd.VehicleNumber == nil || strings.TrimSpace(*upd.VehicleNumber) == "" {
continue
}
filtered = append(filtered, upd)
}
if len(filtered) == 0 {
return nil
}
// Load current links to decide whether to update in place or recreate.
type itemLink struct {
@@ -205,9 +231,9 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
itemLinks := make(map[uint]itemLink)
updatedExpenses := make(map[uint64]struct{})
if len(updates) > 0 {
ids := make([]uint, 0, len(updates))
for _, upd := range updates {
if len(filtered) > 0 {
ids := make([]uint, 0, len(filtered))
for _, upd := range filtered {
if upd.PurchaseItemID != 0 {
ids = append(ids, upd.PurchaseItemID)
}
@@ -252,7 +278,7 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
groups := make(map[string][]groupedItem)
for _, payload := range updates {
for _, payload := range filtered {
if payload.ReceivedDate == nil {
return fiber.NewError(fiber.StatusBadRequest, "received_date is required")
}
@@ -18,6 +18,7 @@ import (
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
projectFlockKandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
rPurchase "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/validations"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
@@ -757,6 +758,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
warehouseID uint
supplierID uint
transportPerItem *float64
vehicleNumber *string
overrideWarehouse bool
receivedQty float64
}
@@ -805,13 +807,16 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
if receivedQty > item.SubQty {
return nil, utils.BadRequest(fmt.Sprintf("Received quantity for item %d cannot exceed ordered quantity (%.3f)", payload.PurchaseItemID, item.SubQty))
}
if receivedQty < item.TotalUsed {
return nil, utils.BadRequest(fmt.Sprintf("Received quantity for item %d cannot be lower than used amount (%.3f)", payload.PurchaseItemID, item.TotalUsed))
}
if _, dup := visitedItems[payload.PurchaseItemID]; dup {
return nil, utils.BadRequest(fmt.Sprintf("Duplicate receiving data for item %d", payload.PurchaseItemID))
}
visitedItems[payload.PurchaseItemID] = struct{}{}
supplierID := purchase.SupplierId
var supplierID uint
if payload.ExpeditionVendorID != nil && *payload.ExpeditionVendorID != 0 {
supplierID = *payload.ExpeditionVendorID
}
@@ -825,6 +830,15 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
transportPerItem = &val
}
var vehicleNumber *string
if payload.VehicleNumber != nil && strings.TrimSpace(*payload.VehicleNumber) != "" {
val := strings.TrimSpace(*payload.VehicleNumber)
vehicleNumber = &val
} else if item.VehicleNumber != nil && strings.TrimSpace(*item.VehicleNumber) != "" {
val := strings.TrimSpace(*item.VehicleNumber)
vehicleNumber = &val
}
prepared = append(prepared, preparedReceiving{
item: item,
payload: payload,
@@ -832,6 +846,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
warehouseID: warehouseID,
supplierID: supplierID,
transportPerItem: transportPerItem,
vehicleNumber: vehicleNumber,
overrideWarehouse: overrideWarehouse,
receivedQty: receivedQty,
})
@@ -871,19 +886,37 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
receivingAction = entity.ApprovalActionUpdated
}
}
noteSuffix := "receive"
if receivingAction == entity.ApprovalActionUpdated {
noteSuffix = "edit-receive"
}
receiveNote := fmt.Sprintf("%s#%s", strings.TrimSpace(*purchase.PoNumber), noteSuffix)
transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
repoTx := rPurchase.NewPurchaseRepository(tx)
pwRepoTx := rProductWarehouse.NewProductWarehouseRepository(tx)
stockLogRepoTx := rStockLogs.NewStockLogRepository(tx)
deltas := make(map[uint]float64)
affected := make(map[uint]struct{})
updates := make([]rPurchase.PurchaseReceivingUpdate, 0, len(prepared))
priceUpdates := make([]rPurchase.PurchasePricingUpdate, 0, len(prepared))
totalQtyDeltas := make(map[uint]float64)
fifoAdds := make([]struct {
itemID uint
pwID uint
qty float64
}, 0, len(prepared))
fifoSubs := make([]struct {
itemID uint
pwID uint
qty float64
}, 0, len(prepared))
logEntries := make([]struct {
itemID uint
pwID uint
delta float64
}, 0, len(prepared))
for _, prep := range prepared {
item := prep.item
@@ -904,16 +937,38 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
newPWID = &pwID
deltaQty := prep.receivedQty - item.TotalQty
switch {
case deltaQty > 0 && newPWID != nil:
fifoAdds = append(fifoAdds, struct {
if newPWID != nil && deltaQty != 0 {
logEntries = append(logEntries, struct {
itemID uint
pwID uint
qty float64
}{itemID: item.Id, pwID: *newPWID, qty: deltaQty})
delta float64
}{itemID: item.Id, pwID: *newPWID, delta: deltaQty})
}
switch {
case deltaQty > 0 && newPWID != nil:
if s.FifoSvc != nil {
fifoAdds = append(fifoAdds, struct {
itemID uint
pwID uint
qty float64
}{itemID: item.Id, pwID: *newPWID, qty: deltaQty})
} else {
deltas[*newPWID] += deltaQty
totalQtyDeltas[item.Id] += deltaQty
}
case deltaQty < 0 && newPWID != nil:
deltas[*newPWID] += deltaQty // negative
affected[*newPWID] = struct{}{}
if s.FifoSvc != nil {
fifoSubs = append(fifoSubs, struct {
itemID uint
pwID uint
qty float64
}{itemID: item.Id, pwID: *newPWID, qty: deltaQty})
affected[*newPWID] = struct{}{}
} else {
deltas[*newPWID] += deltaQty // negative
affected[*newPWID] = struct{}{}
totalQtyDeltas[item.Id] += deltaQty
}
}
dateCopy := prep.receivedDate
@@ -936,7 +991,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
updates = append(updates, update)
if item.Price > 0 && prep.receivedQty >= 0 {
if prep.receivedQty >= 0 {
priceUpdates = append(priceUpdates, rPurchase.PurchasePricingUpdate{
ItemID: item.Id,
Price: item.Price,
@@ -953,16 +1008,25 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
return err
}
if err := pwRepoTx.CleanupEmpty(c.Context(), affected); err != nil {
return err
}
if len(priceUpdates) > 0 {
if err := repoTx.UpdatePricing(c.Context(), purchase.Id, priceUpdates); err != nil {
return err
}
}
if len(totalQtyDeltas) > 0 {
for itemID, delta := range totalQtyDeltas {
if delta == 0 {
continue
}
if err := tx.Model(&entity.PurchaseItem{}).
Where("purchase_id = ? AND id = ?", purchase.Id, itemID).
Update("total_qty", gorm.Expr("COALESCE(total_qty,0) + ?", delta)).Error; err != nil {
return err
}
}
}
// Update due_date based on earliest received date when receiving approved.
if earliestReceived != nil {
due := earliestReceived.AddDate(0, 0, purchase.CreditTerm)
@@ -988,6 +1052,53 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
return err
}
}
for _, adj := range fifoSubs {
if adj.pwID == 0 || adj.qty >= 0 {
continue
}
if err := s.FifoSvc.AdjustStockableQuantity(c.Context(), commonSvc.StockAdjustRequest{
StockableKey: fifo.StockableKeyPurchaseItems,
StockableID: adj.itemID,
ProductWarehouseID: adj.pwID,
Quantity: adj.qty,
Tx: tx,
}); err != nil {
return err
}
}
}
if len(logEntries) > 0 {
logs := make([]*entity.StockLog, 0, len(logEntries))
for _, entry := range logEntries {
if entry.pwID == 0 || entry.delta == 0 {
continue
}
log := &entity.StockLog{
ProductWarehouseId: entry.pwID,
CreatedBy: actorID,
LoggableType: string(utils.StockLogTypePurchase),
LoggableId: purchase.Id,
Notes: receiveNote,
}
if entry.delta > 0 {
log.Increase = entry.delta
} else {
log.Decrease = -entry.delta
}
logs = append(logs, log)
}
if len(logs) > 0 {
if err := stockLogRepoTx.CreateMany(c.Context(), logs, nil); err != nil {
return err
}
}
}
if len(affected) > 0 {
if err := pwRepoTx.CleanupEmpty(c.Context(), affected); err != nil {
return err
}
}
return nil
@@ -1019,6 +1130,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
TransportPerItem: prep.transportPerItem,
ReceivedQty: prep.receivedQty,
ReceivedDate: &date,
VehicleNumber: prep.vehicleNumber,
}
receivingPayloads = append(receivingPayloads, payload)
}
@@ -1414,10 +1526,6 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
qtyCopy := effectiveQty
update.Quantity = &qtyCopy
}
if syncReceiving {
qtyCopy := effectiveQty
update.TotalQty = &qtyCopy
}
updates = append(updates, update)
delete(requestItems, item.Id)