Compare commits

..

1 Commits

Author SHA1 Message Date
giovanni 4cb37e481b fix submit recording laying did not have chickin date 2026-06-02 10:39:50 +07:00
4 changed files with 30 additions and 76 deletions
@@ -1,17 +0,0 @@
BEGIN;
-- Revert the TELUR / TELUR_GRADE marketing over-sell block. Removing these rows
-- makes resolveOverConsume() fall back to the default allow rule again (the
-- post-20260313061525 behaviour). The reasons are unique to this migration, so
-- the DELETE only touches rows created here.
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;
@@ -1,54 +0,0 @@
BEGIN;
-- Restore the marketing over-sell block for TELUR and TELUR_GRADE only.
--
-- Migration 20260313061525 narrowed the MARKETING_OUT over-sell block to
-- flag_group_code='AYAM' and deactivated the global rule. That left TELUR /
-- TELUR_GRADE with no matching block, so resolveOverConsume() fell back to the
-- default rule 'fifo_v2_default_allow' (allow_overconsume=TRUE) and egg
-- Delivery Orders could over-sell silently into marketing_delivery_products.pending_qty.
--
-- These rules make resolveOverConsume('TELUR'|'TELUR_GRADE','MARKETING_OUT') = FALSE,
-- so an egg DO that exceeds available stock is REJECTED (ErrInsufficientStock)
-- instead of being recorded as pending. Scope is "Telur saja" — AYAM and
-- transfer behaviour are intentionally left unchanged.
--
-- NOTE: run the total_used reconciliation (cmd/reconcile-fifo-total-used) BEFORE
-- applying this in production. Enabling the block while phantom total_used still
-- inflates consumption would reject otherwise-valid egg orders.
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'
);
UPDATE fifo_stock_v2_overconsume_rules
SET allow_overconsume = FALSE, priority = 20, is_active = TRUE
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'
);
UPDATE fifo_stock_v2_overconsume_rules
SET allow_overconsume = FALSE, priority = 20, is_active = TRUE
WHERE lane = 'USABLE'
AND function_code = 'MARKETING_OUT'
AND flag_group_code = 'TELUR_GRADE'
AND reason = 'fifo_v2_exception_marketing_block_telur_grade';
COMMIT;
@@ -964,10 +964,7 @@ func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gor
marketingProduct.ProductWarehouseId,
resolveMarketingAsOf(deliveryProduct.DeliveryDate, deliveryProduct.CreatedAt),
); err != nil {
if errors.Is(err, fifoV2.ErrInsufficientStock) {
return fiber.NewError(fiber.StatusBadRequest, "Stok tidak mencukupi untuk memenuhi permintaan delivery order ini")
}
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Gagal mengalokasikan stok: %v", err))
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock for product warehouse %d: %v", marketingProduct.ProductWarehouseId, err))
}
refreshed, err := deliveryProductRepo.GetByID(ctx, deliveryProduct.Id, nil)
@@ -409,7 +409,14 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
return nil, err
}
if err := s.ChickinSvc.EnsureChickInExists(ctx, pfk.Id); err != nil {
return nil, err
if !isLaying {
return nil, err
}
// LAYING fallback: kandang laying tidak punya project_chickins sendiri —
// populasinya dari laying transfer. Cek apakah ada executed laying transfer.
if fallbackErr := s.ensureLayingTransferExecutedForKandang(ctx, pfk.Id); fallbackErr != nil {
return nil, err
}
}
if s.ProductionStandardSvc != nil {
if err := s.ProductionStandardSvc.EnsureWeekStart(ctx, pfk.ProjectFlock.ProductionStandardId, category); err != nil {
@@ -3998,6 +4005,27 @@ func (s *recordingService) reflowRollbackRecordingInventory(ctx context.Context,
return nil
}
func (s *recordingService) ensureLayingTransferExecutedForKandang(ctx context.Context, pfkID uint) error {
var count int64
err := s.Repository.DB().WithContext(ctx).
Table("laying_transfer_targets ltt").
Joins("JOIN laying_transfers lt ON lt.id = ltt.laying_transfer_id").
Where("ltt.target_project_flock_kandang_id = ?", pfkID).
Where("ltt.deleted_at IS NULL").
Where("lt.deleted_at IS NULL").
Where("lt.executed_at IS NOT NULL").
Where("ltt.total_qty > 0").
Count(&count).Error
if err != nil {
s.Log.Errorf("Failed to check executed laying transfer for pfk_id=%d: %+v", pfkID, err)
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memeriksa transfer laying")
}
if count == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Kandang laying belum memiliki transfer laying yang telah dieksekusi sehingga belum dapat membuat recording")
}
return nil
}
func (s *recordingService) requireFIFO() error {
if s.FifoStockV2Svc == nil {
s.Log.Errorf("FIFO v2 service is not available for recording operations")