From 8d0bd3724d43e3c00b028046b93abd4a7eb72ecb Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Tue, 21 Oct 2025 15:59:00 +0700 Subject: [PATCH 1/8] fix(BE): preload in projectflock.service --- .../production/project_flocks/services/projectflock.service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index f6ebf6e7..7dfddef9 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -75,7 +75,7 @@ func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB { Preload("Area"). Preload("Fcr"). Preload("Location"). - Preload("Kandangs.Location") + Preload("Kandangs") } func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) { From e2818b11f0c53f31b23d1f78977d9052dd61bf39 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 22 Oct 2025 08:29:00 +0700 Subject: [PATCH 2/8] fix[BE] delete unused entity and repository --- internal/entities/audit_log.go | 18 ------------- internal/entities/stock_availabilites.go | 26 ------------------- .../modules/production/chickins/module.go | 5 ++-- .../chickins/services/chickin.service.go | 5 +--- .../repositories/audit-logs.repository.go | 21 --------------- .../stock-availabilites.repository.go | 21 --------------- 6 files changed, 3 insertions(+), 93 deletions(-) delete mode 100644 internal/entities/audit_log.go delete mode 100644 internal/entities/stock_availabilites.go delete mode 100644 internal/modules/shared/repositories/audit-logs.repository.go delete mode 100644 internal/modules/shared/repositories/stock-availabilites.repository.go diff --git a/internal/entities/audit_log.go b/internal/entities/audit_log.go deleted file mode 100644 index 3b770125..00000000 --- a/internal/entities/audit_log.go +++ /dev/null @@ -1,18 +0,0 @@ -package entities - -import ( - "time" -) - -type AuditLog struct { - Id uint `gorm:"primaryKey"` - TableName string `gorm:"size:100;not null"` - RecordId uint `gorm:"not null"` - Action string `gorm:"size:30;not null"` - BeforeData string `gorm:"type:jsonb"` - AfterData string `gorm:"type:jsonb"` - ChangedBy uint `gorm:"not null"` - CreatedAt time.Time `gorm:"autoCreateTime"` - - User *User `gorm:"foreignKey:ChangedBy;references:Id"` -} diff --git a/internal/entities/stock_availabilites.go b/internal/entities/stock_availabilites.go deleted file mode 100644 index ec24d36b..00000000 --- a/internal/entities/stock_availabilites.go +++ /dev/null @@ -1,26 +0,0 @@ -package entities - -import ( - "time" - - "gorm.io/gorm" -) - -const ( - EntityTypeProjectFlockKandang = "PROJECT_FLOCK_KANDANG" -) - -type StockAvailability struct { - Id uint `gorm:"primaryKey"` - EntityType string `gorm:"size:50;not null"` - EntityId uint `gorm:"not null"` - ProductId uint `gorm:"not null"` - Quantity float64 `gorm:"not null;default:0"` - ReservedQuantity float64 `gorm:"not null;default:0"` - Unit string `gorm:"size:20"` - LastUpdated time.Time `gorm:"autoUpdateTime"` - CreatedAt time.Time `gorm:"autoCreateTime"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` - - Product *Product `gorm:"foreignKey:ProductId;references:Id"` -} diff --git a/internal/modules/production/chickins/module.go b/internal/modules/production/chickins/module.go index 116e2fbb..3ebf93c7 100644 --- a/internal/modules/production/chickins/module.go +++ b/internal/modules/production/chickins/module.go @@ -10,7 +10,6 @@ import ( rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" sChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/services" - rAuditLog "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" @@ -23,7 +22,7 @@ type ChickinModule struct{} func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { chickinRepo := rChickin.NewChickinRepository(db) kandangRepo := rKandang.NewKandangRepository(db) - auditlogrepo := rAuditLog.NewAuditLogRepository(db) + warehouseRepo := rWarehouse.NewWarehouseRepository(db) projectflockkandangrepo := rProjectFlock.NewProjectFlockKandangRepository(db) projectflockpopulationrepo := rProjectFlock.NewProjectFlockPopulationRepository(db) @@ -32,7 +31,7 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * userRepo := rUser.NewUserRepository(db) - chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, auditlogrepo, projectflockkandangrepo, projectflockpopulationrepo, validate) + chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, projectflockkandangrepo, projectflockpopulationrepo, validate) userService := sUser.NewUserService(userRepo, validate) ChickinRoutes(router, userService, chickinService) diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 43105374..751e633c 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -12,7 +12,6 @@ import ( 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" - AuditLogRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" "github.com/go-playground/validator/v10" @@ -38,12 +37,11 @@ type chickinService struct { WarehouseRepo rWarehouse.WarehouseRepository ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository ProjectFlockRepo rProjectFlock.ProjectflockRepository - AuditLogRepo AuditLogRepo.AuditLogRepository ProjectflockKandangRepo rProjectFlockKandang.ProjectFlockKandangRepository ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository } -func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, auditLogRepo AuditLogRepo.AuditLogRepository, 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 rProjectFlockKandang.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, validate *validator.Validate) ChickinService { return &chickinService{ Log: utils.Log, Validate: validate, @@ -52,7 +50,6 @@ func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo Kan WarehouseRepo: warehouseRepo, ProductWarehouseRepo: productWarehouseRepo, ProjectFlockRepo: projectFlockRepo, - AuditLogRepo: auditLogRepo, ProjectflockKandangRepo: projectflockkandangRepo, ProjectflockPopulationRepo: projectflockpopulationRepo, } diff --git a/internal/modules/shared/repositories/audit-logs.repository.go b/internal/modules/shared/repositories/audit-logs.repository.go deleted file mode 100644 index b247f3f2..00000000 --- a/internal/modules/shared/repositories/audit-logs.repository.go +++ /dev/null @@ -1,21 +0,0 @@ -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 AuditLogRepository interface { - repository.BaseRepository[entity.AuditLog] -} - -type AuditLogRepositoryImpl struct { - *repository.BaseRepositoryImpl[entity.AuditLog] -} - -func NewAuditLogRepository(db *gorm.DB) AuditLogRepository { - return &AuditLogRepositoryImpl{ - BaseRepositoryImpl: repository.NewBaseRepository[entity.AuditLog](db), - } -} diff --git a/internal/modules/shared/repositories/stock-availabilites.repository.go b/internal/modules/shared/repositories/stock-availabilites.repository.go deleted file mode 100644 index 9d3ae632..00000000 --- a/internal/modules/shared/repositories/stock-availabilites.repository.go +++ /dev/null @@ -1,21 +0,0 @@ -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 StockAvailabilityRepository interface { - repository.BaseRepository[entity.StockAvailability] -} - -type StockAvailabilityRepositoryImpl struct { - *repository.BaseRepositoryImpl[entity.StockAvailability] -} - -func NewStockAvailabilityRepository(db *gorm.DB) StockAvailabilityRepository { - return &StockAvailabilityRepositoryImpl{ - BaseRepositoryImpl: repository.NewBaseRepository[entity.StockAvailability](db), - } -} From 6dd45c32890a383c2ae106855f8669eaa32b41ec Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 22 Oct 2025 14:56:32 +0700 Subject: [PATCH 3/8] 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 } From 56b1134872f616c600ced35ad8898cfe52e4b9c6 Mon Sep 17 00:00:00 2001 From: "Hafizh A. Y" Date: Wed, 22 Oct 2025 16:49:45 +0700 Subject: [PATCH 4/8] fix(BE): req body approval and fix projectflock.dto --- .../controllers/projectflock.controller.go | 24 +++-- .../project_flocks/dto/projectflock.dto.go | 35 +++++-- .../production/project_flocks/route.go | 2 +- .../services/projectflock.service.go | 92 ++++++++++++------- .../validations/projectflock.validation.go | 5 +- 5 files changed, 103 insertions(+), 55 deletions(-) diff --git a/internal/modules/production/project_flocks/controllers/projectflock.controller.go b/internal/modules/production/project_flocks/controllers/projectflock.controller.go index 31d0b9f0..a7de23fd 100644 --- a/internal/modules/production/project_flocks/controllers/projectflock.controller.go +++ b/internal/modules/production/project_flocks/controllers/projectflock.controller.go @@ -191,29 +191,33 @@ func (u *ProjectflockController) DeleteOne(c *fiber.Ctx) error { } func (u *ProjectflockController) Approval(c *fiber.Ctx) error { - param := c.Params("id") - - id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } - req := new(validation.Approve) if err := c.BodyParser(req); err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } - result, err := u.ProjectflockService.Approval(c, uint(id), req) + results, err := u.ProjectflockService.Approval(c, req) if err != nil { return err } + var ( + data interface{} + message = "Submit projectflock approval successfully" + ) + if len(results) == 1 { + data = dto.ToProjectFlockListDTO(results[0]) + } else { + message = "Submit projectflock approvals successfully" + data = dto.ToProjectFlockListDTOs(results) + } + return c.Status(fiber.StatusOK). JSON(response.Success{ Code: fiber.StatusOK, Status: "success", - Message: "Submit projectflock approval successfully", - Data: dto.ToProjectFlockListDTO(*result), + Message: message, + Data: data, }) } diff --git a/internal/modules/production/project_flocks/dto/projectflock.dto.go b/internal/modules/production/project_flocks/dto/projectflock.dto.go index e639e968..dff3bc61 100644 --- a/internal/modules/production/project_flocks/dto/projectflock.dto.go +++ b/internal/modules/production/project_flocks/dto/projectflock.dto.go @@ -58,6 +58,30 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO { } } + var flockSummary *flockDTO.FlockBaseDTO + if e.Flock.Id != 0 { + mapped := flockDTO.ToFlockBaseDTO(e.Flock) + flockSummary = &mapped + } + + var areaSummary *areaDTO.AreaBaseDTO + if e.Area.Id != 0 { + mapped := areaDTO.ToAreaBaseDTO(e.Area) + areaSummary = &mapped + } + + var fcrSummary *fcrDTO.FcrBaseDTO + if e.Fcr.Id != 0 { + mapped := fcrDTO.ToFcrBaseDTO(e.Fcr) + fcrSummary = &mapped + } + + var locationSummary *locationDTO.LocationBaseDTO + if e.Location.Id != 0 { + mapped := locationDTO.ToLocationBaseDTO(e.Location) + locationSummary = &mapped + } + latestApproval := defaultProjectFlockLatestApproval(e) if e.LatestApproval != nil { snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval) @@ -66,8 +90,12 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO { return ProjectFlockListDTO{ ProjectFlockBaseDTO: createProjectFlockBaseDTO(e), + Flock: flockSummary, + Area: areaSummary, Kandangs: kandangSummaries, Category: e.Category, + Fcr: fcrSummary, + Location: locationSummary, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt, CreatedUser: createdUser, @@ -101,13 +129,6 @@ func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.Approv result.StepName = label } } - if result.StepName == "" { - result.StepName = "Pengajuan" - } - - if !e.CreatedAt.IsZero() { - result.ActionAt = e.CreatedAt - } if e.CreatedUser.Id != 0 { result.ActionBy = userDTO.ToUserBaseDTO(e.CreatedUser) diff --git a/internal/modules/production/project_flocks/route.go b/internal/modules/production/project_flocks/route.go index 7282c020..bc9d8797 100644 --- a/internal/modules/production/project_flocks/route.go +++ b/internal/modules/production/project_flocks/route.go @@ -25,6 +25,6 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj route.Get("/:id", ctrl.GetOne) route.Patch("/:id", ctrl.UpdateOne) route.Delete("/:id", ctrl.DeleteOne) - route.Post("/:id/approvals", ctrl.Approval) + route.Post("/approvals", ctrl.Approval) route.Get("/flocks/:flock_id/periods", ctrl.GetFlockPeriodSummary) } diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 7dfddef9..b36b5774 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -29,7 +29,7 @@ type ProjectflockService interface { UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error) DeleteOne(ctx *fiber.Ctx, id uint) error GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) - Approval(ctx *fiber.Ctx, id uint, req *validation.Approve) (*entity.ProjectFlock, error) + Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) } type projectflockService struct { @@ -501,17 +501,11 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id return s.GetOne(c, id) } -func (s projectflockService) Approval(c *fiber.Ctx, id uint, req *validation.Approve) (*entity.ProjectFlock, error) { +func (s projectflockService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } - project, err := s.GetOne(c, id) - if err != nil { - s.Log.Errorf("Failed to fetch projectflock %d before approval: %+v", id, err) - return nil, err - } - actorID := uint(1) // TODO: change from auth context var action entity.ApprovalAction switch strings.ToUpper(strings.TrimSpace(req.Action)) { @@ -523,42 +517,58 @@ func (s projectflockService) Approval(c *fiber.Ctx, id uint, req *validation.App return nil, fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED") } + approvableIDs := uniqueUintSlice(req.ApprovableIds) + if len(approvableIDs) == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id") + } + step := utils.ProjectFlockStepPengajuan if action == entity.ApprovalActionApproved { step = utils.ProjectFlockStepAktif } - err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) - if _, err := approvalSvc.CreateApproval( - c.Context(), - utils.ApprovalWorkflowProjectFlock, - project.Id, - step, - &action, - actorID, - req.Notes, - ); err != nil { - return err - } - kandangRepoTx := kandangRepository.NewKandangRepository(dbTransaction) - switch action { - case entity.ApprovalActionApproved: - if err := kandangRepoTx.UpdateStatusByProjectFlockID( + projectRepoTx := repository.NewProjectflockRepository(dbTransaction) + + for _, approvableID := range approvableIDs { + if _, err := projectRepoTx.GetByID(c.Context(), approvableID, nil); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Projectflock %d not found", approvableID)) + } + return err + } + + if _, err := approvalSvc.CreateApproval( c.Context(), - project.Id, - utils.KandangStatusActive, + utils.ApprovalWorkflowProjectFlock, + approvableID, + step, + &action, + actorID, + req.Notes, ); err != nil { return err } - case entity.ApprovalActionRejected: - if err := kandangRepoTx.UpdateStatusByProjectFlockID( - c.Context(), - project.Id, - utils.KandangStatusNonActive, - ); err != nil { - return err + + switch action { + case entity.ApprovalActionApproved: + if err := kandangRepoTx.UpdateStatusByProjectFlockID( + c.Context(), + approvableID, + utils.KandangStatusActive, + ); err != nil { + return err + } + case entity.ApprovalActionRejected: + if err := kandangRepoTx.UpdateStatusByProjectFlockID( + c.Context(), + approvableID, + utils.KandangStatusNonActive, + ); err != nil { + return err + } } } @@ -566,14 +576,26 @@ func (s projectflockService) Approval(c *fiber.Ctx, id uint, req *validation.App }) if err != nil { + if fiberErr, ok := err.(*fiber.Error); ok { + return nil, fiberErr + } if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found") } - s.Log.Errorf("Failed to record approval for projectflock %d: %+v", id, err) + s.Log.Errorf("Failed to record approval for projectflocks %+v: %+v", approvableIDs, err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval") } - return s.GetOne(c, id) + updated := make([]entity.ProjectFlock, 0, len(approvableIDs)) + for _, approvableID := range approvableIDs { + project, err := s.GetOne(c, approvableID) + if err != nil { + return nil, err + } + updated = append(updated, *project) + } + + return updated, nil } func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error { diff --git a/internal/modules/production/project_flocks/validations/projectflock.validation.go b/internal/modules/production/project_flocks/validations/projectflock.validation.go index 9ee066af..f853c883 100644 --- a/internal/modules/production/project_flocks/validations/projectflock.validation.go +++ b/internal/modules/production/project_flocks/validations/projectflock.validation.go @@ -31,6 +31,7 @@ type Query struct { } type Approve struct { - Action string `json:"action" validate:"required_strict"` - Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` + Action string `json:"action" validate:"required_strict"` + ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"` + Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` } From 00837e0da20d0a5071f154347fc97640cfa18764 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 22 Oct 2025 22:20:40 +0700 Subject: [PATCH 5/8] FIX[BE}: add stock logs for transfer stock --- ...kinDetail.go => project_chickin_detail.go} | 0 .../transfers/services/transfer.service.go | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+) rename internal/entities/{ProjectChickinDetail.go => project_chickin_detail.go} (100%) diff --git a/internal/entities/ProjectChickinDetail.go b/internal/entities/project_chickin_detail.go similarity index 100% rename from internal/entities/ProjectChickinDetail.go rename to internal/entities/project_chickin_detail.go diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index 90642f6c..dd6c0068 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -266,6 +266,24 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques } s.Log.Infof("Source product warehouse updated: %+v", sourcePW.Id) + // create stock log for decrease (source) + beforeQty := sourcePW.Quantity + product.ProductQty // sourcePW already decreased + decreaseLog := &entity.StockLog{ + TransactionType: entity.TransactionTypeDecrease, + Quantity: product.ProductQty, + BeforeQuantity: beforeQty, + AfterQuantity: sourcePW.Quantity, + LogType: entity.LogTypeTransfer, + LogId: uint(entityTransfer.Id), + Note: "", + ProductWarehouseId: sourcePW.Id, + CreatedBy: 1, + } + if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), decreaseLog, nil); err != nil { + s.Log.Errorf("Failed to create stock log decrease: %+v", err) + return err + } + // Tambah stok di gudang tujuan destPW, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID( c.Context(), uint(product.ProductID), uint(req.DestinationWarehouseID), @@ -296,6 +314,24 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques } s.Log.Infof("Destination product warehouse updated: %+v", destPW.Id) + // create stock log for increase (destination) + beforeDestQty := destPW.Quantity - product.ProductQty + increaseLog := &entity.StockLog{ + TransactionType: entity.TransactionTypeIncrease, + Quantity: product.ProductQty, + BeforeQuantity: beforeDestQty, + AfterQuantity: destPW.Quantity, + LogType: entity.LogTypeTransfer, + LogId: uint(entityTransfer.Id), + Note: "", + ProductWarehouseId: destPW.Id, + CreatedBy: 1, + } + if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), increaseLog, nil); err != nil { + s.Log.Errorf("Failed to create stock log increase: %+v", err) + return err + } + } return nil From f33eb7fcc7839a797c7b628a2590bae2243d7433 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Thu, 23 Oct 2025 09:03:28 +0700 Subject: [PATCH 6/8] feat[BE-117]: create lookup API in project flock to get projectFlocks kandang id --- ...ckin_detail.go => ProjectChickinDetail.go} | 0 .../chickins/services/chickin.service.go | 4 +- .../controllers/projectflock.controller.go | 16 +++ .../dto/projectflock_kandang.dto.go | 108 ++++++++++++++++++ .../projectflock_kandang.repository.go | 32 ++++++ .../production/project_flocks/route.go | 1 + .../services/projectflock.service.go | 56 ++++++++- 7 files changed, 214 insertions(+), 3 deletions(-) rename internal/entities/{project_chickin_detail.go => ProjectChickinDetail.go} (100%) create mode 100644 internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go diff --git a/internal/entities/project_chickin_detail.go b/internal/entities/ProjectChickinDetail.go similarity index 100% rename from internal/entities/project_chickin_detail.go rename to internal/entities/ProjectChickinDetail.go diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 16e37911..de328b46 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -109,7 +109,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } - projectflockkandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), 1) + projectflockkandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), req.ProjectFlockKandangId) if err != nil { s.Log.Errorf("Failed to get projectflock kandang: %+v", err) return nil, err @@ -154,7 +154,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid ChickInDate format") } newChickin := &entity.ProjectChickin{ - ProjectFlockKandangId: projectflockkandang.ProjectFlockId, + ProjectFlockKandangId: projectflockkandang.Id, ChickInDate: chickinDate, Quantity: totalQuantity, Note: "", diff --git a/internal/modules/production/project_flocks/controllers/projectflock.controller.go b/internal/modules/production/project_flocks/controllers/projectflock.controller.go index a7de23fd..ca60d5df 100644 --- a/internal/modules/production/project_flocks/controllers/projectflock.controller.go +++ b/internal/modules/production/project_flocks/controllers/projectflock.controller.go @@ -244,3 +244,19 @@ func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error { Data: responseBody, }) } + +func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error { + projectFlockIdStr := c.Query("project_flock_id", "") + kandangIdStr := c.Query("kandang_id", "") + + result, err := u.ProjectflockService.GetProjectFlockKandangByParams(c, "", projectFlockIdStr, kandangIdStr) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{Code: fiber.StatusOK, + Status: "success", + Message: "Get projectflock kandang successfully", + Data: dto.ToProjectFlockKandangDTO(*result)}) +} diff --git a/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go b/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go new file mode 100644 index 00000000..ff82fba9 --- /dev/null +++ b/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go @@ -0,0 +1,108 @@ +package dto + +import ( + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto" + fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto" + flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto" + kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto" + locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto" + userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" +) + +// internal DTO used only for lookup response: project flock with kandangs carrying pivot ids +type KandangWithPivotDTO struct { + kandangDTO.KandangBaseDTO + ProjectFlockKandangId *uint `json:"project_flock_kandang_id,omitempty"` +} + +type ProjectFlockWithPivotDTO struct { + ProjectFlockBaseDTO + Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"` + Area *areaDTO.AreaBaseDTO `json:"area,omitempty"` + Category string `json:"category"` + Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"` + Location *locationDTO.LocationBaseDTO `json:"location,omitempty"` + Kandangs []KandangWithPivotDTO `json:"kandangs,omitempty"` + CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"` +} + +type ProjectFlockKandangDTO struct { + Id uint `json:"id"` + ProjectFlockId uint `json:"project_flock_id"` + KandangId uint `json:"kandang_id"` + Kandang *kandangDTO.KandangBaseDTO `json:"kandang,omitempty"` + ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"` +} + +func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO { + var kandang *kandangDTO.KandangBaseDTO + if e.Kandang.Id != 0 { + mapped := kandangDTO.ToKandangBaseDTO(e.Kandang) + kandang = &mapped + } + + var pf *ProjectFlockWithPivotDTO + if e.ProjectFlock.Id != 0 { + // build project flock with kandangs that include pivot ids + pfLocal := ProjectFlockWithPivotDTO{ + ProjectFlockBaseDTO: ProjectFlockBaseDTO{ + Id: e.ProjectFlock.Id, + Period: e.ProjectFlock.Period, + }, + Category: e.ProjectFlock.Category, + } + + // fill related small summaries + if e.ProjectFlock.Flock.Id != 0 { + mapped := ToFlockSummaryDTO(e.ProjectFlock.Flock) + pfLocal.Flock = &mapped + } + if e.ProjectFlock.Area.Id != 0 { + mapped := areaDTO.ToAreaBaseDTO(e.ProjectFlock.Area) + pfLocal.Area = &mapped + } + if e.ProjectFlock.Fcr.Id != 0 { + mapped := fcrDTO.ToFcrBaseDTO(e.ProjectFlock.Fcr) + pfLocal.Fcr = &mapped + } + if e.ProjectFlock.Location.Id != 0 { + mapped := locationDTO.ToLocationBaseDTO(e.ProjectFlock.Location) + pfLocal.Location = &mapped + } + if e.ProjectFlock.CreatedUser.Id != 0 { + mapped := userDTO.ToUserBaseDTO(e.ProjectFlock.CreatedUser) + pfLocal.CreatedUser = &mapped + } + + // build pivot map + pivotMap := make(map[uint]uint) + for _, ph := range e.ProjectFlock.KandangHistory { + pivotMap[ph.KandangId] = ph.Id + } + + // populate kandangs with pivot ids + for _, k := range e.ProjectFlock.Kandangs { + kb := kandangDTO.ToKandangBaseDTO(k) + var pid *uint + if v, ok := pivotMap[k.Id]; ok { + vv := v + pid = &vv + } + pfLocal.Kandangs = append(pfLocal.Kandangs, KandangWithPivotDTO{ + KandangBaseDTO: kb, + ProjectFlockKandangId: pid, + }) + } + + pf = &pfLocal + } + + return ProjectFlockKandangDTO{ + Id: e.Id, + ProjectFlockId: e.ProjectFlockId, + KandangId: e.KandangId, + Kandang: kandang, + ProjectFlock: pf, + } +} 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 fa6864fa..5c78f830 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go @@ -9,6 +9,7 @@ import ( type ProjectFlockKandangRepository interface { GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error) + GetByProjectFlockAndKandang(ctx context.Context, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error) CreateMany(ctx context.Context, records []*entity.ProjectFlockKandang) error DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error GetAll(ctx context.Context) ([]entity.ProjectFlockKandang, error) @@ -45,6 +46,12 @@ func (r *projectFlockKandangRepositoryImpl) GetAll(ctx context.Context) ([]entit if err := r.db.WithContext(ctx). Preload("ProjectFlock"). Preload("ProjectFlock.Flock"). + Preload("ProjectFlock.Fcr"). + Preload("ProjectFlock.Area"). + Preload("ProjectFlock.Location"). + Preload("ProjectFlock.CreatedUser"). + Preload("ProjectFlock.Kandangs"). + Preload("ProjectFlock.KandangHistory"). Preload("Kandang"). Order("project_flock_id ASC, created_at ASC"). Find(&records).Error; err != nil { @@ -66,9 +73,34 @@ func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint if err := r.db.WithContext(ctx). Preload("ProjectFlock"). Preload("ProjectFlock.Flock"). + Preload("ProjectFlock.Fcr"). + Preload("ProjectFlock.Area"). + Preload("ProjectFlock.Location"). + Preload("ProjectFlock.CreatedUser"). + Preload("ProjectFlock.Kandangs"). + Preload("ProjectFlock.KandangHistory"). Preload("Kandang"). First(record, id).Error; err != nil { return nil, err } return record, nil } + +func (r *projectFlockKandangRepositoryImpl) GetByProjectFlockAndKandang(ctx context.Context, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error) { + record := new(entity.ProjectFlockKandang) + if err := r.db.WithContext(ctx). + Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID). + Preload("ProjectFlock"). + Preload("ProjectFlock.Flock"). + Preload("ProjectFlock.Fcr"). + Preload("ProjectFlock.Area"). + Preload("ProjectFlock.Location"). + Preload("ProjectFlock.CreatedUser"). + Preload("ProjectFlock.Kandangs"). + Preload("ProjectFlock.KandangHistory"). + Preload("Kandang"). + First(record).Error; err != nil { + return nil, err + } + return record, nil +} diff --git a/internal/modules/production/project_flocks/route.go b/internal/modules/production/project_flocks/route.go index bc9d8797..4c11d3a1 100644 --- a/internal/modules/production/project_flocks/route.go +++ b/internal/modules/production/project_flocks/route.go @@ -25,6 +25,7 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj route.Get("/:id", ctrl.GetOne) route.Patch("/:id", ctrl.UpdateOne) route.Delete("/:id", ctrl.DeleteOne) + route.Get("/kandangs/lookup", ctrl.LookupProjectFlockKandang) route.Post("/approvals", ctrl.Approval) route.Get("/flocks/:flock_id/periods", ctrl.GetFlockPeriodSummary) } diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index b36b5774..ffd0787f 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strconv" "strings" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" @@ -28,6 +29,7 @@ type ProjectflockService interface { CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error) DeleteOne(ctx *fiber.Ctx, id uint) error + GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error) GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) } @@ -75,7 +77,8 @@ func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB { Preload("Area"). Preload("Fcr"). Preload("Location"). - Preload("Kandangs") + Preload("Kandangs"). + Preload("KandangHistory") } func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) { @@ -639,6 +642,57 @@ func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error { return nil } +func (s projectflockService) GetProjectFlockKandang(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, error) { + // keep for backward compatibility; delegate to new consolidated method + return s.GetProjectFlockKandangByParams(ctx, fmt.Sprintf("%d", id), "", "") +} + +func (s projectflockService) GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error) { + + pfk, err := s.PivotRepo.GetByProjectFlockAndKandang(ctx.Context(), projectFlockID, kandangID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found") + } + return nil, err + } + return pfk, nil +} + +func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error) { + idStr = strings.TrimSpace(idStr) + projectFlockIdStr = strings.TrimSpace(projectFlockIdStr) + kandangIdStr = strings.TrimSpace(kandangIdStr) + + if idStr != "" { + id, err := strconv.Atoi(idStr) + if err != nil || id <= 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + pfk, err := s.PivotRepo.GetByID(ctx.Context(), uint(id)) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found") + } + return nil, err + } + return pfk, nil + } + + if projectFlockIdStr == "" || kandangIdStr == "" { + return nil, fiber.NewError(fiber.StatusBadRequest, "Missing lookup parameters") + } + pfid, err := strconv.Atoi(projectFlockIdStr) + if err != nil || pfid <= 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id") + } + kid, err := strconv.Atoi(kandangIdStr) + if err != nil || kid <= 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id") + } + return s.GetProjectFlockKandangByProjectAndKandang(ctx, uint(pfid), uint(kid)) +} + func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) { flock, err := s.FlockRepo.GetByID(c.Context(), flockID, func(db *gorm.DB) *gorm.DB { return db.Preload("CreatedUser") From 4c6ead427291e7cc8ba91e80202f34f881b762f5 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Thu, 23 Oct 2025 09:05:03 +0700 Subject: [PATCH 7/8] Feat[117]: add lookup api in project flock to get pivot id --- .../production/project_flocks/services/projectflock.service.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index ffd0787f..f9c7881e 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -77,8 +77,7 @@ func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB { Preload("Area"). Preload("Fcr"). Preload("Location"). - Preload("Kandangs"). - Preload("KandangHistory") + Preload("Kandangs") } func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) { From 1b02b660b9df3b5f52ab12598e204d44f7dbe51c Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Thu, 23 Oct 2025 10:05:57 +0700 Subject: [PATCH 8/8] feat(BE-119,117]) : create seeder for chikin details and fix busines logic on delete one chickin --- internal/database/seed/seeder.go | 79 +++++++++++- .../chickins/services/chickin.service.go | 118 ++++++++++++------ 2 files changed, 159 insertions(+), 38 deletions(-) diff --git a/internal/database/seed/seeder.go b/internal/database/seed/seeder.go index 32c3b310..791cfddb 100644 --- a/internal/database/seed/seeder.go +++ b/internal/database/seed/seeder.go @@ -1170,7 +1170,6 @@ func seedChickin(tx *gorm.DB, createdBy uint) error { return err } - // Update/Insert ProjectFlockPopulation var population entity.ProjectFlockPopulation err = tx.Where("project_flock_kandang_id = ?", seed.ProjectFlockKandangId).First(&population).Error if errors.Is(err, gorm.ErrRecordNotFound) { @@ -1198,6 +1197,84 @@ func seedChickin(tx *gorm.DB, createdBy uint) error { return err } } + + var pfk entity.ProjectFlockKandang + if err := tx.Where("id = ?", seed.ProjectFlockKandangId).First(&pfk).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + // no pivot found; skip creating details + continue + } + return err + } + + var warehouse entity.Warehouse + if err := tx.Where("kandang_id = ?", pfk.KandangId).First(&warehouse).Error; err != nil { + // if warehouse not found, cannot create details + if errors.Is(err, gorm.ErrRecordNotFound) { + continue + } + return err + } + + var productWarehouses []entity.ProductWarehouse + err = tx.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"). + Find(&productWarehouses).Error + if err != nil { + return err + } + + // If no product warehouses found, keep existing chickin.Quantity and skip details + if len(productWarehouses) == 0 { + continue + } + + // sum all pw quantities and set chickin.Quantity to that total (mimic CreateOne) + totalQty := 0.0 + for _, pw := range productWarehouses { + totalQty += pw.Quantity + } + + if chickin.Quantity != totalQty { + if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Update("quantity", totalQty).Error; err != nil { + return err + } + chickin.Quantity = totalQty + } + + for _, pw := range productWarehouses { + // ensure detail exists or create it with full pw.Quantity + var detail entity.ProjectChickinDetail + err = tx.Where("project_chickin_id = ? AND product_warehouse_id = ?", chickin.Id, pw.Id).First(&detail).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + detail = entity.ProjectChickinDetail{ + ProjectChickinId: chickin.Id, + ProductWarehouseId: pw.Id, + Quantity: pw.Quantity, + CreatedBy: createdBy, + } + if err := tx.Create(&detail).Error; err != nil { + return err + } + } else if err != nil { + return err + } else { + if detail.Quantity != pw.Quantity { + if err := tx.Model(&entity.ProjectChickinDetail{}).Where("id = ?", detail.Id).Update("quantity", pw.Quantity).Error; err != nil { + return err + } + } + } + + // zero out pw quantity + if err := tx.Model(&entity.ProductWarehouse{}).Where("id = ?", pw.Id).Update("quantity", 0).Error; err != nil { + return err + } + } } return nil diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index de328b46..0df1b6b5 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -261,6 +261,7 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { } return err } + chickinRepoTx := s.Repository.WithTx(tx) pfkRepoTx := s.ProjectflockKandangRepo.WithTx(tx) productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(tx) @@ -292,6 +293,86 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { return rollback(err) } + // helper: restore quantities from details; returns (restored bool, error) + 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")) @@ -300,43 +381,6 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { return rollback(err) } - 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) - } - if err := tx.Commit().Error; err != nil { s.Log.Errorf("Failed to commit transaction: %+v", err) return rollback(err)