feat/BE/US-76/US-78/US-79/TASK-112,120,133,121-Recording growing/TASK-187,189,202,190-Recording Laying/TASK-191,192,194,197,203-Grading Telur

This commit is contained in:
ragilap
2025-10-31 19:15:24 +07:00
parent 4f4c6d66d4
commit 6ab6ee8070
4 changed files with 346 additions and 385 deletions
@@ -208,7 +208,6 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
}
createBody := &entity.ProjectFlock{
FlockName: "",
AreaId: req.AreaId,
Category: cat,
FcrId: req.FcrId,
@@ -170,104 +170,90 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
return nil, err
}
tx := s.Repository.DB().WithContext(ctx).Begin()
if tx.Error != nil {
s.Log.Errorf("Failed to start recording transaction: %+v", tx.Error)
return nil, tx.Error
}
defer func() {
if r := recover(); r != nil {
_ = tx.Rollback()
panic(r)
var createdRecording entity.Recording
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
nextDay, err := s.Repository.GenerateNextDay(tx, req.ProjectFlockKandangId)
if err != nil {
s.Log.Errorf("Failed to determine recording day: %+v", err)
return err
}
}()
nextDay, err := s.Repository.GenerateNextDay(tx, req.ProjectFlockKandangId)
if err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to determine recording day: %+v", err)
return nil, err
}
recordTime := time.Now().UTC()
existsToday, err := s.Repository.ExistsOnDate(ctx, req.ProjectFlockKandangId, recordTime)
if err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to verify existing recording on date: %+v", err)
return nil, err
}
if existsToday {
_ = tx.Rollback()
return nil, fiber.NewError(fiber.StatusBadRequest, "Recording for this project flock today already exists")
}
recording := &entity.Recording{
ProjectFlockKandangId: req.ProjectFlockKandangId,
RecordDatetime: recordTime,
Day: &nextDay,
CreatedBy: 1, // TODO: replace with authenticated user
}
if err := s.Repository.CreateOne(ctx, recording, func(*gorm.DB) *gorm.DB { return tx }); err != nil {
_ = tx.Rollback()
if errors.Is(err, gorm.ErrDuplicatedKey) {
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Recording for project flock kandang %d already exists", req.ProjectFlockKandangId))
recordTime := time.Now().UTC()
existsToday, err := s.Repository.ExistsOnDate(ctx, req.ProjectFlockKandangId, recordTime)
if err != nil {
s.Log.Errorf("Failed to verify existing recording on date: %+v", err)
return err
}
s.Log.Errorf("Failed to create recording: %+v", err)
return nil, err
if existsToday {
return fiber.NewError(fiber.StatusBadRequest, "Recording for this project flock today already exists")
}
day := nextDay
createdRecording = entity.Recording{
ProjectFlockKandangId: req.ProjectFlockKandangId,
RecordDatetime: recordTime,
Day: &day,
CreatedBy: 1, // TODO: replace with authenticated user
}
if err := s.Repository.CreateOne(ctx, &createdRecording, func(*gorm.DB) *gorm.DB { return tx }); err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return fiber.NewError(
fiber.StatusBadRequest,
fmt.Sprintf("Recording for project flock kandang %d already exists", req.ProjectFlockKandangId),
)
}
s.Log.Errorf("Failed to create recording: %+v", err)
return err
}
mappedBodyWeights := recordingutil.MapBodyWeights(createdRecording.Id, req.BodyWeights)
if err := s.Repository.CreateBodyWeights(tx, mappedBodyWeights); err != nil {
s.Log.Errorf("Failed to persist body weights: %+v", err)
return err
}
mappedStocks := recordingutil.MapStocks(createdRecording.Id, req.Stocks)
if err := s.Repository.CreateStocks(tx, mappedStocks); err != nil {
s.Log.Errorf("Failed to persist stocks: %+v", err)
return err
}
mappedDepletions := recordingutil.MapDepletions(createdRecording.Id, req.Depletions)
if err := s.Repository.CreateDepletions(tx, mappedDepletions); err != nil {
s.Log.Errorf("Failed to persist depletions: %+v", err)
return err
}
mappedEggs := recordingutil.MapEggs(createdRecording.Id, createdRecording.CreatedBy, req.Eggs)
if err := s.Repository.CreateEggs(tx, mappedEggs); err != nil {
s.Log.Errorf("Failed to persist eggs: %+v", err)
return err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, mappedDepletions, nil, mappedStocks, nil, mappedEggs)); err != nil {
s.Log.Errorf("Failed to adjust product warehouses: %+v", err)
return err
}
if err := s.computeAndUpdateMetrics(ctx, tx, &createdRecording); err != nil {
s.Log.Errorf("Failed to compute recording metrics: %+v", err)
return err
}
action := entity.ApprovalActionCreated
if err := s.createRecordingApproval(ctx, tx, createdRecording.Id, utils.RecordingStepGradingTelur, action, createdRecording.CreatedBy, nil); err != nil {
s.Log.Errorf("Failed to create recording approval for %d: %+v", createdRecording.Id, err)
return err
}
return nil
})
if transactionErr != nil {
return nil, transactionErr
}
mappedBodyWeights := recordingutil.MapBodyWeights(recording.Id, req.BodyWeights)
if err := s.Repository.CreateBodyWeights(tx, mappedBodyWeights); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to persist body weights: %+v", err)
return nil, err
}
mappedStocks := recordingutil.MapStocks(recording.Id, req.Stocks)
if err := s.Repository.CreateStocks(tx, mappedStocks); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to persist stocks: %+v", err)
return nil, err
}
mappedDepletions := recordingutil.MapDepletions(recording.Id, req.Depletions)
if err := s.Repository.CreateDepletions(tx, mappedDepletions); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to persist depletions: %+v", err)
return nil, err
}
mappedEggs := recordingutil.MapEggs(recording.Id, recording.CreatedBy, req.Eggs)
if err := s.Repository.CreateEggs(tx, mappedEggs); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to persist eggs: %+v", err)
return nil, err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, mappedDepletions, nil, mappedStocks, nil, mappedEggs)); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to adjust product warehouses: %+v", err)
return nil, err
}
if err := s.computeAndUpdateMetrics(ctx, tx, recording); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to compute recording metrics: %+v", err)
return nil, err
}
action := entity.ApprovalActionCreated
if err := s.createRecordingApproval(ctx, tx, recording.Id, utils.RecordingStepGradingTelur, action, recording.CreatedBy, nil); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to create recording approval for %d: %+v", recording.Id, err)
return nil, err
}
if err := tx.Commit().Error; err != nil {
s.Log.Errorf("Failed to commit recording transaction: %+v", err)
return nil, err
}
return s.GetOne(c, recording.Id)
return s.GetOne(c, createdRecording.Id)
}
func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Recording, error) {
@@ -277,165 +263,146 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
ctx := c.Context()
tx := s.Repository.DB().WithContext(ctx).Begin()
if tx.Error != nil {
s.Log.Errorf("Failed to start recording transaction: %+v", tx.Error)
return nil, tx.Error
}
defer func() {
if r := recover(); r != nil {
_ = tx.Rollback()
panic(r)
var recordingEntity *entity.Recording
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
recording, err := s.Repository.GetByID(ctx, id, func(db *gorm.DB) *gorm.DB {
return s.Repository.WithRelations(tx)
})
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "Recording not found")
}
s.Log.Errorf("Failed to find recording: %+v", err)
return err
}
}()
recordingEntity = recording
recording, err := s.Repository.GetByID(ctx, id, func(db *gorm.DB) *gorm.DB {
return s.Repository.WithRelations(tx)
var category string
if recordingEntity.ProjectFlockKandang != nil && recordingEntity.ProjectFlockKandang.ProjectFlock.Id != 0 {
category = strings.ToUpper(recordingEntity.ProjectFlockKandang.ProjectFlock.Category)
}
isLaying := category == strings.ToUpper(string(utils.ProjectFlockCategoryLaying))
if req.Eggs != nil {
if !isLaying && len(req.Eggs) > 0 {
return fiber.NewError(fiber.StatusBadRequest, "Egg details permitted only for laying project flocks")
}
if isLaying && len(req.Eggs) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Egg details are required for laying project flocks")
}
}
if req.BodyWeights != nil {
if err := s.Repository.DeleteBodyWeights(tx, recordingEntity.Id); err != nil {
s.Log.Errorf("Failed to clear body weights: %+v", err)
return err
}
if err := s.Repository.CreateBodyWeights(tx, recordingutil.MapBodyWeights(recordingEntity.Id, req.BodyWeights)); err != nil {
s.Log.Errorf("Failed to update body weights: %+v", err)
return err
}
}
if req.Stocks != nil {
if err := s.ensureProductWarehousesExist(c, req.Stocks, nil, nil); err != nil {
return err
}
existingStocks, err := s.Repository.ListStocks(tx, recordingEntity.Id)
if err != nil {
s.Log.Errorf("Failed to list existing stocks: %+v", err)
return err
}
if err := s.Repository.DeleteStocks(tx, recordingEntity.Id); err != nil {
s.Log.Errorf("Failed to clear stocks: %+v", err)
return err
}
mappedStocks := recordingutil.MapStocks(recordingEntity.Id, req.Stocks)
if err := s.Repository.CreateStocks(tx, mappedStocks); err != nil {
s.Log.Errorf("Failed to update stocks: %+v", err)
return err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingStocks, mappedStocks, nil, nil)); err != nil {
s.Log.Errorf("Failed to adjust product warehouses for stocks: %+v", err)
return err
}
}
if req.Eggs != nil && req.Depletions == nil {
if err := s.ensureProductWarehousesExist(c, nil, nil, req.Eggs); err != nil {
return err
}
}
if req.Depletions != nil {
if err := s.ensureProductWarehousesExist(c, nil, req.Depletions, req.Eggs); err != nil {
return err
}
existingDepletions, err := s.Repository.ListDepletions(tx, recordingEntity.Id)
if err != nil {
s.Log.Errorf("Failed to list existing depletions: %+v", err)
return err
}
if err := s.Repository.DeleteDepletions(tx, recordingEntity.Id); err != nil {
s.Log.Errorf("Failed to clear depletions: %+v", err)
return err
}
mappedDepletions := recordingutil.MapDepletions(recordingEntity.Id, req.Depletions)
if err := s.Repository.CreateDepletions(tx, mappedDepletions); err != nil {
s.Log.Errorf("Failed to update depletions: %+v", err)
return err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(existingDepletions, mappedDepletions, nil, nil, nil, nil)); err != nil {
s.Log.Errorf("Failed to adjust product warehouses for depletions: %+v", err)
return err
}
}
if req.Eggs != nil {
existingEggs, err := s.Repository.ListEggs(tx, recordingEntity.Id)
if err != nil {
s.Log.Errorf("Failed to list existing eggs: %+v", err)
return err
}
if err := s.Repository.DeleteEggs(tx, recordingEntity.Id); err != nil {
s.Log.Errorf("Failed to clear eggs: %+v", err)
return err
}
mappedEggs := recordingutil.MapEggs(recordingEntity.Id, recordingEntity.CreatedBy, req.Eggs)
if err := s.Repository.CreateEggs(tx, mappedEggs); err != nil {
s.Log.Errorf("Failed to update eggs: %+v", err)
return err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, nil, nil, existingEggs, mappedEggs)); err != nil {
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
return err
}
}
if err := s.computeAndUpdateMetrics(ctx, tx, recordingEntity); err != nil {
s.Log.Errorf("Failed to recompute recording metrics: %+v", err)
return err
}
action := entity.ApprovalActionUpdated
if err := s.createRecordingApproval(ctx, tx, recordingEntity.Id, utils.RecordingStepPengajuan, action, recordingEntity.CreatedBy, nil); err != nil {
s.Log.Errorf("Failed to create approval after recording update %d: %+v", recordingEntity.Id, err)
return err
}
return nil
})
if err != nil {
_ = tx.Rollback()
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Recording not found")
}
s.Log.Errorf("Failed to find recording: %+v", err)
return nil, err
}
recordingEntity := recording
var category string
if recordingEntity.ProjectFlockKandang != nil && recordingEntity.ProjectFlockKandang.ProjectFlock.Id != 0 {
category = strings.ToUpper(recordingEntity.ProjectFlockKandang.ProjectFlock.Category)
}
isLaying := category == strings.ToUpper(string(utils.ProjectFlockCategoryLaying))
if req.Eggs != nil {
if !isLaying && len(req.Eggs) > 0 {
_ = tx.Rollback()
return nil, fiber.NewError(fiber.StatusBadRequest, "Egg details permitted only for laying project flocks")
}
if isLaying && len(req.Eggs) == 0 {
_ = tx.Rollback()
return nil, fiber.NewError(fiber.StatusBadRequest, "Egg details are required for laying project flocks")
}
}
if req.BodyWeights != nil {
if err := s.Repository.DeleteBodyWeights(tx, recordingEntity.Id); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to clear body weights: %+v", err)
return nil, err
}
if err := s.Repository.CreateBodyWeights(tx, recordingutil.MapBodyWeights(recordingEntity.Id, req.BodyWeights)); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to update body weights: %+v", err)
return nil, err
}
}
if req.Stocks != nil {
if err := s.ensureProductWarehousesExist(c, req.Stocks, nil, nil); err != nil {
_ = tx.Rollback()
return nil, err
}
existingStocks, err := s.Repository.ListStocks(tx, recordingEntity.Id)
if err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to list existing stocks: %+v", err)
return nil, err
}
if err := s.Repository.DeleteStocks(tx, recordingEntity.Id); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to clear stocks: %+v", err)
return nil, err
}
mappedStocks := recordingutil.MapStocks(recordingEntity.Id, req.Stocks)
if err := s.Repository.CreateStocks(tx, mappedStocks); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to update stocks: %+v", err)
return nil, err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingStocks, mappedStocks, nil, nil)); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to adjust product warehouses for stocks: %+v", err)
return nil, err
}
}
if req.Eggs != nil && req.Depletions == nil {
if err := s.ensureProductWarehousesExist(c, nil, nil, req.Eggs); err != nil {
_ = tx.Rollback()
return nil, err
}
}
var existingDepletions []entity.RecordingDepletion
if req.Depletions != nil {
if err := s.ensureProductWarehousesExist(c, nil, req.Depletions, req.Eggs); err != nil {
_ = tx.Rollback()
return nil, err
}
var err error
existingDepletions, err = s.Repository.ListDepletions(tx, recordingEntity.Id)
if err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to list existing depletions: %+v", err)
return nil, err
}
if err := s.Repository.DeleteDepletions(tx, recordingEntity.Id); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to clear depletions: %+v", err)
return nil, err
}
mappedDepletions := recordingutil.MapDepletions(recordingEntity.Id, req.Depletions)
if err := s.Repository.CreateDepletions(tx, mappedDepletions); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to update depletions: %+v", err)
return nil, err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(existingDepletions, mappedDepletions, nil, nil, nil, nil)); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to adjust product warehouses for depletions: %+v", err)
return nil, err
}
}
if req.Eggs != nil {
existingEggs, err := s.Repository.ListEggs(tx, recordingEntity.Id)
if err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to list existing eggs: %+v", err)
return nil, err
}
if err := s.Repository.DeleteEggs(tx, recordingEntity.Id); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to clear eggs: %+v", err)
return nil, err
}
mappedEggs := recordingutil.MapEggs(recordingEntity.Id, recordingEntity.CreatedBy, req.Eggs)
if err := s.Repository.CreateEggs(tx, mappedEggs); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to update eggs: %+v", err)
return nil, err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, nil, nil, existingEggs, mappedEggs)); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
return nil, err
}
}
if err := s.computeAndUpdateMetrics(ctx, tx, recordingEntity); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to recompute recording metrics: %+v", err)
return nil, err
}
action := entity.ApprovalActionUpdated
if err := s.createRecordingApproval(ctx, tx, recordingEntity.Id, utils.RecordingStepPengajuan, action, recordingEntity.CreatedBy, nil); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to create approval after recording update %d: %+v", recordingEntity.Id, err)
return nil, err
}
if err := tx.Commit().Error; err != nil {
s.Log.Errorf("Failed to commit recording transaction: %+v", err)
return nil, err
if transactionErr != nil {
return nil, transactionErr
}
return s.GetOne(c, id)
@@ -446,107 +413,102 @@ func (s *recordingService) SubmitGrading(c *fiber.Ctx, req *validation.SubmitGra
return nil, err
}
ctx := c.Context()
tx := s.Repository.DB().WithContext(ctx).Begin()
if tx.Error != nil {
s.Log.Errorf("Failed to start grading transaction: %+v", tx.Error)
return nil, tx.Error
if len(req.EggsGrading) == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "eggs_grading must contain at least one item")
}
defer func() {
if r := recover(); r != nil {
_ = tx.Rollback()
panic(r)
recordingEggID := req.EggsGrading[0].RecordingEggId
for _, grading := range req.EggsGrading[1:] {
if grading.RecordingEggId != recordingEggID {
return nil, fiber.NewError(fiber.StatusBadRequest, "semua grading harus untuk recording egg yang sama")
}
}()
recordingEgg, err := s.Repository.GetRecordingEggByID(ctx, req.RecordingEggId, func(db *gorm.DB) *gorm.DB {
return tx
})
if errors.Is(err, gorm.ErrRecordNotFound) {
_ = tx.Rollback()
return nil, fiber.NewError(fiber.StatusNotFound, "Recording egg not found")
}
if err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to get recording egg %d: %+v", req.RecordingEggId, err)
return nil, err
}
var category string
if recordingEgg.Recording.ProjectFlockKandang != nil && recordingEgg.Recording.ProjectFlockKandang.ProjectFlock.Id != 0 {
category = strings.ToUpper(recordingEgg.Recording.ProjectFlockKandang.ProjectFlock.Category)
}
if category != strings.ToUpper(string(utils.ProjectFlockCategoryLaying)) {
_ = tx.Rollback()
return nil, fiber.NewError(fiber.StatusBadRequest, "Grading eggs hanya diperbolehkan pada project flock dengan kategori laying")
}
ctx := c.Context()
var recordingID uint
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
recordingEgg, err := s.Repository.GetRecordingEggByID(ctx, recordingEggID, func(db *gorm.DB) *gorm.DB {
return tx
})
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "Recording egg not found")
}
if err != nil {
s.Log.Errorf("Failed to get recording egg %d: %+v", recordingEggID, err)
return err
}
totalGradingQty := 0.0
for _, grading := range req.EggsGrading {
totalGradingQty += grading.Qty
}
var category string
if recordingEgg.Recording.ProjectFlockKandang != nil && recordingEgg.Recording.ProjectFlockKandang.ProjectFlock.Id != 0 {
category = strings.ToUpper(recordingEgg.Recording.ProjectFlockKandang.ProjectFlock.Category)
}
if category != strings.ToUpper(string(utils.ProjectFlockCategoryLaying)) {
return fiber.NewError(fiber.StatusBadRequest, "Grading eggs hanya diperbolehkan pada project flock dengan kategori laying")
}
availableRecorded := float64(recordingEgg.Qty)
if totalGradingQty > availableRecorded {
_ = tx.Rollback()
return nil, fiber.NewError(
fiber.StatusBadRequest,
fmt.Sprintf("Total grading (%.2f) melebihi jumlah telur tercatat (%.2f)", totalGradingQty, availableRecorded),
)
}
totalGradingQty := 0.0
for _, grading := range req.EggsGrading {
totalGradingQty += grading.Qty
}
if recordingEgg.ProductWarehouse.Id != 0 {
availableWarehouse := recordingEgg.ProductWarehouse.Quantity
if totalGradingQty > availableWarehouse {
_ = tx.Rollback()
return nil, fiber.NewError(
availableRecorded := float64(recordingEgg.Qty)
if totalGradingQty > availableRecorded {
return fiber.NewError(
fiber.StatusBadRequest,
fmt.Sprintf("Total grading (%.2f) melebihi stok telur baik (%.2f)", totalGradingQty, availableWarehouse),
fmt.Sprintf("Total grading (%.2f) melebihi jumlah telur tercatat (%.2f)", totalGradingQty, availableRecorded),
)
}
}
if err := s.Repository.DeleteGradingEggs(tx, recordingEgg.Id); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to clear grading eggs for recording egg %d: %+v", recordingEgg.Id, err)
return nil, err
}
gradings := make([]entity.GradingEgg, 0, len(req.EggsGrading))
createdBy := recordingEgg.CreatedBy
if createdBy == 0 {
createdBy = recordingEgg.Recording.CreatedBy
}
for _, item := range req.EggsGrading {
gradings = append(gradings, entity.GradingEgg{
RecordingEggId: recordingEgg.Id,
Grade: strings.TrimSpace(item.Grade),
Qty: item.Qty,
CreatedBy: createdBy,
})
}
if len(gradings) > 0 {
if err := s.Repository.CreateGradingEggs(tx, gradings); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to persist grading eggs for recording egg %d: %+v", recordingEgg.Id, err)
return nil, err
if recordingEgg.ProductWarehouse.Id != 0 {
availableWarehouse := recordingEgg.ProductWarehouse.Quantity
if totalGradingQty > availableWarehouse {
return fiber.NewError(
fiber.StatusBadRequest,
fmt.Sprintf("Total grading (%.2f) melebihi stok telur baik (%.2f)", totalGradingQty, availableWarehouse),
)
}
}
if err := s.Repository.DeleteGradingEggs(tx, recordingEgg.Id); err != nil {
s.Log.Errorf("Failed to clear grading eggs for recording egg %d: %+v", recordingEgg.Id, err)
return err
}
gradings := make([]entity.GradingEgg, 0, len(req.EggsGrading))
createdBy := recordingEgg.CreatedBy
if createdBy == 0 {
createdBy = recordingEgg.Recording.CreatedBy
}
for _, item := range req.EggsGrading {
gradings = append(gradings, entity.GradingEgg{
RecordingEggId: recordingEgg.Id,
Grade: strings.TrimSpace(item.Grade),
Qty: item.Qty,
CreatedBy: createdBy,
})
}
if len(gradings) > 0 {
if err := s.Repository.CreateGradingEggs(tx, gradings); err != nil {
s.Log.Errorf("Failed to persist grading eggs for recording egg %d: %+v", recordingEgg.Id, err)
return err
}
}
action := entity.ApprovalActionUpdated
if err := s.createRecordingApproval(ctx, tx, recordingEgg.RecordingId, utils.RecordingStepPengajuan, action, createdBy, nil); err != nil {
s.Log.Errorf("Failed to create approval after grading for recording %d: %+v", recordingEgg.RecordingId, err)
return err
}
recordingID = recordingEgg.RecordingId
return nil
})
if transactionErr != nil {
return nil, transactionErr
}
action := entity.ApprovalActionUpdated
if err := s.createRecordingApproval(ctx, tx, recordingEgg.RecordingId, utils.RecordingStepPengajuan, action, createdBy, nil); err != nil {
_ = tx.Rollback()
s.Log.Errorf("Failed to create approval after grading for recording %d: %+v", recordingEgg.RecordingId, err)
return nil, err
}
if err := tx.Commit().Error; err != nil {
s.Log.Errorf("Failed to commit grading transaction: %+v", err)
return nil, err
}
return s.GetOne(c, recordingEgg.RecordingId)
return s.GetOne(c, recordingID)
}
func (s recordingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.Recording, error) {
@@ -629,49 +591,39 @@ func (s recordingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]ent
func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
ctx := c.Context()
tx := s.Repository.DB().WithContext(ctx).Begin()
if tx.Error != nil {
return tx.Error
}
oldDepletions, err := s.Repository.ListDepletions(tx, id)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
_ = tx.Rollback()
s.Log.Errorf("Failed to list depletions before delete: %+v", err)
return err
}
oldEggs, err := s.Repository.ListEggs(tx, id)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
_ = tx.Rollback()
s.Log.Errorf("Failed to list eggs before delete: %+v", err)
return err
}
oldStocks, err := s.Repository.ListStocks(tx, id)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
_ = tx.Rollback()
s.Log.Errorf("Failed to list stocks before delete: %+v", err)
return err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(oldDepletions, nil, oldStocks, nil, oldEggs, nil)); err != nil {
_ = tx.Rollback()
return err
}
if err := s.Repository.WithTx(tx).DeleteOne(ctx, id); err != nil {
_ = tx.Rollback()
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "Recording not found")
return s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
oldDepletions, err := s.Repository.ListDepletions(tx, id)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Failed to list depletions before delete: %+v", err)
return err
}
s.Log.Errorf("Failed to delete recording: %+v", err)
return err
}
if err := tx.Commit().Error; err != nil {
s.Log.Errorf("Failed to commit delete recording transaction: %+v", err)
return err
}
return nil
oldEggs, err := s.Repository.ListEggs(tx, id)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Failed to list eggs before delete: %+v", err)
return err
}
oldStocks, err := s.Repository.ListStocks(tx, id)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Failed to list stocks before delete: %+v", err)
return err
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(oldDepletions, nil, oldStocks, nil, oldEggs, nil)); err != nil {
return err
}
if err := s.Repository.WithTx(tx).DeleteOne(ctx, id); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "Recording not found")
}
s.Log.Errorf("Failed to delete recording: %+v", err)
return err
}
return nil
})
}
// === Persistence Helpers ===
@@ -9,7 +9,7 @@ type (
Stock struct {
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"`
UsageAmount *float64 `json:"usage_amount,omitempty" validate:"omitempty,gte=0"`
Qty *float64 `json:"qty,omitempty" validate:"required_without=UsageAmount,gte=0"`
PendingQty *float64 `json:"pending_qty,omitempty" validate:"omitempty,gte=0"`
}
@@ -46,13 +46,13 @@ type Query struct {
}
type EggGrading struct {
Grade string `json:"grade" validate:"required"`
Qty float64 `json:"qty" validate:"required,gte=0"`
RecordingEggId uint `json:"recording_egg_id" validate:"required,number,min=1"`
Grade string `json:"grade" validate:"required"`
Qty float64 `json:"qty" validate:"required,gte=0"`
}
type SubmitGrading struct {
RecordingEggId uint `json:"recording_egg_id" validate:"required,number,min=1"`
EggsGrading []EggGrading `json:"eggs_grading" validate:"required,dive"`
EggsGrading []EggGrading `json:"eggs_grading" validate:"required,dive"`
}
type Approve struct {
+12 -2
View File
@@ -35,11 +35,21 @@ func MapStocks(recordingID uint, items []validation.Stock) []entity.RecordingSto
result := make([]entity.RecordingStock, 0, len(items))
for _, item := range items {
var usageAmount float64
if item.Qty != nil {
usageAmount = *item.Qty
}
usagePtr := new(float64)
*usagePtr = usageAmount
pending := item.PendingQty
if pending == nil {
pending = new(float64)
}
result = append(result, entity.RecordingStock{
RecordingId: recordingID,
ProductWarehouseId: item.ProductWarehouseId,
UsageQty: item.UsageAmount,
PendingQty: item.PendingQty,
UsageQty: usagePtr,
PendingQty: pending,
})
}
return result