Merge branch 'fix/BE/fifo-recording-and-closing-perhitungan-sapronak' into 'development'

[FEAT/BE] add coloumn usage_qty and change standart ensure product

See merge request mbugroup/lti-api!329
This commit is contained in:
Hafizh A. Y.
2026-02-18 09:02:33 +00:00
5 changed files with 39 additions and 125 deletions
@@ -6,4 +6,7 @@ ALTER TABLE recording_depletions
ALTER TABLE recording_depletions ALTER TABLE recording_depletions
DROP COLUMN IF EXISTS total_used_qty; DROP COLUMN IF EXISTS total_used_qty;
ALTER TABLE recording_depletions
DROP COLUMN IF EXISTS usage_qty;
COMMIT; COMMIT;
@@ -1,7 +1,8 @@
BEGIN; BEGIN;
ALTER TABLE recording_depletions 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 UPDATE recording_depletions
SET pending_qty = 0 SET pending_qty = 0
@@ -11,7 +12,4 @@ ALTER TABLE recording_depletions
ADD CONSTRAINT chk_recording_depletions_pending_zero ADD CONSTRAINT chk_recording_depletions_pending_zero
CHECK (pending_qty = 0); CHECK (pending_qty = 0);
ALTER TABLE recording_depletions
DROP COLUMN IF EXISTS usage_qty;
COMMIT; COMMIT;
@@ -65,9 +65,7 @@ type RecordingRepository interface {
GetTotalEggProductionWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeightKg float64, err error) GetTotalEggProductionWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeightKg float64, err error)
GetAverageTargetMetricsByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, includeTargets bool) (RecordingTargetAverages, error) GetAverageTargetMetricsByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, includeTargets bool) (RecordingTargetAverages, error)
ResyncProjectFlockPopulationUsage(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error ResyncProjectFlockPopulationUsage(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error
ValidateFeedProductWarehouses(ctx context.Context, ids []uint) (uint, error) ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error)
ValidateEggProductWarehouses(ctx context.Context, ids []uint) (uint, error)
ValidateDepletionProductWarehouses(ctx context.Context, ids []uint) (uint, error)
} }
type RecordingRepositoryImpl struct { type RecordingRepositoryImpl struct {
@@ -856,36 +854,11 @@ func (r *RecordingRepositoryImpl) ResyncProjectFlockPopulationUsage(ctx context.
return nil 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 { if len(ids) == 0 {
return 0, nil return 0, nil
} }
var invalidIDs []uint 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). if err := r.DB().WithContext(ctx).
Table("product_warehouses pw"). Table("product_warehouses pw").
Where("pw.id IN ?", ids). Where("pw.id IN ?", ids).
@@ -894,31 +867,7 @@ func (r *RecordingRepositoryImpl) ValidateEggProductWarehouses(ctx context.Conte
WHERE f.flagable_type = 'products' WHERE f.flagable_type = 'products'
AND f.flagable_id = pw.product_id AND f.flagable_id = pw.product_id
AND UPPER(f.name) IN ? AND UPPER(f.name) IN ?
)`, eggFlags). )`, flags).
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).
Pluck("pw.id", &invalidIDs).Error; err != nil { Pluck("pw.id", &invalidIDs).Error; err != nil {
return 0, err return 0, err
} }
@@ -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 { if err := s.ensureProductWarehousesExist(c, req.Stocks, req.Depletions, req.Eggs); err != nil {
return nil, err 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 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 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 return nil, err
} }
actorID, err := m.ActorIDFromContext(c) 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 { if err := s.ensureProductWarehousesExist(c, req.Stocks, nil, nil); err != nil {
return err 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 return err
} }
if err := s.syncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks, note, actorID); err != nil { 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 { if err := s.ensureProductWarehousesExist(c, nil, req.Depletions, nil); err != nil {
return err 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 return err
} }
if err := s.releaseRecordingDepletions(ctx, tx, existingDepletions, note, actorID); err != nil { 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 { if err := s.ensureProductWarehousesExist(c, nil, nil, req.Eggs); err != nil {
return err 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 return err
} }
if err := ensureRecordingEggsUnused(existingEggs); err != nil { if err := ensureRecordingEggsUnused(existingEggs); err != nil {
@@ -924,43 +930,14 @@ func (s *recordingService) ensureProductWarehousesExist(c *fiber.Ctx, stocks []v
return nil return nil
} }
func (s *recordingService) ensureEggProductWarehouses(ctx context.Context, eggs []validation.Egg) error { func (s *recordingService) ensureProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string, label string) error {
if len(eggs) == 0 { if len(ids) == 0 {
return nil 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 { if err := s.validateWarehouseIDs(ctx, ids, func(ctx context.Context, ids []uint) error {
return recordingutil.EnsureEggProductWarehouses(ctx, s.Repository, ids) return recordingutil.EnsureProductWarehousesByFlags(ctx, s.Repository, ids, flags, label)
}, "egg"); err != nil { }, label); err != nil {
s.Log.Errorf("Failed to validate egg product warehouses: %+v", err) s.Log.Errorf("Failed to validate %s product warehouses: %+v", label, 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 err return err
} }
return nil return nil
+14 -27
View File
@@ -21,9 +21,7 @@ type productWarehouseExistsRepo interface {
} }
type recordingValidationRepo interface { type recordingValidationRepo interface {
ValidateFeedProductWarehouses(ctx context.Context, ids []uint) (uint, error) ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error)
ValidateEggProductWarehouses(ctx context.Context, ids []uint) (uint, error)
ValidateDepletionProductWarehouses(ctx context.Context, ids []uint) (uint, error)
} }
func EnsureProductWarehousesExist(ctx context.Context, repo productWarehouseExistsRepo, ids []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 return nil
} }
type pwValidatorFunc func(ctx context.Context, ids []uint) (uint, error) func EnsureProductWarehousesByFlags(ctx context.Context, repo recordingValidationRepo, ids []uint, flags []string, label string) error {
if repo == nil || len(ids) == 0 {
func ensureProductWarehouses(ctx context.Context, ids []uint, label string, validator pwValidatorFunc) error {
if len(ids) == 0 || validator == nil {
return nil return nil
} }
invalidID, err := validator(ctx, ids) invalidID, err := repo.ValidateProductWarehousesByFlags(ctx, ids, flags)
if err != nil { if err != nil {
return err return err
} }
@@ -73,25 +69,16 @@ func CollectWarehouseIDs[T any](items []T, getID idGetter[T]) []uint {
return ids return ids
} }
func EnsureFeedProductWarehouses(ctx context.Context, repo recordingValidationRepo, ids []uint) error { func EnsureProductWarehousesByFlagsForItems[T any](
if repo == nil { ctx context.Context,
return nil repo recordingValidationRepo,
} items []T,
return ensureProductWarehouses(ctx, ids, "feed", repo.ValidateFeedProductWarehouses) getID idGetter[T],
} flags []string,
label string,
func EnsureEggProductWarehouses(ctx context.Context, repo recordingValidationRepo, ids []uint) error { ) error {
if repo == nil { ids := CollectWarehouseIDs(items, getID)
return nil return EnsureProductWarehousesByFlags(ctx, repo, ids, flags, label)
}
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 ComputeDepletionRate(prevRecording *entity.Recording, currentDepletion float64, totalChick int64) float64 { func ComputeDepletionRate(prevRecording *entity.Recording, currentDepletion float64, totalChick int64) float64 {