diff --git a/internal/database/migrations/20260216125014_add_depletions_recording_total_used.down.sql b/internal/database/migrations/20260216125014_add_depletions_recording_total_used.down.sql index 9677a9fe..eb1e8d24 100644 --- a/internal/database/migrations/20260216125014_add_depletions_recording_total_used.down.sql +++ b/internal/database/migrations/20260216125014_add_depletions_recording_total_used.down.sql @@ -6,4 +6,7 @@ ALTER TABLE recording_depletions ALTER TABLE recording_depletions DROP COLUMN IF EXISTS total_used_qty; +ALTER TABLE recording_depletions + DROP COLUMN IF EXISTS usage_qty; + COMMIT; diff --git a/internal/database/migrations/20260216125014_add_depletions_recording_total_used.up.sql b/internal/database/migrations/20260216125014_add_depletions_recording_total_used.up.sql index 59b2aa9a..18870585 100644 --- a/internal/database/migrations/20260216125014_add_depletions_recording_total_used.up.sql +++ b/internal/database/migrations/20260216125014_add_depletions_recording_total_used.up.sql @@ -1,7 +1,8 @@ BEGIN; ALTER TABLE recording_depletions - ADD COLUMN IF NOT EXISTS total_used_qty numeric(15, 3) NOT NULL DEFAULT 0; + ADD COLUMN IF NOT EXISTS total_used_qty numeric(15, 3) NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS usage_qty numeric(15, 3) NOT NULL DEFAULT 0; UPDATE recording_depletions SET pending_qty = 0 @@ -11,7 +12,4 @@ ALTER TABLE recording_depletions ADD CONSTRAINT chk_recording_depletions_pending_zero CHECK (pending_qty = 0); -ALTER TABLE recording_depletions - DROP COLUMN IF EXISTS usage_qty; - COMMIT; diff --git a/internal/modules/production/recordings/repositories/recording.repository.go b/internal/modules/production/recordings/repositories/recording.repository.go index c2a55708..0f93d0a7 100644 --- a/internal/modules/production/recordings/repositories/recording.repository.go +++ b/internal/modules/production/recordings/repositories/recording.repository.go @@ -65,9 +65,7 @@ type RecordingRepository interface { GetTotalEggProductionWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeightKg float64, err error) GetAverageTargetMetricsByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, includeTargets bool) (RecordingTargetAverages, error) ResyncProjectFlockPopulationUsage(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error - ValidateFeedProductWarehouses(ctx context.Context, ids []uint) (uint, error) - ValidateEggProductWarehouses(ctx context.Context, ids []uint) (uint, error) - ValidateDepletionProductWarehouses(ctx context.Context, ids []uint) (uint, error) + ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error) } type RecordingRepositoryImpl struct { @@ -856,36 +854,11 @@ func (r *RecordingRepositoryImpl) ResyncProjectFlockPopulationUsage(ctx context. return nil } -func (r *RecordingRepositoryImpl) ValidateFeedProductWarehouses(ctx context.Context, ids []uint) (uint, error) { +func (r *RecordingRepositoryImpl) ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error) { if len(ids) == 0 { return 0, nil } var invalidIDs []uint - feedFlags := []string{"PAKAN", "OVK"} - if err := r.DB().WithContext(ctx). - Table("product_warehouses pw"). - Where("pw.id IN ?", ids). - Where(`NOT EXISTS ( - SELECT 1 FROM flags f - WHERE f.flagable_type = 'products' - AND f.flagable_id = pw.product_id - AND UPPER(f.name) IN ? - )`, feedFlags). - Pluck("pw.id", &invalidIDs).Error; err != nil { - return 0, err - } - if len(invalidIDs) > 0 { - return invalidIDs[0], nil - } - return 0, nil -} - -func (r *RecordingRepositoryImpl) ValidateEggProductWarehouses(ctx context.Context, ids []uint) (uint, error) { - if len(ids) == 0 { - return 0, nil - } - eggFlags := []string{"TELUR-UTUH", "TELUR-PECAH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR"} - var invalidIDs []uint if err := r.DB().WithContext(ctx). Table("product_warehouses pw"). Where("pw.id IN ?", ids). @@ -894,31 +867,7 @@ func (r *RecordingRepositoryImpl) ValidateEggProductWarehouses(ctx context.Conte WHERE f.flagable_type = 'products' AND f.flagable_id = pw.product_id AND UPPER(f.name) IN ? - )`, eggFlags). - Pluck("pw.id", &invalidIDs).Error; err != nil { - return 0, err - } - if len(invalidIDs) > 0 { - return invalidIDs[0], nil - } - return 0, nil -} - -func (r *RecordingRepositoryImpl) ValidateDepletionProductWarehouses(ctx context.Context, ids []uint) (uint, error) { - if len(ids) == 0 { - return 0, nil - } - ayamFlags := []string{"AYAM-AFKIR", "AYAM-CULLING", "AYAM-MATI"} - var invalidIDs []uint - if err := r.DB().WithContext(ctx). - Table("product_warehouses pw"). - Where("pw.id IN ?", ids). - Where(`NOT EXISTS ( - SELECT 1 FROM flags f - WHERE f.flagable_type = 'products' - AND f.flagable_id = pw.product_id - AND UPPER(f.name) IN ? - )`, ayamFlags). + )`, flags). Pluck("pw.id", &invalidIDs).Error; err != nil { return 0, err } diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index 6ef692b9..5fd387bf 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -311,13 +311,16 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent if err := s.ensureProductWarehousesExist(c, req.Stocks, req.Depletions, req.Eggs); err != nil { return nil, err } - if err := s.ensureFeedProductWarehouses(ctx, req.Stocks); err != nil { + feedIDs := recordingutil.CollectWarehouseIDs(req.Stocks, func(st validation.Stock) uint { return st.ProductWarehouseId }) + if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "OVK"}, "feed"); err != nil { return nil, err } - if err := s.ensureDepletionProductWarehouses(ctx, req.Depletions); err != nil { + depletionIDs := recordingutil.CollectWarehouseIDs(req.Depletions, func(d validation.Depletion) uint { return d.ProductWarehouseId }) + if err := s.ensureProductWarehousesByFlags(ctx, depletionIDs, []string{"AYAM-AFKIR", "AYAM-CULLING", "AYAM-MATI"}, "depletion"); err != nil { return nil, err } - if err := s.ensureEggProductWarehouses(ctx, req.Eggs); err != nil { + eggIDs := recordingutil.CollectWarehouseIDs(req.Eggs, func(e validation.Egg) uint { return e.ProductWarehouseId }) + if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR-UTUH", "TELUR-PECAH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR"}, "egg"); err != nil { return nil, err } actorID, err := m.ActorIDFromContext(c) @@ -508,7 +511,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin if err := s.ensureProductWarehousesExist(c, req.Stocks, nil, nil); err != nil { return err } - if err := s.ensureFeedProductWarehouses(ctx, req.Stocks); err != nil { + feedIDs := recordingutil.CollectWarehouseIDs(req.Stocks, func(st validation.Stock) uint { return st.ProductWarehouseId }) + if err := s.ensureProductWarehousesByFlags(ctx, feedIDs, []string{"PAKAN", "OVK"}, "feed"); err != nil { return err } if err := s.syncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks, note, actorID); err != nil { @@ -536,7 +540,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin if err := s.ensureProductWarehousesExist(c, nil, req.Depletions, nil); err != nil { return err } - if err := s.ensureDepletionProductWarehouses(ctx, req.Depletions); err != nil { + depletionIDs := recordingutil.CollectWarehouseIDs(req.Depletions, func(d validation.Depletion) uint { return d.ProductWarehouseId }) + if err := s.ensureProductWarehousesByFlags(ctx, depletionIDs, []string{"AYAM-AFKIR", "AYAM-CULLING", "AYAM-MATI"}, "depletion"); err != nil { return err } if err := s.releaseRecordingDepletions(ctx, tx, existingDepletions, note, actorID); err != nil { @@ -607,7 +612,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin if err := s.ensureProductWarehousesExist(c, nil, nil, req.Eggs); err != nil { return err } - if err := s.ensureEggProductWarehouses(ctx, req.Eggs); err != nil { + eggIDs := recordingutil.CollectWarehouseIDs(req.Eggs, func(e validation.Egg) uint { return e.ProductWarehouseId }) + if err := s.ensureProductWarehousesByFlags(ctx, eggIDs, []string{"TELUR-UTUH", "TELUR-PECAH", "TELUR-PUTIH", "TELUR-RETAK", "TELUR"}, "egg"); err != nil { return err } if err := ensureRecordingEggsUnused(existingEggs); err != nil { @@ -924,43 +930,14 @@ func (s *recordingService) ensureProductWarehousesExist(c *fiber.Ctx, stocks []v return nil } -func (s *recordingService) ensureEggProductWarehouses(ctx context.Context, eggs []validation.Egg) error { - if len(eggs) == 0 { +func (s *recordingService) ensureProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string, label string) error { + if len(ids) == 0 { return nil } - ids := recordingutil.CollectWarehouseIDs(eggs, func(e validation.Egg) uint { return e.ProductWarehouseId }) if err := s.validateWarehouseIDs(ctx, ids, func(ctx context.Context, ids []uint) error { - return recordingutil.EnsureEggProductWarehouses(ctx, s.Repository, ids) - }, "egg"); err != nil { - s.Log.Errorf("Failed to validate egg product warehouses: %+v", err) - return err - } - return nil -} - -func (s *recordingService) ensureDepletionProductWarehouses(ctx context.Context, depletions []validation.Depletion) error { - if len(depletions) == 0 { - return nil - } - ids := recordingutil.CollectWarehouseIDs(depletions, func(d validation.Depletion) uint { return d.ProductWarehouseId }) - if err := s.validateWarehouseIDs(ctx, ids, func(ctx context.Context, ids []uint) error { - return recordingutil.EnsureDepletionProductWarehouses(ctx, s.Repository, ids) - }, "depletion"); err != nil { - s.Log.Errorf("Failed to validate depletion product warehouses: %+v", err) - return err - } - return nil -} - -func (s *recordingService) ensureFeedProductWarehouses(ctx context.Context, stocks []validation.Stock) error { - if len(stocks) == 0 { - return nil - } - ids := recordingutil.CollectWarehouseIDs(stocks, func(st validation.Stock) uint { return st.ProductWarehouseId }) - if err := s.validateWarehouseIDs(ctx, ids, func(ctx context.Context, ids []uint) error { - return recordingutil.EnsureFeedProductWarehouses(ctx, s.Repository, ids) - }, "feed"); err != nil { - s.Log.Errorf("Failed to validate feed product warehouses: %+v", err) + return recordingutil.EnsureProductWarehousesByFlags(ctx, s.Repository, ids, flags, label) + }, label); err != nil { + s.Log.Errorf("Failed to validate %s product warehouses: %+v", label, err) return err } return nil diff --git a/internal/utils/recording/recording_helpers.go b/internal/utils/recording/recording_helpers.go index 5e79ed40..644f6e8d 100644 --- a/internal/utils/recording/recording_helpers.go +++ b/internal/utils/recording/recording_helpers.go @@ -21,9 +21,7 @@ type productWarehouseExistsRepo interface { } type recordingValidationRepo interface { - ValidateFeedProductWarehouses(ctx context.Context, ids []uint) (uint, error) - ValidateEggProductWarehouses(ctx context.Context, ids []uint) (uint, error) - ValidateDepletionProductWarehouses(ctx context.Context, ids []uint) (uint, error) + ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error) } func EnsureProductWarehousesExist(ctx context.Context, repo productWarehouseExistsRepo, ids []uint) error { @@ -42,13 +40,11 @@ func EnsureProductWarehousesExist(ctx context.Context, repo productWarehouseExis return nil } -type pwValidatorFunc func(ctx context.Context, ids []uint) (uint, error) - -func ensureProductWarehouses(ctx context.Context, ids []uint, label string, validator pwValidatorFunc) error { - if len(ids) == 0 || validator == nil { +func EnsureProductWarehousesByFlags(ctx context.Context, repo recordingValidationRepo, ids []uint, flags []string, label string) error { + if repo == nil || len(ids) == 0 { return nil } - invalidID, err := validator(ctx, ids) + invalidID, err := repo.ValidateProductWarehousesByFlags(ctx, ids, flags) if err != nil { return err } @@ -73,25 +69,16 @@ func CollectWarehouseIDs[T any](items []T, getID idGetter[T]) []uint { return ids } -func EnsureFeedProductWarehouses(ctx context.Context, repo recordingValidationRepo, ids []uint) error { - if repo == nil { - return nil - } - return ensureProductWarehouses(ctx, ids, "feed", repo.ValidateFeedProductWarehouses) -} - -func EnsureEggProductWarehouses(ctx context.Context, repo recordingValidationRepo, ids []uint) error { - if repo == nil { - return nil - } - return ensureProductWarehouses(ctx, ids, "egg", repo.ValidateEggProductWarehouses) -} - -func EnsureDepletionProductWarehouses(ctx context.Context, repo recordingValidationRepo, ids []uint) error { - if repo == nil { - return nil - } - return ensureProductWarehouses(ctx, ids, "depletion", repo.ValidateDepletionProductWarehouses) +func EnsureProductWarehousesByFlagsForItems[T any]( + ctx context.Context, + repo recordingValidationRepo, + items []T, + getID idGetter[T], + flags []string, + label string, +) error { + ids := CollectWarehouseIDs(items, getID) + return EnsureProductWarehousesByFlags(ctx, repo, ids, flags, label) } func ComputeDepletionRate(prevRecording *entity.Recording, currentDepletion float64, totalChick int64) float64 {