[FEAT/BE] wiring recording,transfer_stock,transfer_laying,marketing for consumer chick in project flock population

This commit is contained in:
ragilap
2026-03-04 12:41:26 +07:00
parent 80135466df
commit d334f46829
17 changed files with 513 additions and 11 deletions
@@ -18,6 +18,7 @@ import (
sChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/services"
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
@@ -36,6 +37,7 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
projectflockkandangrepo := rProjectFlock.NewProjectFlockKandangRepository(db)
projectflockpopulationrepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
transferLayingRepo := rTransferLaying.NewTransferLayingRepository(db)
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
productRepo := rProduct.NewProductRepository(db)
fifoStockV2Service := commonSvc.NewFifoStockV2Service(db, utils.Log)
@@ -57,6 +59,7 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
projectflockkandangrepo,
projectflockpopulationrepo,
chickinDetailRepo,
transferLayingRepo,
validate,
fifoStockV2Service)
userService := sUser.NewUserService(userRepo, validate)
@@ -19,6 +19,7 @@ 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"
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/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"
@@ -51,11 +52,12 @@ type chickinService struct {
ProjectflockKandangRepo rProjectFlock.ProjectFlockKandangRepository
ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository
ProjectChickinDetailRepo repository.ProjectChickinDetailRepository
TransferLayingRepo rTransferLaying.TransferLayingRepository
FifoStockV2Svc commonSvc.FifoStockV2Service
StockLogRepo rStockLogs.StockLogRepository
}
func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, productRepo rProduct.ProductRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, projectflockkandangRepo rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, validate *validator.Validate, fifoStockV2Svc commonSvc.FifoStockV2Service) ChickinService {
func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, productRepo rProduct.ProductRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, projectflockkandangRepo rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, transferLayingRepo rTransferLaying.TransferLayingRepository, validate *validator.Validate, fifoStockV2Svc commonSvc.FifoStockV2Service) ChickinService {
return &chickinService{
Log: utils.Log,
Validate: validate,
@@ -68,6 +70,7 @@ func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo Kan
ProjectflockKandangRepo: projectflockkandangRepo,
ProjectflockPopulationRepo: projectflockpopulationRepo,
ProjectChickinDetailRepo: projectChickinDetailRepo,
TransferLayingRepo: transferLayingRepo,
FifoStockV2Svc: fifoStockV2Svc,
StockLogRepo: rStockLogs.NewStockLogRepository(repo.DB()),
}
@@ -120,11 +123,36 @@ func (s chickinService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectChickin, e
return chickin, nil
}
func (s chickinService) ensureNotTransferred(ctx context.Context, projectFlockKandangID uint) error {
if projectFlockKandangID == 0 || s.TransferLayingRepo == nil {
return nil
}
transfer, err := s.TransferLayingRepo.GetLatestApprovedBySourceKandang(ctx, projectFlockKandangID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
s.Log.Errorf("Failed to resolve transfer laying by source kandang %d: %+v", projectFlockKandangID, err)
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transfer laying")
}
if transfer != nil && transfer.ExecutedAt != nil && !transfer.ExecutedAt.IsZero() {
return fiber.NewError(fiber.StatusBadRequest, "Project flock kandang sudah dipindahkan ke laying")
}
return 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
}
if err := s.ensureNotTransferred(c.Context(), req.ProjectFlockKandangId); err != nil {
return nil, err
}
projectFlockKandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), req.ProjectFlockKandangId)
if err != nil {
return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock Kandang not found")
@@ -334,6 +362,17 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
return nil, err
}
chickin, err := s.Repository.GetByID(c.Context(), id, nil)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "Chickin not found")
}
return nil, err
}
if err := s.ensureNotTransferred(c.Context(), chickin.ProjectFlockKandangId); err != nil {
return nil, err
}
updateBody := make(map[string]any)
if req.ChickInDate != "" {
@@ -377,6 +416,10 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
return err
}
if err := s.ensureNotTransferred(c.Context(), chickin.ProjectFlockKandangId); err != nil {
return err
}
actorID, err := m.ActorIDFromContext(c)
if err != nil {
return err
@@ -446,6 +489,9 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "ProjectFlockKandang", ID: &id, Exists: s.ProjectflockKandangRepo.IdExists}); err != nil {
return nil, err
}
if err := s.ensureNotTransferred(c.Context(), id); err != nil {
return nil, err
}
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowChickin, id, nil)
if err != nil {
@@ -98,6 +98,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
projectFlockKandangRepo,
projectFlockPopulationRepo,
chickinDetailRepo,
transferLayingRepo,
validate,
fifoStockV2Service,
)
@@ -10,6 +10,7 @@ import (
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
fifoV2 "gitlab.com/mbugroup/lti-api.git/internal/common/service/fifo_stock_v2"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
@@ -23,6 +24,7 @@ import (
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
fifo "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
recordingutil "gitlab.com/mbugroup/lti-api.git/internal/utils/recording"
"github.com/go-playground/validator/v10"
@@ -416,6 +418,10 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
if err := s.reflowApplyRecordingDepletionsIn(ctx, tx, mappedDepletions); err != nil {
return err
}
if err := s.Repository.ResyncProjectFlockPopulationUsage(ctx, tx, createdRecording.ProjectFlockKandangId); err != nil {
s.Log.Errorf("Failed to resync project flock population usage: %+v", err)
return err
}
mappedEggs := recordingutil.MapEggs(createdRecording.Id, createdRecording.CreatedBy, req.Eggs)
if err := s.Repository.CreateEggs(tx, mappedEggs); err != nil {
@@ -951,6 +957,13 @@ func (s *recordingService) enforceTransferRecordingRoute(
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transfer laying")
}
if transfer != nil && transfer.ExecutedAt != nil && !transfer.ExecutedAt.IsZero() {
return fiber.NewError(
fiber.StatusBadRequest,
"Project flock kandang sudah dipindahkan ke laying",
)
}
effectiveDate := effectiveTransferDate(transfer)
if effectiveDate.IsZero() {
return nil
@@ -1835,6 +1848,14 @@ func (s *recordingService) reflowApplyRecordingDepletionsOut(
}
s.logDepletionTrace("reflow_apply:done", *refreshed, fmt.Sprintf("desired=%.3f used=%.3f pending=%.3f", desired, refreshed.UsageQty, refreshed.PendingQty))
consumeQty := refreshed.UsageQty
if refreshed.PendingQty > 0 {
consumeQty += refreshed.PendingQty
}
if err := s.allocatePopulationForDepletion(ctx, tx, *refreshed, consumeQty); err != nil {
return err
}
logDecrease := refreshed.UsageQty
if refreshed.PendingQty > 0 {
logDecrease += refreshed.PendingQty
@@ -1894,11 +1915,15 @@ func (s *recordingService) reflowResetRecordingDepletionsOut(
return errors.New("stock log repository is not available")
}
logState := newRecordingStockLogState()
stockAllocationRepo := commonRepo.NewStockAllocationRepository(tx)
for _, depletion := range depletions {
if depletion.Id == 0 {
continue
}
if err := stockAllocationRepo.ReleaseByUsable(ctx, fifo.UsableKeyRecordingDepletion.String(), depletion.Id, nil, nil); err != nil {
return err
}
s.logDepletionTrace("reflow_reset:start", depletion, "")
sourceWarehouseID := uint(0)
@@ -1979,6 +2004,58 @@ func (s *recordingService) reflowResetRecordingDepletionsOut(
return nil
}
func (s *recordingService) allocatePopulationForDepletion(
ctx context.Context,
tx *gorm.DB,
depletion entity.RecordingDepletion,
consumeQty float64,
) error {
if consumeQty <= 0 {
return nil
}
if tx == nil {
return errors.New("transaction is required")
}
sourceWarehouseID := uint(0)
if depletion.SourceProductWarehouseId != nil {
sourceWarehouseID = *depletion.SourceProductWarehouseId
}
if sourceWarehouseID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Source product warehouse populasi tidak ditemukan")
}
var projectFlockKandangID uint
if err := tx.WithContext(ctx).
Table("recordings").
Select("project_flock_kandangs_id").
Where("id = ?", depletion.RecordingId).
Scan(&projectFlockKandangID).Error; err != nil {
return err
}
if projectFlockKandangID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Project flock kandang tidak ditemukan untuk depletion")
}
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangIDAndProductWarehouseID(ctx, projectFlockKandangID, sourceWarehouseID)
if err != nil {
return err
}
if len(populations) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Populasi tidak ditemukan untuk depletion")
}
return fifoV2.AllocatePopulationConsumption(
ctx,
tx,
populations,
sourceWarehouseID,
fifo.UsableKeyRecordingDepletion.String(),
depletion.Id,
consumeQty,
)
}
func (s *recordingService) reflowApplyRecordingDepletionsIn(
ctx context.Context,
tx *gorm.DB,
@@ -10,6 +10,7 @@ import (
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
fifoV2 "gitlab.com/mbugroup/lti-api.git/internal/common/service/fifo_stock_v2"
"gitlab.com/mbugroup/lti-api.git/internal/config"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
@@ -1005,6 +1006,9 @@ func (s *transferLayingService) executeApprovedTransferMovement(
}
movedQty := usageDelta
if err := s.allocatePopulationForTransfer(ctx, tx, source, movedQty); err != nil {
return err
}
targetShares := distributeProportionalWithRounding(targets, totalTargetQty, movedQty)
for i, target := range targets {
roundedQty := math.Round(targetShares[i])
@@ -1104,6 +1108,45 @@ func (s *transferLayingService) executeApprovedTransferMovement(
return nil
}
func (s *transferLayingService) allocatePopulationForTransfer(
ctx context.Context,
tx *gorm.DB,
source entity.LayingTransferSource,
consumeQty float64,
) error {
if consumeQty <= 0 {
return nil
}
if tx == nil {
return errors.New("transaction is required")
}
if source.SourceProjectFlockKandangId == 0 || source.ProductWarehouseId == nil || *source.ProductWarehouseId == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Project flock kandang sumber atau product warehouse tidak valid")
}
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangIDAndProductWarehouseID(
ctx,
source.SourceProjectFlockKandangId,
*source.ProductWarehouseId,
)
if err != nil {
return err
}
if len(populations) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Populasi tidak ditemukan untuk transfer laying")
}
return fifoV2.AllocatePopulationConsumption(
ctx,
tx,
populations,
*source.ProductWarehouseId,
fifo.UsableKeyTransferToLayingOut.String(),
source.Id,
consumeQty,
)
}
func (s *transferLayingService) calculateEffectiveMoveDate(ctx context.Context, sources []entity.LayingTransferSource) (time.Time, error) {
if len(sources) == 0 {
return time.Time{}, fiber.NewError(fiber.StatusBadRequest, "Sumber transfer laying tidak ditemukan")