mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-23 06:45:43 +00:00
Merge branch 'feat/BE/Sprint-8' of https://gitlab.com/mbugroup/lti-api into feat/BE/US-281-adjustment_recording
This commit is contained in:
@@ -2,6 +2,7 @@ package chickins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||
|
||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||
@@ -36,16 +38,43 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
||||
projectflockpopulationrepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
||||
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
|
||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||
|
||||
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
|
||||
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
||||
userRepo := rUser.NewUserRepository(db)
|
||||
|
||||
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
||||
Key: fifo.UsableKeyProjectChickin,
|
||||
Table: "project_chickins",
|
||||
Columns: fifo.UsableColumns{
|
||||
ID: "id",
|
||||
ProductWarehouseID: "product_warehouse_id",
|
||||
UsageQuantity: "usage_qty",
|
||||
PendingQuantity: "pending_usage_qty",
|
||||
CreatedAt: "created_at",
|
||||
},
|
||||
}); err != nil {
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
||||
panic(fmt.Sprintf("failed to register chickin usable workflow: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowChickin, utils.ChickinApprovalSteps); err != nil {
|
||||
panic(fmt.Sprintf("failed to register chickin approval workflow: %v", err))
|
||||
}
|
||||
|
||||
chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, projectflockkandangrepo, projectflockpopulationrepo, chickinDetailRepo, validate)
|
||||
chickinService := sChickin.NewChickinService(
|
||||
chickinRepo,
|
||||
kandangRepo,
|
||||
warehouseRepo,
|
||||
productWarehouseRepo,
|
||||
projectFlockRepo,
|
||||
projectflockkandangrepo,
|
||||
projectflockpopulationrepo,
|
||||
chickinDetailRepo,
|
||||
validate,
|
||||
fifoService)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
ChickinRoutes(router, userService, chickinService)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -15,7 +16,9 @@ import (
|
||||
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"
|
||||
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@@ -23,6 +26,8 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var chickinUsableKey = fifo.UsableKeyProjectChickin
|
||||
|
||||
type ChickinService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectChickin, error)
|
||||
@@ -43,9 +48,11 @@ type chickinService struct {
|
||||
ProjectflockKandangRepo rProjectFlock.ProjectFlockKandangRepository
|
||||
ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository
|
||||
ProjectChickinDetailRepo repository.ProjectChickinDetailRepository
|
||||
FifoSvc commonSvc.FifoService
|
||||
StockLogRepo rStockLogs.StockLogRepository
|
||||
}
|
||||
|
||||
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 {
|
||||
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, fifoSvc commonSvc.FifoService) ChickinService {
|
||||
return &chickinService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
@@ -57,6 +64,8 @@ func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo Kan
|
||||
ProjectflockKandangRepo: projectflockkandangRepo,
|
||||
ProjectflockPopulationRepo: projectflockpopulationRepo,
|
||||
ProjectChickinDetailRepo: projectChickinDetailRepo,
|
||||
FifoSvc: fifoSvc,
|
||||
StockLogRepo: rStockLogs.NewStockLogRepository(repo.DB()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,15 +133,14 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse for Kandang not found")
|
||||
}
|
||||
|
||||
category := strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category))
|
||||
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newChikins := make([]*entity.ProjectChickin, 0)
|
||||
chickinQtyMap := make(map[uint]float64)
|
||||
|
||||
for _, chickinReq := range req.ChickinRequests {
|
||||
for idx, chickinReq := range req.ChickinRequests {
|
||||
|
||||
productWarehouse, err := s.ProductWarehouseRepo.GetByID(c.Context(), chickinReq.ProductWarehouseId, nil)
|
||||
if err != nil {
|
||||
@@ -152,26 +160,23 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid ChickInDate format for product warehouse %d", chickinReq.ProductWarehouseId))
|
||||
}
|
||||
|
||||
availableQty, err := s.calculateAvailableQuantity(c, req.ProjectFlockKandangId, productWarehouse, category)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to calculate available quantity for product warehouse %d", chickinReq.ProductWarehouseId))
|
||||
}
|
||||
|
||||
availableQty := productWarehouse.Quantity
|
||||
if availableQty <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No available quantity for product warehouse %d", chickinReq.ProductWarehouseId))
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No available stock in product warehouse %d for chickin", chickinReq.ProductWarehouseId))
|
||||
}
|
||||
|
||||
newChickin := &entity.ProjectChickin{
|
||||
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
||||
ChickInDate: chickinDate,
|
||||
UsageQty: 0,
|
||||
PendingUsageQty: availableQty,
|
||||
PendingUsageQty: 0,
|
||||
ProductWarehouseId: chickinReq.ProductWarehouseId,
|
||||
Notes: chickinReq.Note,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
newChikins = append(newChikins, newChickin)
|
||||
chickinQtyMap[uint(idx)] = availableQty
|
||||
}
|
||||
|
||||
if len(newChikins) == 0 {
|
||||
@@ -188,30 +193,23 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
|
||||
if err := s.Repository.WithTx(dbTransaction).CreateMany(c.Context(), newChikins, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create chickins")
|
||||
}
|
||||
|
||||
for idx, chickin := range newChikins {
|
||||
desiredQty := chickinQtyMap[uint(idx)]
|
||||
if err := s.ConsumeChickinStocks(c.Context(), dbTransaction, chickin, desiredQty, actorID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
latest, err := approvalSvcTx.LatestByTarget(c.Context(), utils.ApprovalWorkflowChickin, projectFlockKandang.Id, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get latest approval")
|
||||
}
|
||||
|
||||
if category == string(utils.ProjectFlockCategoryLaying) {
|
||||
for _, chickin := range newChikins {
|
||||
updates := map[string]any{"qty": gorm.Expr("qty - ?", 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", chickin.ProductWarehouseId))
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update product warehouse quantity")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var approvalAction entity.ApprovalAction
|
||||
if isFirstTime {
|
||||
approvalAction = entity.ApprovalActionCreated
|
||||
@@ -301,6 +299,32 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
||||
|
||||
func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
|
||||
chickin, err := s.Repository.GetByID(c.Context(), id, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if chickin.UsageQty > 0 {
|
||||
if err := s.ReleaseChickinStocks(c.Context(), s.Repository.DB(), chickin, actorID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
warehouseDeltas := make(map[uint]float64)
|
||||
warehouseDeltas[chickin.ProductWarehouseId] += chickin.UsageQty
|
||||
if err := s.adjustProductWarehouseQuantities(c.Context(), s.Repository.DB(), warehouseDeltas); err != nil {
|
||||
s.Log.Errorf("Failed to adjust product warehouses for deleted chickin %d: %+v", chickin.Id, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
||||
@@ -311,54 +335,6 @@ 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
|
||||
@@ -387,11 +363,10 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
}
|
||||
|
||||
for _, id := range approvableIDs {
|
||||
idCopy := id
|
||||
if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "ProjectFlockKandang", ID: &idCopy, Exists: s.ProjectflockKandangRepo.IdExists}); err != nil {
|
||||
|
||||
if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "ProjectFlockKandang", ID: &id, Exists: s.ProjectflockKandangRepo.IdExists}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowChickin, id, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -414,7 +389,6 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
|
||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
|
||||
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
|
||||
for _, approvableID := range approvableIDs {
|
||||
if _, err := approvalSvc.CreateApproval(
|
||||
@@ -472,9 +446,9 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
}
|
||||
|
||||
pfkID := approvableID
|
||||
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "PULLET", dbTransaction, actorID, &pfkID)
|
||||
targetPW, err := s.getOrCreateProductWarehouse(c, warehouse.Id, "LAYER", dbTransaction, actorID, &pfkID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get/create PULLET product warehouse")
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get/create LAYER product warehouse")
|
||||
}
|
||||
if err := s.convertChickinsToTarget(c, chickins, targetPW, dbTransaction, actorID); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to convert chickins to target")
|
||||
@@ -491,27 +465,17 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
||||
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 fiber.NewError(fiber.StatusInternalServerError, "Failed to get ProjectFlockKandang")
|
||||
}
|
||||
|
||||
categoryForRejection := strings.ToUpper(strings.TrimSpace(kandangForRejection.ProjectFlock.Category))
|
||||
|
||||
for _, chickin := range chickins {
|
||||
|
||||
if categoryForRejection == string(utils.ProjectFlockCategoryGrowing) {
|
||||
updates := map[string]any{"qty": gorm.Expr("qty + ?", chickin.PendingUsageQty)}
|
||||
if err := s.ReleaseChickinStocks(c.Context(), dbTransaction, &chickin, actorID); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to release stock for rejected chickin %d: %v", chickin.Id, err))
|
||||
}
|
||||
|
||||
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 fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to restore product warehouse quantity for chickin %d", chickin.Id))
|
||||
}
|
||||
warehouseDeltas := make(map[uint]float64)
|
||||
warehouseDeltas[chickin.ProductWarehouseId] += chickin.UsageQty
|
||||
if err := s.adjustProductWarehouseQuantities(c.Context(), dbTransaction, warehouseDeltas); err != nil {
|
||||
s.Log.Errorf("Failed to adjust product warehouses for rejected chickin %d: %+v", chickin.Id, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := chickinRepoTx.DeleteOne(c.Context(), chickin.Id); err != nil {
|
||||
@@ -549,7 +513,7 @@ func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId
|
||||
products, err := s.ProductWarehouseRepo.GetByFlagAndWarehouseID(ctx.Context(), categoryCode, warehouseId)
|
||||
if err == nil && len(products) > 0 {
|
||||
existingPW := &products[0]
|
||||
// Update project_flock_kandang_id if not already set
|
||||
|
||||
if existingPW.ProjectFlockKandangId == nil && projectFlockKandangId != nil {
|
||||
existingPW.ProjectFlockKandangId = projectFlockKandangId
|
||||
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).UpdateOne(ctx.Context(), existingPW.Id, existingPW, nil); err != nil {
|
||||
@@ -572,7 +536,6 @@ func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId
|
||||
WarehouseId: warehouseId,
|
||||
ProjectFlockKandangId: projectFlockKandangId,
|
||||
Quantity: 0,
|
||||
// CreatedBy: actorID,
|
||||
}
|
||||
|
||||
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).CreateOne(ctx.Context(), newPW, nil); err != nil {
|
||||
@@ -588,10 +551,10 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti
|
||||
return fmt.Errorf("invalid target product warehouse")
|
||||
}
|
||||
|
||||
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
|
||||
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||
ProjectFlockPopulationRepotx := s.ProjectflockPopulationRepo.WithTx(dbTransaction)
|
||||
|
||||
var totalQuantityAdded float64
|
||||
|
||||
for _, chickin := range chickins {
|
||||
|
||||
populationExists, err := ProjectFlockPopulationRepotx.ExistsByProjectChickinID(ctx.Context(), chickin.Id)
|
||||
@@ -604,34 +567,7 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti
|
||||
continue
|
||||
}
|
||||
|
||||
quantityToConvert := chickin.PendingUsageQty
|
||||
|
||||
if err := chickinRepoTx.PatchOne(ctx.Context(), chickin.Id, map[string]any{
|
||||
"usage_qty": quantityToConvert,
|
||||
"pending_usage_qty": 0,
|
||||
}, nil); err != nil {
|
||||
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{
|
||||
"qty": gorm.Expr("qty - ?", 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)
|
||||
}
|
||||
}
|
||||
|
||||
if err := productWarehouseTx.PatchOne(ctx.Context(), targetPW.Id, map[string]any{
|
||||
"qty": gorm.Expr("qty + ?", quantityToConvert),
|
||||
}, nil); err != nil {
|
||||
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)
|
||||
}
|
||||
quantityToConvert := chickin.UsageQty
|
||||
|
||||
population := &entity.ProjectFlockPopulation{
|
||||
ProjectChickinId: chickin.Id,
|
||||
@@ -644,7 +580,121 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti
|
||||
if err := ProjectFlockPopulationRepotx.CreateOne(ctx.Context(), population, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
totalQuantityAdded += quantityToConvert
|
||||
}
|
||||
|
||||
if totalQuantityAdded > 0 {
|
||||
if err := s.ProductWarehouseRepo.AdjustQuantities(ctx.Context(), map[uint]float64{
|
||||
targetPW.Id: totalQuantityAdded,
|
||||
}, func(db *gorm.DB) *gorm.DB {
|
||||
return dbTransaction
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to update target product warehouse quantity: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, desiredQty float64, actorID uint) error {
|
||||
if chickin == nil || s.FifoSvc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.Log.Infof("ConsumeChickinStocks: chickin_id=%d, product_warehouse_id=%d, desired_qty=%.3f",
|
||||
chickin.Id, chickin.ProductWarehouseId, desiredQty)
|
||||
|
||||
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
||||
UsableKey: chickinUsableKey,
|
||||
UsableID: chickin.Id,
|
||||
ProductWarehouseID: chickin.ProductWarehouseId,
|
||||
Quantity: desiredQty,
|
||||
AllowPending: true,
|
||||
Tx: tx,
|
||||
})
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to consume FIFO stock for chickin %d: %+v", chickin.Id, err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.Log.Infof("ConsumeChickinStocks result: usage_qty=%.3f, pending_qty=%.3f, allocated_allocations=%d",
|
||||
result.UsageQuantity, result.PendingQuantity, len(result.AddedAllocations))
|
||||
|
||||
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Updates(map[string]interface{}{
|
||||
"usage_qty": result.UsageQuantity,
|
||||
"pending_usage_qty": result.PendingQuantity,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.UsageQuantity > 0 {
|
||||
decreaseLog := &entity.StockLog{
|
||||
Decrease: result.UsageQuantity,
|
||||
LoggableType: string(utils.StockLogTypeChikin),
|
||||
LoggableId: chickin.Id,
|
||||
ProductWarehouseId: chickin.ProductWarehouseId,
|
||||
CreatedBy: actorID,
|
||||
Notes: fmt.Sprintf("Chickin #%d", chickin.Id),
|
||||
}
|
||||
if err := s.StockLogRepo.CreateOne(ctx, decreaseLog, nil); err != nil {
|
||||
s.Log.Errorf("Failed to create stock log for chickin %d: %+v", chickin.Id, err)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, actorID uint) error {
|
||||
if chickin == nil || s.FifoSvc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentUsage float64
|
||||
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Select("usage_qty").Scan(¤tUsage).Error; err != nil {
|
||||
s.Log.Warnf("Failed to get current usage for chickin %d: %+v", chickin.Id, err)
|
||||
currentUsage = 0
|
||||
}
|
||||
|
||||
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||
UsableKey: chickinUsableKey,
|
||||
UsableID: chickin.Id,
|
||||
Tx: tx,
|
||||
}); err != nil {
|
||||
s.Log.Errorf("Failed to release FIFO stock for chickin %d: %+v", chickin.Id, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Updates(map[string]interface{}{
|
||||
"usage_qty": 0,
|
||||
"pending_usage_qty": 0,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create stock log for the restoration
|
||||
if currentUsage > 0 {
|
||||
increaseLog := &entity.StockLog{
|
||||
Increase: currentUsage,
|
||||
LoggableType: string(utils.StockLogTypeChikin),
|
||||
LoggableId: chickin.Id,
|
||||
ProductWarehouseId: chickin.ProductWarehouseId,
|
||||
CreatedBy: actorID,
|
||||
Notes: fmt.Sprintf("Chickin #%d - Stock released", chickin.Id),
|
||||
}
|
||||
if err := s.StockLogRepo.CreateOne(ctx, increaseLog, nil); err != nil {
|
||||
s.Log.Errorf("Failed to create stock log for chickin %d: %+v", chickin.Id, err)
|
||||
// Don't return error here, stock already released
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *chickinService) adjustProductWarehouseQuantities(ctx context.Context, tx *gorm.DB, deltas map[uint]float64) error {
|
||||
if len(deltas) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.ProductWarehouseRepo.AdjustQuantities(ctx, deltas, func(*gorm.DB) *gorm.DB { return tx })
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/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"
|
||||
productionStandardDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/dto"
|
||||
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
|
||||
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||
chickinDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/dto"
|
||||
@@ -30,6 +31,7 @@ type ProjectFlockDTO struct {
|
||||
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
|
||||
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
||||
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
@@ -82,6 +84,7 @@ func toProjectFlockDTO(pf *projectFlockDTO.ProjectFlockListDTO) *ProjectFlockDTO
|
||||
Area: pf.Area,
|
||||
Category: pf.Category,
|
||||
Fcr: pf.Fcr,
|
||||
ProductionStandard: pf.ProductionStandard,
|
||||
Location: pf.Location,
|
||||
CreatedUser: pf.CreatedUser,
|
||||
CreatedAt: pf.CreatedAt,
|
||||
|
||||
+3
-9
@@ -555,6 +555,7 @@ func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehous
|
||||
availableQty := productWarehouse.Quantity
|
||||
|
||||
if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryGrowing) {
|
||||
|
||||
var totalPendingQty float64
|
||||
|
||||
for _, chickin := range projectFlockKandang.Chickins {
|
||||
@@ -568,15 +569,8 @@ func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehous
|
||||
availableQty = 0
|
||||
}
|
||||
} else if projectFlockKandang.ProjectFlock.Category == string(utils.ProjectFlockCategoryLaying) {
|
||||
var totalPopulation float64
|
||||
var totalPendingQty float64
|
||||
|
||||
populations, err := s.PopulationRepo.GetByProjectFlockKandangIDAndProductWarehouseID(c.Context(), projectFlockKandang.Id, productWarehouse.Id)
|
||||
if err == nil {
|
||||
for _, pop := range populations {
|
||||
totalPopulation += pop.TotalQty
|
||||
}
|
||||
}
|
||||
var totalPendingQty float64
|
||||
|
||||
for _, chickin := range projectFlockKandang.Chickins {
|
||||
if chickin.ProductWarehouseId == productWarehouse.Id && chickin.DeletedAt.Time.IsZero() && chickin.PendingUsageQty > 0 {
|
||||
@@ -584,7 +578,7 @@ func (s projectFlockKandangService) calculateAvailableQuantityForProductWarehous
|
||||
}
|
||||
}
|
||||
|
||||
availableQty = productWarehouse.Quantity - totalPopulation - totalPendingQty
|
||||
availableQty = productWarehouse.Quantity - totalPendingQty
|
||||
if availableQty < 0 {
|
||||
availableQty = 0
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||
nonstockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/dto"
|
||||
productionStandardDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/dto"
|
||||
|
||||
// pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
@@ -28,6 +29,7 @@ type ProjectFlockListDTO struct {
|
||||
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
|
||||
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
||||
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||
Kandangs []KandangWithProjectFlockIdDTO `json:"kandangs,omitempty"`
|
||||
ProjectBudgets []ProjectBudgetDTO `json:"project_budgets,omitempty"`
|
||||
@@ -103,6 +105,12 @@ func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectF
|
||||
fcrSummary = &mapped
|
||||
}
|
||||
|
||||
var productionStandardSummary *productionStandardDTO.ProductionStandardRelationDTO
|
||||
if e.ProductionStandard.Id != 0 {
|
||||
mapped := productionStandardDTO.ToProductionStandardRelationDTO(e.ProductionStandard)
|
||||
productionStandardSummary = &mapped
|
||||
}
|
||||
|
||||
var locationSummary *locationDTO.LocationRelationDTO
|
||||
if e.Location.Id != 0 {
|
||||
mapped := locationDTO.ToLocationRelationDTO(e.Location)
|
||||
@@ -122,6 +130,7 @@ func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectF
|
||||
ProjectBudgets: ToProjectBudgetDTOs(e.Budgets),
|
||||
Category: e.Category,
|
||||
Fcr: fcrSummary,
|
||||
ProductionStandard: productionStandardSummary,
|
||||
Location: locationSummary,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/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"
|
||||
productionStandardDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/dto"
|
||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||
)
|
||||
|
||||
@@ -19,6 +20,7 @@ type ProjectFlockWithPivotDTO struct {
|
||||
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
|
||||
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
||||
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||
Kandangs []KandangWithPivotDTO `json:"kandangs,omitempty"`
|
||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||
@@ -61,6 +63,10 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
||||
mapped := fcrDTO.ToFcrRelationDTO(e.ProjectFlock.Fcr)
|
||||
pfLocal.Fcr = &mapped
|
||||
}
|
||||
if e.ProjectFlock.ProductionStandard.Id != 0 {
|
||||
mapped := productionStandardDTO.ToProductionStandardRelationDTO(e.ProjectFlock.ProductionStandard)
|
||||
pfLocal.ProductionStandard = &mapped
|
||||
}
|
||||
if e.ProjectFlock.Location.Id != 0 {
|
||||
mapped := locationDTO.ToLocationRelationDTO(e.ProjectFlock.Location)
|
||||
pfLocal.Location = &mapped
|
||||
|
||||
@@ -19,8 +19,10 @@ type ProjectflockRepository interface {
|
||||
GetNextPeriodsForKandangs(ctx context.Context, kandangIDs []uint) (map[uint]int, error)
|
||||
GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error)
|
||||
GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error)
|
||||
GetActiveByLocationID(ctx context.Context, locationID uint64) ([]entity.ProjectFlock, error)
|
||||
AreaExists(ctx context.Context, id uint) (bool, error)
|
||||
FcrExists(ctx context.Context, id uint) (bool, error)
|
||||
ProductionStandardExists(ctx context.Context, id uint) (bool, error)
|
||||
LocationExists(ctx context.Context, id uint) (bool, error)
|
||||
}
|
||||
type KandangPeriodRow struct {
|
||||
@@ -51,6 +53,7 @@ func (r *ProjectflockRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm
|
||||
Preload("CreatedUser").
|
||||
Preload("Area").
|
||||
Preload("Fcr").
|
||||
Preload("ProductionStandard").
|
||||
Preload("Location").
|
||||
Preload("Kandangs").
|
||||
Preload("KandangHistory").
|
||||
@@ -117,12 +120,14 @@ func (r *ProjectflockRepositoryImpl) applySearchFilters(db *gorm.DB, rawSearch s
|
||||
return db.
|
||||
Joins("LEFT JOIN areas ON areas.id = project_flocks.area_id").
|
||||
Joins("LEFT JOIN fcrs ON fcrs.id = project_flocks.fcr_id").
|
||||
Joins("LEFT JOIN production_standards ON production_standards.id = project_flocks.production_standard_id").
|
||||
Joins("LEFT JOIN locations ON locations.id = project_flocks.location_id").
|
||||
Joins("LEFT JOIN users AS created_users ON created_users.id = project_flocks.created_by").
|
||||
Where(`
|
||||
LOWER(areas.name) LIKE ?
|
||||
OR LOWER(project_flocks.category) LIKE ?
|
||||
OR LOWER(fcrs.name) LIKE ?
|
||||
OR LOWER(production_standards.name) LIKE ?
|
||||
OR LOWER(locations.name) LIKE ?
|
||||
OR LOWER(locations.address) LIKE ?
|
||||
OR LOWER(created_users.name) LIKE ?
|
||||
@@ -152,6 +157,7 @@ func (r *ProjectflockRepositoryImpl) applySearchFilters(db *gorm.DB, rawSearch s
|
||||
likeQuery,
|
||||
likeQuery,
|
||||
likeQuery,
|
||||
likeQuery,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -163,6 +169,10 @@ func (r *ProjectflockRepositoryImpl) FcrExists(ctx context.Context, id uint) (bo
|
||||
return repository.Exists[entity.Fcr](ctx, r.DB(), id)
|
||||
}
|
||||
|
||||
func (r *ProjectflockRepositoryImpl) ProductionStandardExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.ProductionStandard](ctx, r.DB(), id)
|
||||
}
|
||||
|
||||
func (r *ProjectflockRepositoryImpl) LocationExists(ctx context.Context, id uint) (bool, error) {
|
||||
return repository.Exists[entity.Location](ctx, r.DB(), id)
|
||||
}
|
||||
@@ -295,3 +305,17 @@ func (r *ProjectflockRepositoryImpl) ExistsByFlockName(ctx context.Context, floc
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (r *ProjectflockRepositoryImpl) GetActiveByLocationID(ctx context.Context, locationID uint64) ([]entity.ProjectFlock, error) {
|
||||
var projectFlocks []entity.ProjectFlock
|
||||
err := r.DB().WithContext(ctx).
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.project_flock_id = project_flocks.id").
|
||||
Where("project_flocks.location_id = ?", locationID).
|
||||
Where("project_flock_kandangs.closed_at IS NULL").
|
||||
Group("project_flocks.id").
|
||||
Find(&projectFlocks).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return projectFlocks, nil
|
||||
}
|
||||
|
||||
@@ -249,6 +249,7 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
if err := commonSvc.EnsureRelations(c.Context(),
|
||||
commonSvc.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: s.Repository.AreaExists},
|
||||
commonSvc.RelationCheck{Name: "FCR", ID: &req.FcrId, Exists: s.Repository.FcrExists},
|
||||
commonSvc.RelationCheck{Name: "Production Standard", ID: &req.ProductionStandardId, Exists: s.Repository.ProductionStandardExists},
|
||||
commonSvc.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: s.Repository.LocationExists},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -300,6 +301,7 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
AreaId: req.AreaId,
|
||||
Category: cat,
|
||||
FcrId: req.FcrId,
|
||||
ProductionStandardId: req.ProductionStandardId,
|
||||
LocationId: req.LocationId,
|
||||
CreatedBy: actorID,
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ type Create struct {
|
||||
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
|
||||
Category string `json:"category" validate:"required_strict"`
|
||||
FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"`
|
||||
ProductionStandardId uint `json:"production_standard_id" validate:"required_strict,number,gt=0"`
|
||||
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
||||
KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"`
|
||||
ProjectBudgets []ProjectBudget `json:"project_budgets" validate:"required,min=1,dive"`
|
||||
|
||||
Reference in New Issue
Block a user