Merge branch 'fix/recording-validation' into 'development'

fix: allow editing sold egg weight on recording

See merge request mbugroup/lti-api!499
This commit is contained in:
Adnan Zahir
2026-05-02 17:05:00 +07:00
3 changed files with 65 additions and 1 deletions
@@ -49,6 +49,7 @@ type RecordingRepository interface {
DeleteEggs(tx *gorm.DB, recordingID uint) error
ListEggs(tx *gorm.DB, recordingID uint) ([]entity.RecordingEgg, error)
UpdateEggTotalQty(tx *gorm.DB, eggID uint, totalQty float64) error
UpdateEggWeight(tx *gorm.DB, eggID uint, weight *float64) error
GetRecordingEggByID(ctx context.Context, id uint, modifier func(*gorm.DB) *gorm.DB) (*entity.RecordingEgg, error)
ExistsOnDate(ctx context.Context, projectFlockKandangId uint, recordTime time.Time) (bool, error)
@@ -543,6 +544,12 @@ func (r *RecordingRepositoryImpl) UpdateEggTotalQty(tx *gorm.DB, eggID uint, tot
Update("total_qty", totalQty).Error
}
func (r *RecordingRepositoryImpl) UpdateEggWeight(tx *gorm.DB, eggID uint, weight *float64) error {
return tx.Model(&entity.RecordingEgg{}).
Where("id = ?", eggID).
Update("weight", weight).Error
}
func (r *RecordingRepositoryImpl) GetRecordingEggByID(
ctx context.Context,
id uint,
@@ -799,6 +799,12 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
match := recordingutil.EggTotalsEqual(existingTotals, incomingTotals)
if match {
hasEggChanges = false
} else if recordingutil.EggQtyByWarehouseEqual(existingTotals, incomingTotals) {
// Weight-only change: update weight fields directly without touching FIFO
if err := s.updateEggWeightsOnly(tx, existingEggs, req.Eggs); err != nil {
return err
}
// hasEggChanges stays true so metrics are recomputed
} else {
category := ""
if recordingEntity.ProjectFlockKandang != nil && recordingEntity.ProjectFlockKandang.ProjectFlock.Id != 0 {
@@ -816,7 +822,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
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 {
if err := ensureRecordingEggQtyChangeSafe(existingEggs, req.Eggs); err != nil {
return err
}
if err := s.logRecordingEggUsage(ctx, tx, existingEggs, note, actorID); err != nil {
@@ -3779,6 +3785,44 @@ func ensureRecordingEggsUnused(eggs []entity.RecordingEgg) error {
return nil
}
func ensureRecordingEggQtyChangeSafe(existingEggs []entity.RecordingEgg, reqEggs []validation.Egg) error {
usedByWarehouse := make(map[uint]float64)
for _, egg := range existingEggs {
usedByWarehouse[egg.ProductWarehouseId] += egg.TotalUsed
}
newQtyByWarehouse := make(map[uint]int)
for _, egg := range reqEggs {
newQtyByWarehouse[egg.ProductWarehouseId] += egg.Qty
}
for warehouseID, used := range usedByWarehouse {
if used <= 0 {
continue
}
if float64(newQtyByWarehouse[warehouseID]) < used {
return fiber.NewError(fiber.StatusBadRequest,
fmt.Sprintf("Jumlah telur tidak dapat dikurangi di bawah jumlah yang sudah terjual (%.0f butir)", used))
}
}
return nil
}
func (s *recordingService) updateEggWeightsOnly(tx *gorm.DB, existingEggs []entity.RecordingEgg, reqEggs []validation.Egg) error {
weightByWarehouse := make(map[uint]*float64)
for i := range reqEggs {
weightByWarehouse[reqEggs[i].ProductWarehouseId] = reqEggs[i].Weight
}
for _, egg := range existingEggs {
newWeight, ok := weightByWarehouse[egg.ProductWarehouseId]
if !ok {
continue
}
if err := s.Repository.UpdateEggWeight(tx, egg.Id, newWeight); err != nil {
return err
}
}
return nil
}
func (s *recordingService) recalculateFrom(ctx context.Context, tx *gorm.DB, projectFlockKandangId uint, from time.Time) error {
if tx == nil || projectFlockKandangId == 0 || from.IsZero() {
return nil
@@ -148,6 +148,19 @@ func EggTotalsEqual(a, b map[uint]EggTotals) bool {
return true
}
func EggQtyByWarehouseEqual(a, b map[uint]EggTotals) bool {
if len(a) != len(b) {
return false
}
for key, value := range a {
other, ok := b[key]
if !ok || value.Qty != other.Qty {
return false
}
}
return true
}
func DepletionRouteMapsEqual(a, b map[DepletionRoute]float64) bool {
if len(a) != len(b) {
return false