From 6dd45c32890a383c2ae106855f8669eaa32b41ec Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 22 Oct 2025 14:56:32 +0700 Subject: [PATCH] fix[BE]: adjust chickin if product warehouse have more than one DOC product --- ...29_create_project_chickin_details.down.sql | 1 + ...4829_create_project_chickin_details.up.sql | 45 ++++++ internal/entities/ProjectChickinDetail.go | 22 +++ .../modules/production/chickins/module.go | 3 +- .../project_chickin_detail.repository.go | 21 +++ .../chickins/services/chickin.service.go | 143 ++++++++++-------- .../projectflock_kandang.repository.go | 1 - 7 files changed, 170 insertions(+), 66 deletions(-) create mode 100644 internal/database/migrations/20251022024829_create_project_chickin_details.down.sql create mode 100644 internal/database/migrations/20251022024829_create_project_chickin_details.up.sql create mode 100644 internal/entities/ProjectChickinDetail.go create mode 100644 internal/modules/production/chickins/repositories/project_chickin_detail.repository.go diff --git a/internal/database/migrations/20251022024829_create_project_chickin_details.down.sql b/internal/database/migrations/20251022024829_create_project_chickin_details.down.sql new file mode 100644 index 00000000..521e57bf --- /dev/null +++ b/internal/database/migrations/20251022024829_create_project_chickin_details.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS project_chickin_details; \ No newline at end of file diff --git a/internal/database/migrations/20251022024829_create_project_chickin_details.up.sql b/internal/database/migrations/20251022024829_create_project_chickin_details.up.sql new file mode 100644 index 00000000..349086ba --- /dev/null +++ b/internal/database/migrations/20251022024829_create_project_chickin_details.up.sql @@ -0,0 +1,45 @@ +CREATE TABLE IF NOT EXISTS project_chickin_details ( + id BIGSERIAL PRIMARY KEY, + project_chickin_id BIGINT NOT NULL, + product_warehouse_id BIGINT NOT NULL, + quantity NUMERIC(15, 3) NOT NULL, + created_by BIGINT NOT NULL, + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now(), + deleted_at TIMESTAMPTZ +); + +-- FOREIGN KEYS (dijalankan setelah semua tabel parent ada) +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_chickins') THEN + ALTER TABLE project_chickin_details + ADD CONSTRAINT fk_project_chickin_id + FOREIGN KEY (project_chickin_id) + REFERENCES project_chickins(id) + ON DELETE CASCADE ON UPDATE CASCADE; + END IF; + + IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN + ALTER TABLE project_chickin_details + ADD CONSTRAINT fk_product_warehouse_id + FOREIGN KEY (product_warehouse_id) + REFERENCES product_warehouses(id) + ON DELETE RESTRICT ON UPDATE CASCADE; + END IF; + + IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN + ALTER TABLE project_chickin_details + ADD CONSTRAINT fk_created_by + FOREIGN KEY (created_by) + REFERENCES users(id) + ON DELETE RESTRICT ON UPDATE CASCADE; + END IF; +END $$; + +-- INDEXES +CREATE INDEX IF NOT EXISTS idx_project_chickin_details_project_chickin_id ON project_chickin_details (project_chickin_id); + +CREATE INDEX IF NOT EXISTS idx_project_chickin_details_product_warehouse_id ON project_chickin_details (product_warehouse_id); + +CREATE INDEX IF NOT EXISTS idx_project_chickin_details_created_by ON project_chickin_details (created_by); \ No newline at end of file diff --git a/internal/entities/ProjectChickinDetail.go b/internal/entities/ProjectChickinDetail.go new file mode 100644 index 00000000..c11cb9da --- /dev/null +++ b/internal/entities/ProjectChickinDetail.go @@ -0,0 +1,22 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type ProjectChickinDetail struct { + Id uint `gorm:"primaryKey"` + ProjectChickinId uint `gorm:"column:project_chickin_id;not null"` + ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"` + Quantity float64 `gorm:"type:numeric(15,3);not null"` + CreatedBy uint `gorm:"column:created_by;not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + ProjectChickin *ProjectChickin `gorm:"foreignKey:ProjectChickinId;references:Id"` + ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"` + CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"` +} diff --git a/internal/modules/production/chickins/module.go b/internal/modules/production/chickins/module.go index 3ebf93c7..f1e0baea 100644 --- a/internal/modules/production/chickins/module.go +++ b/internal/modules/production/chickins/module.go @@ -21,6 +21,7 @@ type ChickinModule struct{} func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { chickinRepo := rChickin.NewChickinRepository(db) + chickinDetailRepo := rChickin.NewChickinDetailRepository(db) kandangRepo := rKandang.NewKandangRepository(db) warehouseRepo := rWarehouse.NewWarehouseRepository(db) @@ -31,7 +32,7 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * userRepo := rUser.NewUserRepository(db) - chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, projectflockkandangrepo, projectflockpopulationrepo, validate) + chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, projectflockkandangrepo, projectflockpopulationrepo, chickinDetailRepo, validate) userService := sUser.NewUserService(userRepo, validate) ChickinRoutes(router, userService, chickinService) diff --git a/internal/modules/production/chickins/repositories/project_chickin_detail.repository.go b/internal/modules/production/chickins/repositories/project_chickin_detail.repository.go new file mode 100644 index 00000000..42c267ec --- /dev/null +++ b/internal/modules/production/chickins/repositories/project_chickin_detail.repository.go @@ -0,0 +1,21 @@ +package repository + +import ( + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type ProjectChickinDetailRepository interface { + repository.BaseRepository[entity.ProjectChickinDetail] +} + +type ChickinDetailRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.ProjectChickinDetail] +} + +func NewChickinDetailRepository(db *gorm.DB) ProjectChickinDetailRepository { + return &ChickinDetailRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickinDetail](db), + } +} diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 751e633c..16e37911 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -8,8 +8,6 @@ import ( KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" - rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" - validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/validations" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -37,11 +35,12 @@ type chickinService struct { WarehouseRepo rWarehouse.WarehouseRepository ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository ProjectFlockRepo rProjectFlock.ProjectflockRepository - ProjectflockKandangRepo rProjectFlockKandang.ProjectFlockKandangRepository + ProjectflockKandangRepo rProjectFlock.ProjectFlockKandangRepository ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository + ProjectChickinDetailRepo repository.ProjectChickinDetailRepository } -func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, projectflockkandangRepo rProjectFlockKandang.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, validate *validator.Validate) ChickinService { +func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, projectflockkandangRepo rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, validate *validator.Validate) ChickinService { return &chickinService{ Log: utils.Log, Validate: validate, @@ -52,6 +51,7 @@ func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo Kan ProjectFlockRepo: projectFlockRepo, ProjectflockKandangRepo: projectflockkandangRepo, ProjectflockPopulationRepo: projectflockpopulationRepo, + ProjectChickinDetailRepo: projectChickinDetailRepo, } } @@ -121,23 +121,12 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } - projectFlock, err := s.ProjectFlockRepo.GetByID( - c.Context(), - projectflockkandang.ProjectFlockId, - func(db *gorm.DB) *gorm.DB { - return db - }, - ) - if err != nil { - s.Log.Errorf("Failed to get project flock: %+v", err) - return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock not found") - } var productWarehouses []entity.ProductWarehouse err = s.ProductWarehouseRepo.DB(). WithContext(c.Context()). 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 = ?", projectFlock.Category, warehouse.Id). + Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id). Order("created_at DESC"). Find(&productWarehouses).Error if err != nil { @@ -186,6 +175,19 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) return nil, err } + + // add ke detail chickin + newChickinDetail := &entity.ProjectChickinDetail{ + ProjectChickinId: newChickin.Id, + ProductWarehouseId: pw.Id, + Quantity: pw.Quantity, + CreatedBy: 1, // todo: ganti dengan user login + } + err = s.ProjectChickinDetailRepo.CreateOne(c.Context(), newChickinDetail, nil) + if err != nil { + s.Log.Errorf("Failed to create chickin detail: %+v", err) + return nil, err + } } existingPopulation, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId) @@ -246,85 +248,98 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) } func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { - // todo: cek apakah chickin sudah di approve atau belum + db := s.Repository.DB() - chickin, err := s.Repository.GetByID(c.Context(), id, nil) + 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) + } + 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 fiber.NewError(fiber.StatusNotFound, "Chickin not found") + return rollback(fiber.NewError(fiber.StatusNotFound, "Chickin not found")) } if err != nil { s.Log.Errorf("Failed get chickin by id: %+v", err) - return err + return rollback(err) } - population, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), chickin.ProjectFlockKandangId) - if err != nil { - s.Log.Errorf("Failed to get project flock population: %+v", err) - return err - } - - err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), population.Id, map[string]any{ - "reserved_quantity": population.ReservedQuantity - chickin.Quantity, - }, nil) - if err != nil { - s.Log.Errorf("Failed to update project flock population: %+v", err) - return err - } - - if err := s.Repository.DeleteOne(c.Context(), id); err != nil { + 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 fiber.NewError(fiber.StatusNotFound, "Chickin not found") + 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) + } + + 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 err + return rollback(err) } - projectflockkandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), population.ProjectFlockKandangId) + projectflockkandang, err := pfkRepoTx.GetByID(c.Context(), population.ProjectFlockKandangId) if err != nil { s.Log.Errorf("Failed to get projectflock kandang: %+v", err) - return err + return rollback(err) } - warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectflockkandang.KandangId) - if err != nil { + 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 err + return rollback(err) } - projectFlock, err := s.ProjectFlockRepo.GetByID( - c.Context(), - projectflockkandang.ProjectFlockId, - func(db *gorm.DB) *gorm.DB { - return db - }, - ) - - if err != nil { - s.Log.Errorf("Failed to get project flock: %+v", err) - return fiber.NewError(fiber.StatusNotFound, "Project Flock not found") - } var productWarehouse entity.ProductWarehouse - err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). + 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 = ?", projectFlock.Category, warehouse.Id). - Order("created_at DESC"). + 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 fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse") + 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 err + return rollback(err) } updatedQuantity := productWarehouse.Quantity + chickin.Quantity - err = s.ProductWarehouseRepo.PatchOne(c.Context(), productWarehouse.Id, map[string]any{ - "quantity": updatedQuantity, - }, nil) - if err != nil { + 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 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 diff --git a/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go b/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go index a5ceaf7f..fa6864fa 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go @@ -67,7 +67,6 @@ func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint Preload("ProjectFlock"). Preload("ProjectFlock.Flock"). Preload("Kandang"). - Preload("CreatedUser"). First(record, id).Error; err != nil { return nil, err }