package service import ( "errors" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" 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" 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" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" "gorm.io/gorm" ) type ChickinService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error) GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectChickin, error) CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) DeleteOne(ctx *fiber.Ctx, id uint) error Approve(ctx *fiber.Ctx, id uint) error } type chickinService struct { Log *logrus.Logger Validate *validator.Validate Repository repository.ProjectChickinRepository KandangRepo KandangRepo.KandangRepository WarehouseRepo rWarehouse.WarehouseRepository ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository ProjectFlockRepo rProjectFlock.ProjectflockRepository 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 rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, validate *validator.Validate) ChickinService { return &chickinService{ Log: utils.Log, Validate: validate, Repository: repo, KandangRepo: kandangRepo, WarehouseRepo: warehouseRepo, ProductWarehouseRepo: productWarehouseRepo, ProjectFlockRepo: projectFlockRepo, ProjectflockKandangRepo: projectflockkandangRepo, ProjectflockPopulationRepo: projectflockpopulationRepo, ProjectChickinDetailRepo: projectChickinDetailRepo, } } func (s chickinService) withRelations(db *gorm.DB) *gorm.DB { return db. Preload("CreatedUser"). Preload("ProjectFlockKandang.Kandang"). Preload("ProjectFlockKandang.Kandang.Location"). Preload("ProjectFlockKandang.Kandang.Location.Area"). Preload("ProjectFlockKandang.Kandang.Pic"). Preload("ProjectFlockKandang.ProjectFlock"). Preload("ProjectFlockKandang.ProjectFlock.Flock"). Preload("ProjectFlockKandang.ProjectFlock.Area"). Preload("ProjectFlockKandang.ProjectFlock.Fcr"). Preload("ProjectFlockKandang.ProjectFlock.Location"). Preload("ProjectFlockKandang.ProjectFlock.Location.Area") } func (s chickinService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error) { if err := s.Validate.Struct(params); err != nil { return nil, 0, err } offset := (params.Page - 1) * params.Limit chickins, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) if params.ProjectFlockKandangId != 0 { return db.Where("project_flock_kandang_id = ?", params.ProjectFlockKandangId) } return db.Order("created_at DESC").Order("updated_at DESC") }) if err != nil { s.Log.Errorf("Failed to get chickins: %+v", err) return nil, 0, err } return chickins, total, nil } func (s chickinService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectChickin, error) { chickin, err := s.Repository.GetByID(c.Context(), id, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Chickin not found") } if err != nil { s.Log.Errorf("Failed get chickin by id: %+v", err) return nil, err } return chickin, nil } func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } 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 } warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectflockkandang.KandangId) if err != nil { s.Log.Errorf("Failed to get warehouse: %+v", err) return nil, err } // move complex DB query into repository for cleaner service productWarehouses, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), "DOC", warehouse.Id) if err != nil { s.Log.Errorf("Failed to get product warehouses: %+v", err) return nil, err } if len(productWarehouses) == 0 { return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse") } totalQuantity := 0.0 for _, pw := range productWarehouses { totalQuantity += pw.Quantity } if totalQuantity < 1 { return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient quantity in Product Warehouses") } chickinDate, err := utils.ParseDateString(req.ChickInDate) if err != nil { s.Log.Errorf("Failed to parse chickin date: %+v", err) return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid ChickInDate format") } newChickin := &entity.ProjectChickin{ ProjectFlockKandangId: projectflockkandang.Id, ChickInDate: chickinDate, Quantity: totalQuantity, Note: req.Note, CreatedBy: 1, //todo: ganti dengan user login } err = s.Repository.CreateOne(c.Context(), newChickin, nil) if err != nil { s.Log.Errorf("Failed to create chickin: %+v", err) return nil, err } // Update semua product warehouse: set quantity jadi 0 for _, pw := range productWarehouses { err = s.ProductWarehouseRepo.PatchOne(c.Context(), pw.Id, map[string]any{ "quantity": 0, }, nil) if err != nil { s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) return nil, err } 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) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { s.Log.Errorf("Failed to get project flock population: %+v", err) return nil, err } if existingPopulation != nil { err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), existingPopulation.Id, map[string]any{ "reserved_quantity": newChickin.Quantity + existingPopulation.ReservedQuantity, }, nil) if err != nil { s.Log.Errorf("Failed to update project flock population: %+v", err) return nil, err } } else { newPopulation := &entity.ProjectFlockPopulation{ ProjectFlockKandangId: req.ProjectFlockKandangId, InitialQuantity: 0, CurrentQuantity: 0, ReservedQuantity: newChickin.Quantity, CreatedBy: 1, // todo: ganti dengan user login } err = s.ProjectflockPopulationRepo.CreateOne(c.Context(), newPopulation, nil) if err != nil { s.Log.Errorf("Failed to create project flock population: %+v", err) return nil, err } } return s.GetOne(c, newChickin.Id) } func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } updateBody := make(map[string]any) if req.ChickInDate != "" { updateBody["chick_in_date"] = req.ChickInDate } if req.Note != "" { updateBody["note"] = req.Note } if len(updateBody) == 0 { return s.GetOne(c, id) } if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Chickin not found") } s.Log.Errorf("Failed to update chickin: %+v", err) return nil, err } return s.GetOne(c, id) } func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { db := s.Repository.DB() tx := db.WithContext(c.Context()).Begin() if tx.Error != nil { s.Log.Errorf("Failed to begin transaction: %+v", tx.Error) return tx.Error } rollback := func(err error) error { if rerr := tx.Rollback().Error; rerr != nil { s.Log.Errorf("Rollback failed: %+v", rerr) } return err } chickinRepoTx := s.Repository.WithTx(tx) pfkRepoTx := s.ProjectflockKandangRepo.WithTx(tx) productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(tx) chickin, err := chickinRepoTx.GetByID(c.Context(), id, nil) if errors.Is(err, gorm.ErrRecordNotFound) { return rollback(fiber.NewError(fiber.StatusNotFound, "Chickin not found")) } if err != nil { s.Log.Errorf("Failed get chickin by id: %+v", err) return rollback(err) } var population entity.ProjectFlockPopulation if err := tx.WithContext(c.Context()).Where("project_flock_kandang_id = ?", chickin.ProjectFlockKandangId).First(&population).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return rollback(fiber.NewError(fiber.StatusNotFound, "Project flock population not found")) } s.Log.Errorf("Failed to get project flock population: %+v", err) return rollback(err) } newReserved := population.ReservedQuantity - chickin.Quantity if newReserved < 0 { newReserved = 0 } if err := tx.WithContext(c.Context()).Model(&entity.ProjectFlockPopulation{}).Where("id = ?", population.Id).Updates(map[string]any{"reserved_quantity": newReserved}).Error; err != nil { s.Log.Errorf("Failed to update project flock population: %+v", err) return rollback(err) } restoreFromDetails := func() (bool, error) { var details []entity.ProjectChickinDetail if err := tx.WithContext(c.Context()).Where("project_chickin_id = ?", chickin.Id).Find(&details).Error; err != nil { return false, err } if len(details) == 0 { return false, nil } for _, d := range details { var pw entity.ProductWarehouse if err := tx.WithContext(c.Context()).Where("id = ?", d.ProductWarehouseId).First(&pw).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { continue } return false, err } updatedQuantity := pw.Quantity + d.Quantity if err := productWarehouseRepoTx.PatchOne(c.Context(), pw.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil { return false, err } } if err := tx.WithContext(c.Context()).Where("project_chickin_id = ?", chickin.Id).Delete(&entity.ProjectChickinDetail{}).Error; err != nil { return false, err } return true, nil } restored, err := restoreFromDetails() if err != nil { s.Log.Errorf("Failed to restore from chickin details: %+v", err) return rollback(err) } if !restored { projectflockkandang, err := pfkRepoTx.GetByID(c.Context(), population.ProjectFlockKandangId) if err != nil { s.Log.Errorf("Failed to get projectflock kandang: %+v", err) return rollback(err) } var warehouse entity.Warehouse if err := tx.WithContext(c.Context()).Where("kandang_id = ?", projectflockkandang.KandangId).First(&warehouse).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return rollback(fiber.NewError(fiber.StatusNotFound, "Warehouse not found for kandang")) } s.Log.Errorf("Failed to get warehouse: %+v", err) return rollback(err) } var productWarehouse entity.ProductWarehouse err = tx.WithContext(c.Context()).Table("product_warehouses"). Select("product_warehouses.*"). Joins("JOIN products ON products.id = product_warehouses.product_id"). Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id). Order("product_warehouses.created_at DESC"). First(&productWarehouse).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return rollback(fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse")) } s.Log.Errorf("Failed to get product warehouse: %+v", err) return rollback(err) } updatedQuantity := productWarehouse.Quantity + chickin.Quantity if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouse.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil { s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) return rollback(err) } } // delete chickin (single place) if err := chickinRepoTx.DeleteOne(c.Context(), id); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return rollback(fiber.NewError(fiber.StatusNotFound, "Chickin not found")) } s.Log.Errorf("Failed to delete chickin: %+v", err) return rollback(err) } if err := tx.Commit().Error; err != nil { s.Log.Errorf("Failed to commit transaction: %+v", err) return rollback(err) } return nil } func (s *chickinService) Approve(c *fiber.Ctx, id uint) error { // todo: ini contoh akhir jika sudah approved chickin, err := s.Repository.GetByID( c.Context(), id, nil, ) if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "Chickin not found") } if err != nil { s.Log.Errorf("Failed get chickin by id: %+v", err) return 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, "initial_quantity": population.InitialQuantity + chickin.Quantity, "current_quantity": population.CurrentQuantity + chickin.Quantity, }, nil) if err != nil { s.Log.Errorf("Failed to update project flock population: %+v", err) return err } return nil }