From a390d1d23a5ebec7c7d47b95d945ed2cd8aa7ee9 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 29 Oct 2025 14:19:08 +0700 Subject: [PATCH] FIX[BE]: Fix Delete one on chickin match with BE standard --- .../product_warehouse.repository.go | 29 ++ .../project_chickin_detail.repository.go | 25 ++ .../chickins/services/chickin.service.go | 258 ++++++++---------- .../project_flock_population_repository.go | 20 +- 4 files changed, 194 insertions(+), 138 deletions(-) diff --git a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go index f1f1fa57..0017fe6c 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -17,6 +17,9 @@ type ProductWarehouseRepository interface { ExistsByID(ctx context.Context, id uint) (bool, error) GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error) GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error) + GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) (*entity.ProductWarehouse, error) + WithTxRepo(tx *gorm.DB) ProductWarehouseRepository + DB() *gorm.DB } type ProductWarehouseRepositoryImpl struct { @@ -31,6 +34,15 @@ func NewProductWarehouseRepository(db *gorm.DB) ProductWarehouseRepository { } } +func (r *ProductWarehouseRepositoryImpl) WithTxRepo(tx *gorm.DB) ProductWarehouseRepository { + return &ProductWarehouseRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.ProductWarehouse](tx), + db: tx, + } +} +func (r *ProductWarehouseRepositoryImpl) DB() *gorm.DB { + return r.db +} func (r *ProductWarehouseRepositoryImpl) IsProductExist(ctx context.Context, productId uint) (bool, error) { return repository.Exists[entity.Product](ctx, r.db, productId) } @@ -89,3 +101,20 @@ func (r *ProductWarehouseRepositoryImpl) GetByCategoryCodeAndWarehouseID(ctx con } return productWarehouses, nil } + +func (r *ProductWarehouseRepositoryImpl) GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) (*entity.ProductWarehouse, error) { + var productWarehouse entity.ProductWarehouse + err := r.db.WithContext(ctx). + Table("product_warehouses"). + Select("product_warehouses.*"). + Joins("JOIN products ON products.id = product_warehouses.product_id"). + Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). + Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", categoryCode, warehouseId). + Order("product_warehouses.created_at DESC"). + Limit(1). + First(&productWarehouse).Error + if err != nil { + return nil, err + } + return &productWarehouse, nil +} diff --git a/internal/modules/production/chickins/repositories/project_chickin_detail.repository.go b/internal/modules/production/chickins/repositories/project_chickin_detail.repository.go index 42c267ec..f5be8770 100644 --- a/internal/modules/production/chickins/repositories/project_chickin_detail.repository.go +++ b/internal/modules/production/chickins/repositories/project_chickin_detail.repository.go @@ -1,6 +1,8 @@ package repository import ( + "context" + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gorm.io/gorm" @@ -8,6 +10,10 @@ import ( type ProjectChickinDetailRepository interface { repository.BaseRepository[entity.ProjectChickinDetail] + CreateOne(ctx context.Context, entity *entity.ProjectChickinDetail, modifier func(*gorm.DB) *gorm.DB) error + DeleteMany(ctx context.Context, modifier func(*gorm.DB) *gorm.DB) error + GetByProjectChickinID(ctx context.Context, projectChickinID uint) ([]entity.ProjectChickinDetail, error) + WithTxRepo(tx *gorm.DB) ProjectChickinDetailRepository } type ChickinDetailRepositoryImpl struct { @@ -19,3 +25,22 @@ func NewChickinDetailRepository(db *gorm.DB) ProjectChickinDetailRepository { BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickinDetail](db), } } + +func (r *ChickinDetailRepositoryImpl) WithTxRepo(tx *gorm.DB) ProjectChickinDetailRepository { + return &ChickinDetailRepositoryImpl{BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickinDetail](tx)} +} + +func (r *ChickinDetailRepositoryImpl) DB() *gorm.DB { + return r.BaseRepositoryImpl.DB() +} + +func (r *ChickinDetailRepositoryImpl) GetByProjectChickinID(ctx context.Context, projectChickinID uint) ([]entity.ProjectChickinDetail, error) { + var records []entity.ProjectChickinDetail + if err := r.DB().WithContext(ctx).Where("project_chickin_id = ?", projectChickinID).Find(&records).Error; err != nil { + return nil, err + } + if len(records) == 0 { + return nil, gorm.ErrRecordNotFound + } + return records, nil +} diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index f422666f..83d94ba5 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -77,6 +77,7 @@ func (s chickinService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity } offset := (params.Page - 1) * params.Limit + chickins, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) if params.ProjectFlockKandangId != 0 { @@ -241,143 +242,129 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) } func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { - db := s.Repository.DB() - tx := db.WithContext(c.Context()).Begin() - if tx.Error != nil { - s.Log.Errorf("Failed to begin transaction: %+v", tx.Error) - return tx.Error - } - rollback := func(err error) error { - if rerr := tx.Rollback().Error; rerr != nil { - s.Log.Errorf("Rollback failed: %+v", rerr) + err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + + chickinRepo := repository.NewChickinRepository(dbTransaction) + projectFlockKandangRepo := s.ProjectflockKandangRepo.WithTx(dbTransaction) + productWarehouseRepo := s.ProductWarehouseRepo.WithTxRepo(dbTransaction) + projectFlockPopulationRepo := s.ProjectflockPopulationRepo.WithTx(dbTransaction) + projectChickinDetailRepo := s.ProjectChickinDetailRepo.WithTxRepo(dbTransaction) + warehouseRepoTx := rWarehouse.NewWarehouseRepository(dbTransaction) + + chickin, err := chickinRepo.GetByID(c.Context(), id, nil) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Chickin not found") + } + return err + } + + population, err := projectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), chickin.ProjectFlockKandangId) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Project flock population not found") + } + return err + } + + newReserved := population.ReservedQuantity - chickin.Quantity + if newReserved < 0 { + newReserved = 0 + } + + err = projectFlockPopulationRepo.PatchOne(c.Context(), population.Id, map[string]any{ + "reserved_quantity": newReserved, + }, nil) + if err != nil { + return err + } + + restoreFromDetails := func() (bool, error) { + + details, err := projectChickinDetailRepo.GetByProjectChickinID(c.Context(), chickin.Id) + if err != nil { + return false, err + } + + for _, d := range details { + productWarehouse, err := productWarehouseRepo.GetByID(c.Context(), d.ProductWarehouseId, nil) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + continue + } + return false, err + } + + updatedQuantity := productWarehouse.Quantity + d.Quantity + if err := productWarehouseRepo.PatchOne(c.Context(), productWarehouse.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil { + return false, err + } + } + + if err := projectChickinDetailRepo.DeleteMany(c.Context(), func(db *gorm.DB) *gorm.DB { + return db.Where("project_chickin_id = ?", chickin.Id) + }); err != nil { + return false, err + } + + return true, nil + } + + restored, err := restoreFromDetails() + if err != nil { + return err + } + + if !restored { + projectflockkandang, err := projectFlockKandangRepo.GetByID(c.Context(), population.ProjectFlockKandangId) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Project flock kandang not found") + } + return err + } + + warehouse, err := warehouseRepoTx.GetByKandangID(c.Context(), projectflockkandang.KandangId) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Warehouse not found for kandang") + } + return err + } + + productWarehouse, err := productWarehouseRepo.GetLatestByCategoryCodeAndWarehouseID(c.Context(), "DOC", warehouse.Id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse") + } + return err + } + + updatedQuantity := productWarehouse.Quantity + chickin.Quantity + + if err := productWarehouseRepo.PatchOne(c.Context(), productWarehouse.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil { + return err + } + } + + if err := chickinRepo.DeleteOne(c.Context(), id); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Chickin not found") + } + return err + } + + return nil + }) + + if err != nil { + if ferr, ok := err.(*fiber.Error); ok { + return ferr } return err } - chickinRepoTx := s.Repository.WithTx(tx) - pfkRepoTx := s.ProjectflockKandangRepo.WithTx(tx) - productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(tx) - - chickin, err := chickinRepoTx.GetByID(c.Context(), id, nil) - if errors.Is(err, gorm.ErrRecordNotFound) { - return rollback(fiber.NewError(fiber.StatusNotFound, "Chickin not found")) - } - if err != nil { - s.Log.Errorf("Failed get chickin by id: %+v", err) - return rollback(err) - } - - var population entity.ProjectFlockPopulation - if err := tx.WithContext(c.Context()).Where("project_flock_kandang_id = ?", chickin.ProjectFlockKandangId).First(&population).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return rollback(fiber.NewError(fiber.StatusNotFound, "Project flock population not found")) - } - s.Log.Errorf("Failed to get project flock population: %+v", err) - return rollback(err) - } - - newReserved := population.ReservedQuantity - chickin.Quantity - if newReserved < 0 { - newReserved = 0 - } - if err := tx.WithContext(c.Context()).Model(&entity.ProjectFlockPopulation{}).Where("id = ?", population.Id).Updates(map[string]any{"reserved_quantity": newReserved}).Error; err != nil { - s.Log.Errorf("Failed to update project flock population: %+v", err) - return rollback(err) - } - - restoreFromDetails := func() (bool, error) { - var details []entity.ProjectChickinDetail - if err := tx.WithContext(c.Context()).Where("project_chickin_id = ?", chickin.Id).Find(&details).Error; err != nil { - return false, err - } - if len(details) == 0 { - return false, nil - } - - for _, d := range details { - var pw entity.ProductWarehouse - if err := tx.WithContext(c.Context()).Where("id = ?", d.ProductWarehouseId).First(&pw).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - - continue - } - return false, err - } - - updatedQuantity := pw.Quantity + d.Quantity - if err := productWarehouseRepoTx.PatchOne(c.Context(), pw.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil { - return false, err - } - } - - if err := tx.WithContext(c.Context()).Where("project_chickin_id = ?", chickin.Id).Delete(&entity.ProjectChickinDetail{}).Error; err != nil { - return false, err - } - return true, nil - } - - restored, err := restoreFromDetails() - if err != nil { - s.Log.Errorf("Failed to restore from chickin details: %+v", err) - return rollback(err) - } - - if !restored { - - projectflockkandang, err := pfkRepoTx.GetByID(c.Context(), population.ProjectFlockKandangId) - if err != nil { - s.Log.Errorf("Failed to get projectflock kandang: %+v", err) - return rollback(err) - } - - var warehouse entity.Warehouse - if err := tx.WithContext(c.Context()).Where("kandang_id = ?", projectflockkandang.KandangId).First(&warehouse).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return rollback(fiber.NewError(fiber.StatusNotFound, "Warehouse not found for kandang")) - } - s.Log.Errorf("Failed to get warehouse: %+v", err) - return rollback(err) - } - - var productWarehouse entity.ProductWarehouse - err = tx.WithContext(c.Context()).Table("product_warehouses"). - Select("product_warehouses.*"). - Joins("JOIN products ON products.id = product_warehouses.product_id"). - Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). - Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id). - Order("product_warehouses.created_at DESC"). - First(&productWarehouse).Error - - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return rollback(fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse")) - } - s.Log.Errorf("Failed to get product warehouse: %+v", err) - return rollback(err) - } - - updatedQuantity := productWarehouse.Quantity + chickin.Quantity - if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouse.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil { - s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) - return rollback(err) - } - } - - // delete chickin (single place) - if err := chickinRepoTx.DeleteOne(c.Context(), id); err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return rollback(fiber.NewError(fiber.StatusNotFound, "Chickin not found")) - } - s.Log.Errorf("Failed to delete chickin: %+v", err) - return rollback(err) - } - - if err := tx.Commit().Error; err != nil { - s.Log.Errorf("Failed to commit transaction: %+v", err) - return rollback(err) - } - return nil } @@ -385,11 +372,8 @@ func (s *chickinService) Approve(c *fiber.Ctx, id uint) error { // todo: ini contoh akhir jika sudah approved - chickin, err := s.Repository.GetByID( - c.Context(), - id, - nil, - ) + chickin, err := s.Repository.GetByID(c.Context(), id, nil) + if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "Chickin not found") } diff --git a/internal/modules/production/project_flocks/repositories/project_flock_population_repository.go b/internal/modules/production/project_flocks/repositories/project_flock_population_repository.go index cb4b0d5f..3328d286 100644 --- a/internal/modules/production/project_flocks/repositories/project_flock_population_repository.go +++ b/internal/modules/production/project_flocks/repositories/project_flock_population_repository.go @@ -9,8 +9,16 @@ import ( ) type ProjectFlockPopulationRepository interface { - repository.BaseRepository[entity.ProjectFlockPopulation] + // domain-specific GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (*entity.ProjectFlockPopulation, error) + + // subset of base repository methods used by services + CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error + PatchOne(ctx context.Context, id uint, updates map[string]any, modifier func(*gorm.DB) *gorm.DB) error + + // transaction helpers + WithTx(tx *gorm.DB) ProjectFlockPopulationRepository + DB() *gorm.DB } type projectFlockPopulationRepositoryImpl struct { @@ -23,6 +31,16 @@ func NewProjectFlockPopulationRepository(db *gorm.DB) ProjectFlockPopulationRepo } } +func (r *projectFlockPopulationRepositoryImpl) WithTx(tx *gorm.DB) ProjectFlockPopulationRepository { + return &projectFlockPopulationRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlockPopulation](tx), + } +} + +func (r *projectFlockPopulationRepositoryImpl) DB() *gorm.DB { + return r.BaseRepositoryImpl.DB() +} + func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (*entity.ProjectFlockPopulation, error) { var record entity.ProjectFlockPopulation err := r.DB().WithContext(ctx).