diff --git a/internal/database/migrations/20260304063215_fix_fifo_chickin_out.down.sql b/internal/database/migrations/20260304063215_fix_fifo_chickin_out.down.sql new file mode 100644 index 00000000..26e637ff --- /dev/null +++ b/internal/database/migrations/20260304063215_fix_fifo_chickin_out.down.sql @@ -0,0 +1,9 @@ +BEGIN; + +DELETE FROM fifo_stock_v2_route_rules +WHERE flag_group_code = 'AYAM' + AND lane = 'USABLE' + AND function_code = 'CHICKIN_OUT' + AND source_table = 'project_chickins'; + +COMMIT; diff --git a/internal/database/migrations/20260304063215_fix_fifo_chickin_out.up.sql b/internal/database/migrations/20260304063215_fix_fifo_chickin_out.up.sql new file mode 100644 index 00000000..3967da36 --- /dev/null +++ b/internal/database/migrations/20260304063215_fix_fifo_chickin_out.up.sql @@ -0,0 +1,34 @@ +BEGIN; + +INSERT INTO fifo_stock_v2_route_rules( + flag_group_code, + lane, + function_code, + source_table, + source_id_column, + product_warehouse_col, + quantity_col, + used_quantity_col, + pending_quantity_col, + scope_sql, + legacy_type_key, + allow_pending_default, + is_active +) +VALUES + ('AYAM', 'USABLE', 'CHICKIN_OUT', 'project_chickins', 'id', 'product_warehouse_id', 'usage_qty', NULL, 'pending_usage_qty', 'deleted_at IS NULL', 'PROJECT_CHICKIN', TRUE, TRUE) +ON CONFLICT (flag_group_code, lane, function_code, source_table) DO UPDATE +SET + source_id_column = EXCLUDED.source_id_column, + product_warehouse_col = EXCLUDED.product_warehouse_col, + quantity_col = EXCLUDED.quantity_col, + used_quantity_col = EXCLUDED.used_quantity_col, + pending_quantity_col = EXCLUDED.pending_quantity_col, + scope_sql = EXCLUDED.scope_sql, + legacy_type_key = EXCLUDED.legacy_type_key, + allow_pending_default = EXCLUDED.allow_pending_default, + updated_at = NOW(), + -- Keep existing is_active (do not override disable migration if it was intentional). + is_active = fifo_stock_v2_route_rules.is_active; + +COMMIT; diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 27ef3743..85127792 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -28,6 +28,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" "gorm.io/gorm" + "gorm.io/gorm/clause" ) type ChickinService interface { @@ -518,6 +519,21 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit touchedProductWarehouseIDs := make(map[uint]struct{}) for _, approvableID := range approvableIDs { + // Re-check latest approval inside transaction to prevent double-approve races. + var latest entity.Approval + if err := dbTransaction.WithContext(c.Context()). + Table("approvals"). + Where("approvable_type = ? AND approvable_id = ?", utils.ApprovalWorkflowChickin.String(), approvableID). + Order("id DESC"). + Limit(1). + Clauses(clause.Locking{Strength: "UPDATE"}). + Take(&latest).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to recheck approval status") + } + if latest.Id != 0 && latest.StepNumber != uint16(utils.ChickinStepPengajuan) { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("ProjectFlockKandang %d sudah tidak berada di tahap PENGAJUAN", approvableID)) + } + if _, err := approvalSvc.CreateApproval( c.Context(), utils.ApprovalWorkflowChickin,