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
DROP COLUMN IF EXISTS total_used_qty;
ALTER TABLE recording_depletions
DROP COLUMN IF EXISTS usage_qty;
COMMIT;
@@ -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;
@@ -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
}
@@ -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
+14 -27
View File
@@ -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 {