mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-25 07:45:44 +00:00
Feat[BE]: add multilpple type of chickin growing and laying, make convertion product when chickin approved, add projectflockkandangid on projectflock api
This commit is contained in:
@@ -25,7 +25,7 @@ import (
|
||||
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)
|
||||
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
|
||||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectChickin, error)
|
||||
@@ -109,7 +109,7 @@ func (s chickinService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectChickin, e
|
||||
return chickin, nil
|
||||
}
|
||||
|
||||
func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error) {
|
||||
func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]entity.ProjectChickin, error) {
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -150,6 +150,11 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
||||
actorID := uint(1) // todo nanti ambil dari auth context
|
||||
newChikins := make([]*entity.ProjectChickin, 0)
|
||||
for _, productWarehouse := range productWarehouses {
|
||||
|
||||
if productWarehouse.Quantity <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
newChickin := &entity.ProjectChickin{
|
||||
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
||||
ChickInDate: chickinDate,
|
||||
@@ -167,6 +172,12 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "No chickins to create")
|
||||
}
|
||||
|
||||
existingChikins, err := s.Repository.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing chickins")
|
||||
}
|
||||
isFirstTime := len(existingChikins) == 0
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||
@@ -187,16 +198,27 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
||||
|
||||
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("failed to update product warehouse quantity for id %d", chickin.ProductWarehouseId)
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found", chickin.ProductWarehouseId))
|
||||
}
|
||||
return err
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update product warehouse quantity")
|
||||
}
|
||||
}
|
||||
var approvalAction entity.ApprovalAction
|
||||
if isFirstTime {
|
||||
approvalAction = entity.ApprovalActionCreated
|
||||
} else {
|
||||
approvalAction = entity.ApprovalActionUpdated
|
||||
}
|
||||
|
||||
if latest == nil {
|
||||
|
||||
action := entity.ApprovalActionCreated
|
||||
if _, err := approvalSvcTx.CreateApproval(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, utils.ProjectFlockKandangStepPengajuan, &action, actorID, nil); err != nil {
|
||||
if _, err := approvalSvcTx.CreateApproval(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, utils.ProjectFlockKandangStepPengajuan, &approvalAction, actorID, nil); err != nil {
|
||||
lower := strings.ToLower(err.Error())
|
||||
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if latest.StepNumber != uint16(utils.ProjectFlockKandangStepPengajuan) {
|
||||
if _, err := approvalSvcTx.CreateApproval(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, utils.ProjectFlockKandangStepPengajuan, &approvalAction, actorID, nil); err != nil {
|
||||
lower := strings.ToLower(err.Error())
|
||||
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
|
||||
return err
|
||||
@@ -210,7 +232,19 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return newChikins[0], nil
|
||||
result := make([]entity.ProjectChickin, 0, len(newChikins))
|
||||
for _, chickin := range newChikins {
|
||||
loaded, err := s.GetOne(c, chickin.Id)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to reload chickin %d with relations: %v", chickin.Id, err))
|
||||
}
|
||||
result = append(result, *loaded)
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load created chickins")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) {
|
||||
@@ -257,7 +291,8 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
if err := s.Validate.Struct(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actorID := uint(1) // todo nanti ambil dari auth context
|
||||
|
||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.Repository.DB()))
|
||||
|
||||
var action entity.ApprovalAction
|
||||
switch strings.ToUpper(strings.TrimSpace(req.Action)) {
|
||||
@@ -269,21 +304,19 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED")
|
||||
}
|
||||
|
||||
approvableIDs := uniqueUintSlice(req.ApprovableIds)
|
||||
approvableIDs := utils.UniqueUintSlice(req.ApprovableIds)
|
||||
if len(approvableIDs) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
||||
}
|
||||
|
||||
// Validate all ProjectFlockKandang IDs exist and have valid approval status
|
||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.Repository.DB()))
|
||||
for _, id := range approvableIDs {
|
||||
idCopy := id
|
||||
if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "ProjectFlockKandang", ID: &idCopy, Exists: s.ProjectflockKandangRepo.IdExists}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check latest approval status - must be PENGAJUAN to be approvable
|
||||
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, id, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||
}
|
||||
@@ -308,6 +341,7 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
|
||||
for _, approvableID := range approvableIDs {
|
||||
|
||||
actorID := uint(1) // todo nanti ambil dari auth context
|
||||
if _, err := approvalSvc.CreateApproval(
|
||||
c.Context(),
|
||||
utils.ApprovalWorkflowProjectFlockKandang,
|
||||
@@ -348,21 +382,34 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
return err
|
||||
}
|
||||
|
||||
pulletPW, err := s.getOrCreatePulletProductWarehouse(c, warehouse.Id, dbTransaction, actorID)
|
||||
if err != nil {
|
||||
category := strings.ToUpper(strings.TrimSpace(kandangForApproval.ProjectFlock.Category))
|
||||
var conversionCategoryCode string
|
||||
|
||||
continue
|
||||
switch category {
|
||||
case string(utils.ProjectFlockCategoryGrowing):
|
||||
conversionCategoryCode = "PULLET"
|
||||
case string(utils.ProjectFlockCategoryLaying):
|
||||
conversionCategoryCode = "LAYER"
|
||||
default:
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Unknown category for conversion: %s", category))
|
||||
}
|
||||
|
||||
if err := s.convertChickinsToPullet(c, chickins, pulletPW, dbTransaction, actorID); err != nil {
|
||||
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, conversionCategoryCode, dbTransaction, actorID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get/create %s product warehouse: %w", conversionCategoryCode, err)
|
||||
}
|
||||
if err := s.convertChickinsToTarget(c, chickins, targetPW, dbTransaction, actorID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else if action == entity.ApprovalActionRejected {
|
||||
|
||||
chickins, err := chickinRepoTx.GetByProjectFlockKandangID(c.Context(), approvableID)
|
||||
if err != nil {
|
||||
s.Log.Warnf("failed to get chickins for rejection %d: %v", approvableID, err)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("failed to get chickins for rejection %d: %w", approvableID, err)
|
||||
}
|
||||
|
||||
if len(chickins) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -371,12 +418,21 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
updates := map[string]any{"quantity": chickin.PendingUsageQty}
|
||||
|
||||
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||
s.Log.Warnf("failed to restore product warehouse quantity for rejection: %v", err)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found during rejection", chickin.ProductWarehouseId))
|
||||
}
|
||||
return fmt.Errorf("failed to restore product warehouse quantity for chickin %d: %w", chickin.Id, err)
|
||||
}
|
||||
|
||||
if err := chickinRepoTx.DeleteOne(c.Context(), chickin.Id); err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("failed to delete rejected chickin %d: %w", chickin.Id, err)
|
||||
}
|
||||
s.Log.Infof("chickin %d already deleted during rejection", chickin.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -387,7 +443,6 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
||||
}
|
||||
s.Log.Errorf("Failed to record approval for chickins %+v: %+v", approvableIDs, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
||||
}
|
||||
|
||||
@@ -403,39 +458,39 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (s *chickinService) getOrCreatePulletProductWarehouse(ctx *fiber.Ctx, warehouseId uint, dbTransaction *gorm.DB, actorID uint) (*entity.ProductWarehouse, error) {
|
||||
func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId uint, categoryCode string, dbTransaction *gorm.DB, actorID uint) (*entity.ProductWarehouse, error) {
|
||||
|
||||
pulletProducts, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(ctx.Context(), "PULLET", warehouseId)
|
||||
if err == nil && len(pulletProducts) > 0 {
|
||||
return &pulletProducts[0], nil
|
||||
products, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(ctx.Context(), categoryCode, warehouseId)
|
||||
if err == nil && len(products) > 0 {
|
||||
return &products[0], nil
|
||||
}
|
||||
|
||||
pulletProduct, err := s.ProductWarehouseRepo.GetFirstProductByCategoryCode(ctx.Context(), "PULLET")
|
||||
product, err := s.ProductWarehouseRepo.GetFirstProductByCategoryCode(ctx.Context(), categoryCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get PULLET product: %w", err)
|
||||
return nil, fmt.Errorf("failed to get %s product: %w", categoryCode, err)
|
||||
}
|
||||
if pulletProduct == nil {
|
||||
return nil, fmt.Errorf("no PULLET product found in system")
|
||||
if product == nil {
|
||||
return nil, fmt.Errorf("no %s product found in system", categoryCode)
|
||||
}
|
||||
|
||||
newPulletPW := &entity.ProductWarehouse{
|
||||
ProductId: pulletProduct.Id,
|
||||
newPW := &entity.ProductWarehouse{
|
||||
ProductId: product.Id,
|
||||
WarehouseId: warehouseId,
|
||||
Quantity: 0,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).CreateOne(ctx.Context(), newPulletPW, nil); err != nil {
|
||||
return nil, fmt.Errorf("failed to create PULLET product warehouse: %w", err)
|
||||
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).CreateOne(ctx.Context(), newPW, nil); err != nil {
|
||||
return nil, fmt.Errorf("failed to create %s product warehouse: %w", categoryCode, err)
|
||||
}
|
||||
|
||||
return newPulletPW, nil
|
||||
return newPW, nil
|
||||
}
|
||||
|
||||
func (s *chickinService) convertChickinsToPullet(ctx *fiber.Ctx, chickins []entity.ProjectChickin, pulletPW *entity.ProductWarehouse, dbTransaction *gorm.DB, actorID uint) error {
|
||||
func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []entity.ProjectChickin, targetPW *entity.ProductWarehouse, dbTransaction *gorm.DB, actorID uint) error {
|
||||
|
||||
if pulletPW == nil || pulletPW.Id == 0 {
|
||||
return fmt.Errorf("invalid PULLET product warehouse")
|
||||
if targetPW == nil || targetPW.Id == 0 {
|
||||
return fmt.Errorf("invalid target product warehouse")
|
||||
}
|
||||
|
||||
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
|
||||
@@ -444,6 +499,16 @@ func (s *chickinService) convertChickinsToPullet(ctx *fiber.Ctx, chickins []enti
|
||||
|
||||
for _, chickin := range chickins {
|
||||
|
||||
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(ctx.Context(), chickin.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check population existence for chickin %d: %w", chickin.Id, err)
|
||||
}
|
||||
|
||||
if populationExists {
|
||||
s.Log.Infof("population already exists for chickin %d, skipping", chickin.Id)
|
||||
continue
|
||||
}
|
||||
|
||||
quantityToConvert := chickin.PendingUsageQty
|
||||
|
||||
if err := chickinRepoTx.PatchOne(ctx.Context(), chickin.Id, map[string]any{
|
||||
@@ -453,52 +518,31 @@ func (s *chickinService) convertChickinsToPullet(ctx *fiber.Ctx, chickins []enti
|
||||
return fmt.Errorf("failed to update chickin %d qty: %w", chickin.Id, err)
|
||||
}
|
||||
|
||||
// Update quantity di PULLET product warehouse dengan quantity dari chickin
|
||||
if err := productWarehouseTx.PatchOne(ctx.Context(), pulletPW.Id, map[string]any{
|
||||
if err := productWarehouseTx.PatchOne(ctx.Context(), targetPW.Id, map[string]any{
|
||||
"quantity": gorm.Expr("quantity + ?", quantityToConvert),
|
||||
}, nil); err != nil {
|
||||
s.Log.Warnf("failed to update PULLET warehouse quantity: %v", err)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Target product warehouse %d not found", targetPW.Id))
|
||||
}
|
||||
return fmt.Errorf("failed to update target warehouse quantity: %w", err)
|
||||
}
|
||||
|
||||
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(ctx.Context(), chickin.Id)
|
||||
if err != nil {
|
||||
s.Log.Warnf("failed to check population existence for chickin %d: %v", chickin.Id, err)
|
||||
continue
|
||||
population := &entity.ProjectFlockPopulation{
|
||||
ProjectChickinId: chickin.Id,
|
||||
ProductWarehouseId: targetPW.Id,
|
||||
TotalQty: quantityToConvert,
|
||||
TotalUsedQty: 0,
|
||||
Notes: chickin.Notes,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
if !populationExists {
|
||||
population := &entity.ProjectFlockPopulation{
|
||||
ProjectChickinId: chickin.Id,
|
||||
ProductWarehouseId: pulletPW.Id,
|
||||
TotalQty: quantityToConvert,
|
||||
TotalUsedQty: 0,
|
||||
Notes: chickin.Notes,
|
||||
CreatedBy: actorID,
|
||||
if err := ProjectFlockPopulationRepotx.CreateOne(ctx.Context(), population, nil); err != nil {
|
||||
lower := strings.ToLower(err.Error())
|
||||
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
|
||||
return err
|
||||
}
|
||||
if err := ProjectFlockPopulationRepotx.CreateOne(ctx.Context(), population, nil); err != nil {
|
||||
lower := strings.ToLower(err.Error())
|
||||
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
|
||||
return err
|
||||
}
|
||||
s.Log.Infof("ignored duplicate population for chickin %d: %v", chickin.Id, err)
|
||||
}
|
||||
} else {
|
||||
s.Log.Infof("population already exists for chickin %d", chickin.Id)
|
||||
s.Log.Infof("ignored duplicate population for chickin %d: %v", chickin.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uniqueUintSlice(values []uint) []uint {
|
||||
seen := make(map[uint]struct{}, len(values))
|
||||
result := make([]uint, 0, len(values))
|
||||
for _, v := range values {
|
||||
if _, ok := seen[v]; ok {
|
||||
continue
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
result = append(result, v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user