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/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/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/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/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 diff --git a/internal/modules/production/chickins/module.go b/internal/modules/production/chickins/module.go index 116e2fbb..f1e0baea 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" @@ -22,8 +21,9 @@ 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) - auditlogrepo := rAuditLog.NewAuditLogRepository(db) + warehouseRepo := rWarehouse.NewWarehouseRepository(db) projectflockkandangrepo := rProjectFlock.NewProjectFlockKandangRepository(db) projectflockpopulationrepo := rProjectFlock.NewProjectFlockPopulationRepository(db) @@ -32,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, auditlogrepo, 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 43105374..0df1b6b5 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -8,11 +8,8 @@ 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" - 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 +35,12 @@ type chickinService struct { WarehouseRepo rWarehouse.WarehouseRepository ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository ProjectFlockRepo rProjectFlock.ProjectflockRepository - AuditLogRepo AuditLogRepo.AuditLogRepository - 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, 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 rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, validate *validator.Validate) ChickinService { return &chickinService{ Log: utils.Log, Validate: validate, @@ -52,9 +49,9 @@ func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo Kan WarehouseRepo: warehouseRepo, ProductWarehouseRepo: productWarehouseRepo, ProjectFlockRepo: projectFlockRepo, - AuditLogRepo: auditLogRepo, ProjectflockKandangRepo: projectflockkandangRepo, ProjectflockPopulationRepo: projectflockpopulationRepo, + ProjectChickinDetailRepo: projectChickinDetailRepo, } } @@ -112,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 @@ -124,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 { @@ -168,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: "", @@ -189,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) @@ -249,85 +248,142 @@ 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) + } + + // 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")) } s.Log.Errorf("Failed to delete chickin: %+v", err) - return err + return rollback(err) } - projectflockkandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), population.ProjectFlockKandangId) - if err != nil { - s.Log.Errorf("Failed to get projectflock kandang: %+v", err) - return err - } - warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectflockkandang.KandangId) - if err != nil { - s.Log.Errorf("Failed to get warehouse: %+v", err) - return 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()). - 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"). - 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") - } - s.Log.Errorf("Failed to get product warehouse: %+v", err) - return err - } - - updatedQuantity := productWarehouse.Quantity + chickin.Quantity - err = s.ProductWarehouseRepo.PatchOne(c.Context(), productWarehouse.Id, map[string]any{ - "quantity": updatedQuantity, - }, nil) - if err != nil { - s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) - return 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/controllers/projectflock.controller.go b/internal/modules/production/project_flocks/controllers/projectflock.controller.go index 31d0b9f0..ca60d5df 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, }) } @@ -240,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.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/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 7282c020..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.Post("/:id/approvals", ctrl.Approval) + 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 f6ebf6e7..f9c7881e 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,8 +29,9 @@ 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, id uint, req *validation.Approve) (*entity.ProjectFlock, error) + Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) } type projectflockService struct { @@ -75,7 +77,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) { @@ -501,17 +503,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 +519,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 +578,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 { @@ -617,6 +641,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") 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"` } 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), - } -}