Merge branch 'fix/BE/Purchase-edit-qty' into 'development'

[FIX/BE-US] adjustment recording and purchase stock log

See merge request mbugroup/lti-api!229
This commit is contained in:
Hafizh A. Y.
2026-01-22 07:41:50 +00:00
5 changed files with 285 additions and 25 deletions
@@ -83,7 +83,7 @@ func (r *ConstantRepositoryImpl) GetConstants() map[string]interface{} {
"KANDANG", "KANDANG",
}, },
"stock_log": map[string][]string{ "stock_log": map[string][]string{
"log_types": []string{"TRANSFER", "ADJUSTMENT"}, "log_types": []string{"TRANSFER", "ADJUSTMENT", "MARKETING", "CHICKIN", "PURCHASE", "RECORDING"},
"transaction_types": []string{"INCREASE", "DECREASE"}, "transaction_types": []string{"INCREASE", "DECREASE"},
}, },
"supplier_categories": []string{ "supplier_categories": []string{
@@ -16,6 +16,7 @@ import (
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services" sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
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"
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
@@ -31,6 +32,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db) projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db) stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
stockLogRepo := rStockLogs.NewStockLogRepository(db)
productionStandardRepo := rProductionStandard.NewProductionStandardRepository(db) productionStandardRepo := rProductionStandard.NewProductionStandardRepository(db)
productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db) productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
standardGrowthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db) standardGrowthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
@@ -113,6 +115,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
approvalRepo, approvalRepo,
approvalService, approvalService,
fifoService, fifoService,
stockLogRepo,
productionStandardService, productionStandardService,
validate, validate,
) )
@@ -13,6 +13,7 @@ import (
sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services" sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services"
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
"gitlab.com/mbugroup/lti-api.git/internal/utils" "gitlab.com/mbugroup/lti-api.git/internal/utils"
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
@@ -39,8 +40,8 @@ type RecordingService interface {
} }
type RecordingFIFOIntegrationService interface { type RecordingFIFOIntegrationService interface {
ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock, note string, actorID uint) error
ReleaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error ReleaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock, note string, actorID uint) error
} }
var recordingStockUsableKey = fifo.UsableKeyRecordingStock var recordingStockUsableKey = fifo.UsableKeyRecordingStock
@@ -57,6 +58,7 @@ type recordingService struct {
ApprovalSvc commonSvc.ApprovalService ApprovalSvc commonSvc.ApprovalService
ProductionStandardSvc sProductionStandard.ProductionStandardService ProductionStandardSvc sProductionStandard.ProductionStandardService
FifoSvc commonSvc.FifoService FifoSvc commonSvc.FifoService
StockLogRepo rStockLogs.StockLogRepository
} }
func NewRecordingService( func NewRecordingService(
@@ -67,6 +69,7 @@ func NewRecordingService(
approvalRepo commonRepo.ApprovalRepository, approvalRepo commonRepo.ApprovalRepository,
approvalSvc commonSvc.ApprovalService, approvalSvc commonSvc.ApprovalService,
fifoSvc commonSvc.FifoService, fifoSvc commonSvc.FifoService,
stockLogRepo rStockLogs.StockLogRepository,
productionStandardSvc sProductionStandard.ProductionStandardService, productionStandardSvc sProductionStandard.ProductionStandardService,
validate *validator.Validate, validate *validator.Validate,
) RecordingService { ) RecordingService {
@@ -81,6 +84,7 @@ func NewRecordingService(
ApprovalSvc: approvalSvc, ApprovalSvc: approvalSvc,
ProductionStandardSvc: productionStandardSvc, ProductionStandardSvc: productionStandardSvc,
FifoSvc: fifoSvc, FifoSvc: fifoSvc,
StockLogRepo: stockLogRepo,
} }
} }
@@ -88,12 +92,14 @@ func NewRecordingFIFOIntegrationService(
repo repository.RecordingRepository, repo repository.RecordingRepository,
productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository,
fifoSvc commonSvc.FifoService, fifoSvc commonSvc.FifoService,
stockLogRepo rStockLogs.StockLogRepository,
) RecordingFIFOIntegrationService { ) RecordingFIFOIntegrationService {
return &recordingService{ return &recordingService{
Log: utils.Log, Log: utils.Log,
Repository: repo, Repository: repo,
ProductWarehouseRepo: productWarehouseRepo, ProductWarehouseRepo: productWarehouseRepo,
FifoSvc: fifoSvc, FifoSvc: fifoSvc,
StockLogRepo: stockLogRepo,
} }
} }
@@ -274,7 +280,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
} }
applyStockDesiredQuantities(mappedStocks, stockDesired, s.FifoSvc != nil) applyStockDesiredQuantities(mappedStocks, stockDesired, s.FifoSvc != nil)
if err := s.consumeRecordingStocks(ctx, tx, mappedStocks); err != nil { note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id)
if err := s.consumeRecordingStocks(ctx, tx, mappedStocks, note, actorID); err != nil {
return err return err
} }
@@ -293,7 +300,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
return err return err
} }
if s.FifoSvc != nil { if s.FifoSvc != nil {
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions); err != nil { note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id)
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil {
return err return err
} }
} }
@@ -304,7 +312,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
return err return err
} }
if s.FifoSvc != nil { if s.FifoSvc != nil {
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs); err != nil { note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id)
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs, note, actorID); err != nil {
return err return err
} }
} }
@@ -346,6 +355,10 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
} }
ctx := c.Context() ctx := c.Context()
actorID, err := m.ActorIDFromContext(c)
if err != nil {
return nil, err
}
var recordingEntity *entity.Recording var recordingEntity *entity.Recording
var updatedRecording *entity.Recording var updatedRecording *entity.Recording
@@ -431,14 +444,16 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
} }
if hasStockChanges { if hasStockChanges {
if err := s.syncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks); err != nil { note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
if err := s.syncRecordingStocks(ctx, tx, recordingEntity.Id, existingStocks, req.Stocks, note, actorID); err != nil {
return err return err
} }
} }
if hasDepletionChanges { if hasDepletionChanges {
if s.FifoSvc != nil { if s.FifoSvc != nil {
if err := s.releaseRecordingDepletions(ctx, tx, existingDepletions); err != nil { note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
if err := s.releaseRecordingDepletions(ctx, tx, existingDepletions, note, actorID); err != nil {
return err return err
} }
} }
@@ -464,7 +479,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
} }
if s.FifoSvc != nil { if s.FifoSvc != nil {
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions); err != nil { note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil {
return err return err
} }
} }
@@ -480,6 +496,28 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
if err := ensureRecordingEggsUnused(existingEggs); err != nil { if err := ensureRecordingEggsUnused(existingEggs); err != nil {
return err return err
} }
if s.StockLogRepo != nil {
note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
logs := make([]*entity.StockLog, 0, len(existingEggs))
for _, egg := range existingEggs {
if egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
continue
}
logs = append(logs, &entity.StockLog{
ProductWarehouseId: egg.ProductWarehouseId,
CreatedBy: actorID,
Decrease: float64(egg.Qty),
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: recordingEntity.Id,
Notes: note,
})
}
if len(logs) > 0 {
if err := s.StockLogRepo.WithTx(tx).CreateMany(ctx, logs, nil); err != nil {
return err
}
}
}
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingEggs, nil)); err != nil { if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingEggs, nil)); err != nil {
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err) s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
return err return err
@@ -498,7 +536,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
} }
if s.FifoSvc != nil { if s.FifoSvc != nil {
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs); err != nil { note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
if err := s.replenishRecordingEggs(ctx, tx, mappedEggs, note, actorID); err != nil {
return err return err
} }
} else { } else {
@@ -675,7 +714,7 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
return err return err
} }
if s.FifoSvc != nil { if s.FifoSvc != nil {
if err := s.releaseRecordingDepletions(ctx, tx, oldDepletions); err != nil { if err := s.releaseRecordingDepletions(ctx, tx, oldDepletions, "", 0); err != nil {
return err return err
} }
} }
@@ -697,7 +736,7 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
return err return err
} }
if err := s.releaseRecordingStocks(ctx, tx, oldStocks); err != nil { if err := s.releaseRecordingStocks(ctx, tx, oldStocks, "", 0); err != nil {
return err return err
} }
@@ -756,10 +795,19 @@ func (s *recordingService) ensureProductWarehousesExist(c *fiber.Ctx, stocks []v
return nil return nil
} }
func (s *recordingService) consumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error { func (s *recordingService) consumeRecordingStocks(
ctx context.Context,
tx *gorm.DB,
stocks []entity.RecordingStock,
note string,
actorID uint,
) error {
if len(stocks) == 0 || s.FifoSvc == nil { if len(stocks) == 0 || s.FifoSvc == nil {
return nil return nil
} }
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
}
for _, stock := range stocks { for _, stock := range stocks {
if stock.Id == 0 { if stock.Id == 0 {
@@ -792,15 +840,42 @@ func (s *recordingService) consumeRecordingStocks(ctx context.Context, tx *gorm.
if err := s.Repository.UpdateStockUsage(tx, stock.Id, result.UsageQuantity, result.PendingQuantity); err != nil { if err := s.Repository.UpdateStockUsage(tx, stock.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
return err return err
} }
logDecrease := result.UsageQuantity
if result.PendingQuantity > 0 {
logDecrease += result.PendingQuantity
}
if logDecrease > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: stock.ProductWarehouseId,
CreatedBy: actorID,
Decrease: logDecrease,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: stock.RecordingId,
Notes: note,
}
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
return err
}
}
} }
return nil return nil
} }
func (s *recordingService) consumeRecordingDepletions(ctx context.Context, tx *gorm.DB, depletions []entity.RecordingDepletion) error { func (s *recordingService) consumeRecordingDepletions(
ctx context.Context,
tx *gorm.DB,
depletions []entity.RecordingDepletion,
note string,
actorID uint,
) error {
if len(depletions) == 0 || s.FifoSvc == nil { if len(depletions) == 0 || s.FifoSvc == nil {
return nil return nil
} }
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
}
for _, depletion := range depletions { for _, depletion := range depletions {
if depletion.Id == 0 { if depletion.Id == 0 {
@@ -832,19 +907,67 @@ func (s *recordingService) consumeRecordingDepletions(ctx context.Context, tx *g
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, result.PendingQuantity); err != nil { if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, result.PendingQuantity); err != nil {
return err return err
} }
logDecrease := result.UsageQuantity
if result.PendingQuantity > 0 {
logDecrease += result.PendingQuantity
}
if logDecrease > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: sourceWarehouseID,
CreatedBy: actorID,
Decrease: logDecrease,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: depletion.RecordingId,
Notes: note,
}
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
return err
}
}
destDelta := depletion.Qty + depletion.PendingQty
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: depletion.ProductWarehouseId,
CreatedBy: actorID,
Increase: destDelta,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: depletion.RecordingId,
Notes: note,
}
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
return err
}
}
} }
return nil return nil
} }
func (s *recordingService) ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error { func (s *recordingService) ConsumeRecordingStocks(
return s.consumeRecordingStocks(ctx, tx, stocks) ctx context.Context,
tx *gorm.DB,
stocks []entity.RecordingStock,
note string,
actorID uint,
) error {
return s.consumeRecordingStocks(ctx, tx, stocks, note, actorID)
} }
func (s *recordingService) releaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error { func (s *recordingService) releaseRecordingStocks(
ctx context.Context,
tx *gorm.DB,
stocks []entity.RecordingStock,
note string,
actorID uint,
) error {
if len(stocks) == 0 || s.FifoSvc == nil { if len(stocks) == 0 || s.FifoSvc == nil {
return nil return nil
} }
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
}
for _, stock := range stocks { for _, stock := range stocks {
if stock.Id == 0 { if stock.Id == 0 {
@@ -863,15 +986,38 @@ func (s *recordingService) releaseRecordingStocks(ctx context.Context, tx *gorm.
if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil { if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil {
return err return err
} }
if stock.UsageQty != nil && *stock.UsageQty > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: stock.ProductWarehouseId,
CreatedBy: actorID,
Increase: *stock.UsageQty,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: stock.RecordingId,
Notes: note,
}
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
return err
}
}
} }
return nil return nil
} }
func (s *recordingService) releaseRecordingDepletions(ctx context.Context, tx *gorm.DB, depletions []entity.RecordingDepletion) error { func (s *recordingService) releaseRecordingDepletions(
ctx context.Context,
tx *gorm.DB,
depletions []entity.RecordingDepletion,
note string,
actorID uint,
) error {
if len(depletions) == 0 || s.FifoSvc == nil { if len(depletions) == 0 || s.FifoSvc == nil {
return nil return nil
} }
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
}
for _, depletion := range depletions { for _, depletion := range depletions {
if depletion.Id == 0 { if depletion.Id == 0 {
@@ -898,13 +1044,52 @@ func (s *recordingService) releaseRecordingDepletions(ctx context.Context, tx *g
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, 0); err != nil { if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, 0); err != nil {
return err return err
} }
logIncrease := depletion.Qty
if depletion.PendingQty > 0 {
logIncrease += depletion.PendingQty
}
if logIncrease > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: sourceWarehouseID,
CreatedBy: actorID,
Increase: logIncrease,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: depletion.RecordingId,
Notes: note,
}
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
return err
}
}
destDelta := depletion.Qty + depletion.PendingQty
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: depletion.ProductWarehouseId,
CreatedBy: actorID,
Decrease: destDelta,
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: depletion.RecordingId,
Notes: note,
}
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
return err
}
}
} }
return nil return nil
} }
func (s *recordingService) ReleaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error { func (s *recordingService) ReleaseRecordingStocks(
return s.releaseRecordingStocks(ctx, tx, stocks) ctx context.Context,
tx *gorm.DB,
stocks []entity.RecordingStock,
note string,
actorID uint,
) error {
return s.releaseRecordingStocks(ctx, tx, stocks, note, actorID)
} }
func (s *recordingService) resolvePopulationWarehouseID(ctx context.Context, projectFlockKandangID uint) (uint, error) { func (s *recordingService) resolvePopulationWarehouseID(ctx context.Context, projectFlockKandangID uint) (uint, error) {
@@ -963,27 +1148,48 @@ func (s *recordingService) adjustProductWarehouseQuantities(ctx context.Context,
return s.ProductWarehouseRepo.AdjustQuantities(ctx, deltas, func(*gorm.DB) *gorm.DB { return tx }) return s.ProductWarehouseRepo.AdjustQuantities(ctx, deltas, func(*gorm.DB) *gorm.DB { return tx })
} }
func (s *recordingService) replenishRecordingEggs(ctx context.Context, tx *gorm.DB, eggs []entity.RecordingEgg) error { func (s *recordingService) replenishRecordingEggs(
ctx context.Context,
tx *gorm.DB,
eggs []entity.RecordingEgg,
note string,
actorID uint,
) error {
if len(eggs) == 0 || s.FifoSvc == nil { if len(eggs) == 0 || s.FifoSvc == nil {
return nil return nil
} }
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
return errors.New("stock log repository is not available")
}
for _, egg := range eggs { for _, egg := range eggs {
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 { if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
continue continue
} }
note := fmt.Sprintf("Recording egg #%d", egg.Id)
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{ if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyRecordingEgg, StockableKey: fifo.StockableKeyRecordingEgg,
StockableID: egg.Id, StockableID: egg.Id,
ProductWarehouseID: egg.ProductWarehouseId, ProductWarehouseID: egg.ProductWarehouseId,
Quantity: float64(egg.Qty), Quantity: float64(egg.Qty),
Note: &note,
Tx: tx, Tx: tx,
}); err != nil { }); err != nil {
s.Log.Errorf("Failed to replenish FIFO stock for recording egg %d: %+v", egg.Id, err) s.Log.Errorf("Failed to replenish FIFO stock for recording egg %d: %+v", egg.Id, err)
return err return err
} }
if strings.TrimSpace(note) != "" && actorID != 0 {
log := &entity.StockLog{
ProductWarehouseId: egg.ProductWarehouseId,
CreatedBy: actorID,
Increase: float64(egg.Qty),
LoggableType: string(utils.StockLogTypeRecording),
LoggableId: egg.RecordingId,
Notes: note,
}
if err := s.StockLogRepo.WithTx(tx).CreateOne(ctx, log, nil); err != nil {
return err
}
}
} }
return nil return nil
@@ -1034,6 +1240,8 @@ func (s *recordingService) syncRecordingStocks(
recordingID uint, recordingID uint,
existing []entity.RecordingStock, existing []entity.RecordingStock,
incoming []validation.Stock, incoming []validation.Stock,
note string,
actorID uint,
) error { ) error {
if s.FifoSvc == nil { if s.FifoSvc == nil {
if err := s.Repository.DeleteStocks(tx, recordingID); err != nil { if err := s.Repository.DeleteStocks(tx, recordingID); err != nil {
@@ -1080,7 +1288,7 @@ func (s *recordingService) syncRecordingStocks(
leftovers = append(leftovers, list...) leftovers = append(leftovers, list...)
} }
if len(leftovers) > 0 { if len(leftovers) > 0 {
if err := s.releaseRecordingStocks(ctx, tx, leftovers); err != nil { if err := s.releaseRecordingStocks(ctx, tx, leftovers, note, actorID); err != nil {
return err return err
} }
ids := make([]uint, 0, len(leftovers)) ids := make([]uint, 0, len(leftovers))
@@ -1099,7 +1307,7 @@ func (s *recordingService) syncRecordingStocks(
if len(stocksToConsume) == 0 { if len(stocksToConsume) == 0 {
return nil return nil
} }
return s.consumeRecordingStocks(ctx, tx, stocksToConsume) return s.consumeRecordingStocks(ctx, tx, stocksToConsume, note, actorID)
} }
type eggTotals struct { type eggTotals struct {
@@ -18,6 +18,7 @@ import (
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories" rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
projectFlockKandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" projectFlockKandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
rPurchase "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories" rPurchase "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/validations" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/validations"
"gitlab.com/mbugroup/lti-api.git/internal/utils" "gitlab.com/mbugroup/lti-api.git/internal/utils"
@@ -830,9 +831,16 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
receivingAction = entity.ApprovalActionUpdated receivingAction = entity.ApprovalActionUpdated
} }
} }
noteSuffix := "receive"
if receivingAction == entity.ApprovalActionUpdated {
noteSuffix = "edit-receive"
}
receiveNote := fmt.Sprintf("%s#%s", strings.TrimSpace(*purchase.PoNumber), noteSuffix)
transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error { transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
repoTx := rPurchase.NewPurchaseRepository(tx) repoTx := rPurchase.NewPurchaseRepository(tx)
pwRepoTx := rProductWarehouse.NewProductWarehouseRepository(tx) pwRepoTx := rProductWarehouse.NewProductWarehouseRepository(tx)
stockLogRepoTx := rStockLogs.NewStockLogRepository(tx)
deltas := make(map[uint]float64) deltas := make(map[uint]float64)
affected := make(map[uint]struct{}) affected := make(map[uint]struct{})
@@ -849,6 +857,11 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
pwID uint pwID uint
qty float64 qty float64
}, 0, len(prepared)) }, 0, len(prepared))
logEntries := make([]struct {
itemID uint
pwID uint
delta float64
}, 0, len(prepared))
for _, prep := range prepared { for _, prep := range prepared {
item := prep.item item := prep.item
@@ -869,6 +882,13 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
newPWID = &pwID newPWID = &pwID
deltaQty := prep.receivedQty - item.TotalQty deltaQty := prep.receivedQty - item.TotalQty
if newPWID != nil && deltaQty != 0 {
logEntries = append(logEntries, struct {
itemID uint
pwID uint
delta float64
}{itemID: item.Id, pwID: *newPWID, delta: deltaQty})
}
switch { switch {
case deltaQty > 0 && newPWID != nil: case deltaQty > 0 && newPWID != nil:
if s.FifoSvc != nil { if s.FifoSvc != nil {
@@ -993,6 +1013,33 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
} }
} }
if len(logEntries) > 0 {
logs := make([]*entity.StockLog, 0, len(logEntries))
for _, entry := range logEntries {
if entry.pwID == 0 || entry.delta == 0 {
continue
}
log := &entity.StockLog{
ProductWarehouseId: entry.pwID,
CreatedBy: actorID,
LoggableType: string(utils.StockLogTypePurchase),
LoggableId: purchase.Id,
Notes: receiveNote,
}
if entry.delta > 0 {
log.Increase = entry.delta
} else {
log.Decrease = -entry.delta
}
logs = append(logs, log)
}
if len(logs) > 0 {
if err := stockLogRepoTx.CreateMany(c.Context(), logs, nil); err != nil {
return err
}
}
}
if len(affected) > 0 { if len(affected) > 0 {
if err := pwRepoTx.CleanupEmpty(c.Context(), affected); err != nil { if err := pwRepoTx.CleanupEmpty(c.Context(), affected); err != nil {
return err return err
+2
View File
@@ -113,6 +113,8 @@ const (
StockLogTypeTransfer StockLogType = "TRANSFER" StockLogTypeTransfer StockLogType = "TRANSFER"
StockLogTypeMarketing StockLogType = "MARKETING" StockLogTypeMarketing StockLogType = "MARKETING"
StockLogTypeChikin StockLogType = "CHICKIN" StockLogTypeChikin StockLogType = "CHICKIN"
StockLogTypePurchase StockLogType = "PURCHASE"
StockLogTypeRecording StockLogType = "RECORDING"
) )
// ------------------------------------------------------------------- // -------------------------------------------------------------------