FIX[BE]: fix logic on Chickin Laying not convert to layer but still Pullet, and inisiate laying transfer migration and base basic API

This commit is contained in:
aguhh18
2025-11-04 08:24:38 +07:00
parent c72db5bd18
commit 8220e34302
22 changed files with 587 additions and 163 deletions
@@ -150,8 +150,12 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
actorID := uint(1) // todo nanti ambil dari auth context
newChikins := make([]*entity.ProjectChickin, 0)
for _, productWarehouse := range productWarehouses {
availableQty, err := s.calculateAvailableQuantity(c, req.ProjectFlockKandangId, &productWarehouse, category)
if err != nil {
s.Log.Warnf("Failed to calculate available quantity for product warehouse %d: %v", productWarehouse.Id, err)
}
if productWarehouse.Quantity <= 0 {
if availableQty <= 0 {
continue
}
@@ -159,7 +163,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
ProjectFlockKandangId: req.ProjectFlockKandangId,
ChickInDate: chickinDate,
UsageQty: 0,
PendingUsageQty: productWarehouse.Quantity,
PendingUsageQty: availableQty,
ProductWarehouseId: productWarehouse.Id,
Notes: req.Note,
CreatedBy: actorID,
@@ -176,6 +180,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
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 {
@@ -192,17 +197,19 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
return err
}
for _, chickin := range newChikins {
if category == string(utils.ProjectFlockCategoryLaying) {
for _, chickin := range newChikins {
updates := map[string]any{"quantity": gorm.Expr("quantity - ?", chickin.PendingUsageQty)}
updates := map[string]any{"quantity": 0}
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found", chickin.ProductWarehouseId))
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found", chickin.ProductWarehouseId))
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update product warehouse quantity")
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update product warehouse quantity")
}
}
var approvalAction entity.ApprovalAction
if isFirstTime {
approvalAction = entity.ApprovalActionCreated
@@ -287,6 +294,56 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
return nil
}
func (s chickinService) calculateAvailableQuantity(ctx *fiber.Ctx, projectFlockKandangID uint, productWarehouse *entity.ProductWarehouse, category string) (float64, error) {
availableQty := productWarehouse.Quantity
if category == string(utils.ProjectFlockCategoryGrowing) {
var totalPendingQty float64
chickins, err := s.Repository.GetByProjectFlockKandangID(ctx.Context(), projectFlockKandangID)
if err == nil {
for _, chickin := range chickins {
if chickin.ProductWarehouseId == productWarehouse.Id && chickin.DeletedAt.Time.IsZero() && chickin.PendingUsageQty > 0 {
totalPendingQty += chickin.PendingUsageQty
}
}
}
availableQty = productWarehouse.Quantity - totalPendingQty
if availableQty < 0 {
availableQty = 0
}
} else if category == string(utils.ProjectFlockCategoryLaying) {
var totalPopulation float64
var totalPendingQty float64
populations, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangIDAndProductWarehouseID(ctx.Context(), projectFlockKandangID, productWarehouse.Id)
if err == nil {
for _, pop := range populations {
totalPopulation += pop.TotalQty
}
}
chickins, err := s.Repository.GetByProjectFlockKandangID(ctx.Context(), projectFlockKandangID)
if err == nil {
for _, chickin := range chickins {
if chickin.ProductWarehouseId == productWarehouse.Id && chickin.DeletedAt.Time.IsZero() && chickin.PendingUsageQty > 0 {
totalPendingQty += chickin.PendingUsageQty
}
}
}
availableQty = productWarehouse.Quantity - totalPopulation - totalPendingQty
if availableQty < 0 {
availableQty = 0
}
}
return availableQty, nil
}
func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.ProjectChickin, error) {
if err := s.Validate.Struct(req); err != nil {
return nil, err
@@ -356,7 +413,7 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
return err
}
s.Log.Infof("ignored duplicate approval for kandang %d: %v", approvableID, err)
}
if action == entity.ApprovalActionApproved {
@@ -374,54 +431,76 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
return err
}
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), kandangForApproval.KandangId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Warehouse for kandang %d not found", kandangForApproval.KandangId))
}
return err
}
category := strings.ToUpper(strings.TrimSpace(kandangForApproval.ProjectFlock.Category))
var conversionCategoryCode string
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 category == string(utils.ProjectFlockCategoryGrowing) {
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), kandangForApproval.KandangId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Warehouse for kandang %d not found", kandangForApproval.KandangId))
}
return err
}
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID)
if err != nil {
return fmt.Errorf("failed to get/create PULLET product warehouse: %w", err)
}
if err := s.convertChickinsToTarget(c, chickins, targetPW, dbTransaction, actorID); err != nil {
return err
}
} else if category == string(utils.ProjectFlockCategoryLaying) {
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), kandangForApproval.KandangId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Warehouse for kandang %d not found", kandangForApproval.KandangId))
}
return err
}
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID)
if err != nil {
return fmt.Errorf("failed to get/create PULLET product warehouse: %w", err)
}
if err := s.convertChickinsToTarget(c, chickins, targetPW, dbTransaction, actorID); err != nil {
return err
}
}
}
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, conversionCategoryCode, dbTransaction, actorID)
if action == entity.ApprovalActionRejected {
chickins, err := chickinRepoTx.GetPendingByProjectFlockKandangID(c.Context(), approvableID)
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 && !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("failed to get chickins for rejection %d: %w", approvableID, err)
return fmt.Errorf("failed to get pending chickins for rejection %d: %w", approvableID, err)
}
if len(chickins) == 0 {
continue
}
kandangForRejection, err := s.ProjectflockKandangRepo.GetByID(c.Context(), approvableID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("ProjectFlockKandang %d not found", approvableID))
}
return err
}
categoryForRejection := strings.ToUpper(strings.TrimSpace(kandangForRejection.ProjectFlock.Category))
for _, chickin := range chickins {
updates := map[string]any{"quantity": chickin.PendingUsageQty}
if categoryForRejection == string(utils.ProjectFlockCategoryGrowing) {
updates := map[string]any{"quantity": gorm.Expr("quantity + ?", chickin.PendingUsageQty)}
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found during rejection", chickin.ProductWarehouseId))
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
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)
}
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 {
@@ -518,6 +597,18 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti
return fmt.Errorf("failed to update chickin %d qty: %w", chickin.Id, err)
}
if chickin.ProductWarehouseId != targetPW.Id {
if err := productWarehouseTx.PatchOne(ctx.Context(), chickin.ProductWarehouseId, map[string]any{
"quantity": gorm.Expr("quantity - ?", quantityToConvert),
}, nil); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Source product warehouse %d not found", chickin.ProductWarehouseId))
}
return fmt.Errorf("failed to deduct source warehouse quantity for chickin %d: %w", chickin.Id, err)
}
}
// Add to target product warehouse
if err := productWarehouseTx.PatchOne(ctx.Context(), targetPW.Id, map[string]any{
"quantity": gorm.Expr("quantity + ?", quantityToConvert),
}, nil); err != nil {