mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-06-09 15:07:49 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33bae94d43 | |||
| 6d2b6a0cb8 |
@@ -195,9 +195,12 @@ func (s *fifoStockV2Service) allocateInternal(ctx context.Context, tx *gorm.DB,
|
|||||||
|
|
||||||
if remaining > 0 {
|
if remaining > 0 {
|
||||||
if !allowOverConsume {
|
if !allowOverConsume {
|
||||||
return nil, fmt.Errorf("%w: requested %.3f, allocated %.3f", ErrInsufficientStock, req.NeedQty, result.AllocatedQty)
|
s.logger.Warnf("FIFO v2: clearing historical pending (%.3f) for %s/%d at PW=%d — over-consume is blocked by rule",
|
||||||
|
remaining, req.Usable.LegacyTypeKey, req.Usable.ID, req.ProductWarehouseID)
|
||||||
|
result.PendingQty = 0
|
||||||
|
} else {
|
||||||
|
result.PendingQty = remaining
|
||||||
}
|
}
|
||||||
result.PendingQty = remaining
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.applyUsableDeltas(tx, *usableRule, req.Usable.ID, result.AllocatedQty, result.PendingQty); err != nil {
|
if err := s.applyUsableDeltas(tx, *usableRule, req.Usable.ID, result.AllocatedQty, result.PendingQty); err != nil {
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Rollback: re-insert TELUR/TELUR_GRADE block rules yang dihapus oleh migration ini.
|
||||||
|
|
||||||
|
INSERT INTO fifo_stock_v2_overconsume_rules(flag_group_code, function_code, lane, allow_overconsume, priority, reason, is_active)
|
||||||
|
SELECT 'TELUR', 'MARKETING_OUT', 'USABLE', FALSE, 20, 'fifo_v2_exception_marketing_block_telur', TRUE
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM fifo_stock_v2_overconsume_rules
|
||||||
|
WHERE lane = 'USABLE'
|
||||||
|
AND function_code = 'MARKETING_OUT'
|
||||||
|
AND flag_group_code = 'TELUR'
|
||||||
|
AND reason = 'fifo_v2_exception_marketing_block_telur'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO fifo_stock_v2_overconsume_rules(flag_group_code, function_code, lane, allow_overconsume, priority, reason, is_active)
|
||||||
|
SELECT 'TELUR_GRADE', 'MARKETING_OUT', 'USABLE', FALSE, 20, 'fifo_v2_exception_marketing_block_telur_grade', TRUE
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM fifo_stock_v2_overconsume_rules
|
||||||
|
WHERE lane = 'USABLE'
|
||||||
|
AND function_code = 'MARKETING_OUT'
|
||||||
|
AND flag_group_code = 'TELUR_GRADE'
|
||||||
|
AND reason = 'fifo_v2_exception_marketing_block_telur_grade'
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Revert rules yang ditambahkan oleh migration 20260603031237_block_marketing_overconsume_telur.
|
||||||
|
-- TELUR/TELUR_GRADE kembali fallback ke default allow rule (allow_overconsume=TRUE)
|
||||||
|
-- karena validasi stok sekarang ditangani di service layer (code validation) bukan lewat
|
||||||
|
-- config overconsume FIFO v2.
|
||||||
|
|
||||||
|
DELETE FROM fifo_stock_v2_overconsume_rules
|
||||||
|
WHERE lane = 'USABLE'
|
||||||
|
AND function_code = 'MARKETING_OUT'
|
||||||
|
AND flag_group_code IN ('TELUR', 'TELUR_GRADE')
|
||||||
|
AND reason IN (
|
||||||
|
'fifo_v2_exception_marketing_block_telur',
|
||||||
|
'fifo_v2_exception_marketing_block_telur_grade'
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -972,6 +972,19 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
|
|||||||
if err := deliveryProductRepo.UpdateOne(ctx, deliveryProduct.Id, deliveryProduct, nil); err != nil {
|
if err := deliveryProductRepo.UpdateOne(ctx, deliveryProduct.Id, deliveryProduct, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
||||||
}
|
}
|
||||||
|
if requestedQty > 0 {
|
||||||
|
available, err := s.checkAvailableStockQty(ctx, tx, marketingProduct.ProductWarehouseId)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memeriksa ketersediaan stok")
|
||||||
|
}
|
||||||
|
if requestedQty > available {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf(
|
||||||
|
"Stok tidak mencukupi: dibutuhkan %g, tersedia %g",
|
||||||
|
requestedQty, available,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := reflowMarketingScope(
|
if err := reflowMarketingScope(
|
||||||
ctx,
|
ctx,
|
||||||
s.FifoStockV2Svc,
|
s.FifoStockV2Svc,
|
||||||
@@ -1505,3 +1518,28 @@ func uniqueUintIDs(ids []uint) []uint {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkAvailableStockQty returns the net available qty for a product warehouse:
|
||||||
|
// gross qty (product_warehouses.qty) minus the sum of active CONSUME allocations
|
||||||
|
// in stock_allocations. This gives the true available stock accounting for all
|
||||||
|
// other delivery orders that have already consumed from the same warehouse.
|
||||||
|
func (s deliveryOrdersService) checkAvailableStockQty(ctx context.Context, tx *gorm.DB, productWarehouseId uint) (float64, error) {
|
||||||
|
var pw entity.ProductWarehouse
|
||||||
|
if err := tx.WithContext(ctx).Select("qty").First(&pw, productWarehouseId).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var usedQty float64
|
||||||
|
if err := tx.WithContext(ctx).Raw(`
|
||||||
|
SELECT COALESCE(SUM(qty), 0)
|
||||||
|
FROM stock_allocations
|
||||||
|
WHERE stockable_type = 'product_warehouses'
|
||||||
|
AND stockable_id = ?
|
||||||
|
AND status = 'ACTIVE'
|
||||||
|
AND allocation_purpose = 'CONSUME'
|
||||||
|
`, productWarehouseId).Scan(&usedQty).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pw.Quantity - usedQty, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user