mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-23 23:05:44 +00:00
Merge branch 'development' of https://gitlab.com/mbugroup/lti-api into fix/BE/UUIT-Recording-closing-report-uniformity-dashboard
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
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"
|
||||
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/fifo"
|
||||
|
||||
@@ -31,6 +32,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
|
||||
stockLogRepo := rStockLogs.NewStockLogRepository(db)
|
||||
productionStandardRepo := rProductionStandard.NewProductionStandardRepository(db)
|
||||
productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
|
||||
standardGrowthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
|
||||
@@ -113,6 +115,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
approvalRepo,
|
||||
approvalService,
|
||||
fifoService,
|
||||
stockLogRepo,
|
||||
productionStandardService,
|
||||
validate,
|
||||
)
|
||||
|
||||
@@ -171,6 +171,7 @@ func (r *RecordingRepositoryImpl) GenerateNextDay(tx *gorm.DB, projectFlockKanda
|
||||
var days []int
|
||||
if err := tx.Model(&entity.Recording{}).
|
||||
Where("project_flock_kandangs_id = ?", projectFlockKandangId).
|
||||
Where("deleted_at IS NULL").
|
||||
Where("day IS NOT NULL").
|
||||
Pluck("day", &days).Error; err != nil {
|
||||
return 0, err
|
||||
@@ -399,7 +400,7 @@ func (r *RecordingRepositoryImpl) GetEggSummaryByRecording(tx *gorm.DB, recordin
|
||||
}
|
||||
err = tx.
|
||||
Table("recording_eggs").
|
||||
Select("COALESCE(SUM(recording_eggs.qty), 0) AS total_qty, COALESCE(SUM(recording_eggs.qty * COALESCE(recording_eggs.weight, 0)), 0) AS total_weight_grams").
|
||||
Select("COALESCE(SUM(recording_eggs.qty), 0) AS total_qty, COALESCE(SUM(COALESCE(recording_eggs.weight, 0) * 1000), 0) AS total_weight_grams").
|
||||
Where("recording_eggs.recording_id = ?", recordingID).
|
||||
Scan(&result).Error
|
||||
if err != nil {
|
||||
@@ -485,7 +486,7 @@ func (r *RecordingRepositoryImpl) GetTotalEggProductionWeightByProjectFlockID(ct
|
||||
var result float64
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("recording_eggs").
|
||||
Select("COALESCE(SUM(recording_eggs.qty * recording_eggs.weight), 0) / 1000").
|
||||
Select("COALESCE(SUM(recording_eggs.weight), 0)").
|
||||
Joins("JOIN recordings ON recordings.id = recording_eggs.recording_id").
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = recordings.project_flock_kandangs_id").
|
||||
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
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"
|
||||
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"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||
@@ -39,8 +40,8 @@ type RecordingService interface {
|
||||
}
|
||||
|
||||
type RecordingFIFOIntegrationService interface {
|
||||
ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error
|
||||
ReleaseRecordingStocks(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, note string, actorID uint) error
|
||||
}
|
||||
|
||||
var recordingStockUsableKey = fifo.UsableKeyRecordingStock
|
||||
@@ -57,6 +58,7 @@ type recordingService struct {
|
||||
ApprovalSvc commonSvc.ApprovalService
|
||||
ProductionStandardSvc sProductionStandard.ProductionStandardService
|
||||
FifoSvc commonSvc.FifoService
|
||||
StockLogRepo rStockLogs.StockLogRepository
|
||||
}
|
||||
|
||||
func NewRecordingService(
|
||||
@@ -67,6 +69,7 @@ func NewRecordingService(
|
||||
approvalRepo commonRepo.ApprovalRepository,
|
||||
approvalSvc commonSvc.ApprovalService,
|
||||
fifoSvc commonSvc.FifoService,
|
||||
stockLogRepo rStockLogs.StockLogRepository,
|
||||
productionStandardSvc sProductionStandard.ProductionStandardService,
|
||||
validate *validator.Validate,
|
||||
) RecordingService {
|
||||
@@ -81,6 +84,7 @@ func NewRecordingService(
|
||||
ApprovalSvc: approvalSvc,
|
||||
ProductionStandardSvc: productionStandardSvc,
|
||||
FifoSvc: fifoSvc,
|
||||
StockLogRepo: stockLogRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,12 +92,14 @@ func NewRecordingFIFOIntegrationService(
|
||||
repo repository.RecordingRepository,
|
||||
productWarehouseRepo rProductWarehouse.ProductWarehouseRepository,
|
||||
fifoSvc commonSvc.FifoService,
|
||||
stockLogRepo rStockLogs.StockLogRepository,
|
||||
) RecordingFIFOIntegrationService {
|
||||
return &recordingService{
|
||||
Log: utils.Log,
|
||||
Repository: repo,
|
||||
ProductWarehouseRepo: productWarehouseRepo,
|
||||
FifoSvc: fifoSvc,
|
||||
StockLogRepo: stockLogRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,14 +165,13 @@ func (s recordingService) GetNextDay(c *fiber.Ctx, projectFlockKandangId uint) (
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
|
||||
}
|
||||
|
||||
db := s.Repository.DB().WithContext(c.Context())
|
||||
next, err := s.Repository.GenerateNextDay(db, projectFlockKandangId)
|
||||
day, err := s.computeRecordingDay(c.Context(), projectFlockKandangId, time.Now().UTC())
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to compute next recording day for project_flock_kandang_id=%d: %+v", projectFlockKandangId, err)
|
||||
s.Log.Errorf("Failed to compute recording day for project_flock_kandang_id=%d: %+v", projectFlockKandangId, err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return next, nil
|
||||
return day, nil
|
||||
}
|
||||
|
||||
func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Recording, error) {
|
||||
@@ -208,6 +213,11 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
}
|
||||
}
|
||||
|
||||
day, err := s.computeRecordingDay(ctx, pfk.Id, recordTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isLaying && len(req.Eggs) > 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Egg details permitted only for laying project flocks")
|
||||
}
|
||||
@@ -221,13 +231,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
}
|
||||
var createdRecording entity.Recording
|
||||
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
nextDay, err := s.Repository.GenerateNextDay(tx, req.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to determine recording day: %+v", err)
|
||||
return err
|
||||
}
|
||||
if s.ProductionStandardSvc != nil {
|
||||
if err := s.ProductionStandardSvc.EnsureWeekAvailable(ctx, pfk.ProjectFlock.ProductionStandardId, category, nextDay); err != nil {
|
||||
if err := s.ProductionStandardSvc.EnsureWeekAvailable(ctx, pfk.ProjectFlock.ProductionStandardId, category, day); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -241,7 +246,6 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Recording for this project flock today already exists")
|
||||
}
|
||||
|
||||
day := nextDay
|
||||
createdRecording = entity.Recording{
|
||||
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
||||
RecordDatetime: recordTime,
|
||||
@@ -274,7 +278,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -293,7 +298,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -304,7 +310,8 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -346,6 +353,10 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
}
|
||||
|
||||
ctx := c.Context()
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var recordingEntity *entity.Recording
|
||||
var updatedRecording *entity.Recording
|
||||
@@ -431,14 +442,16 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if hasDepletionChanges {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -464,7 +477,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -480,6 +494,28 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
if err := ensureRecordingEggsUnused(existingEggs); err != nil {
|
||||
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 {
|
||||
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
|
||||
return err
|
||||
@@ -498,7 +534,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
} else {
|
||||
@@ -675,7 +712,7 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -697,7 +734,7 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.releaseRecordingStocks(ctx, tx, oldStocks); err != nil {
|
||||
if err := s.releaseRecordingStocks(ctx, tx, oldStocks, "", 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -756,10 +793,19 @@ func (s *recordingService) ensureProductWarehousesExist(c *fiber.Ctx, stocks []v
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||
return errors.New("stock log repository is not available")
|
||||
}
|
||||
|
||||
for _, stock := range stocks {
|
||||
if stock.Id == 0 {
|
||||
@@ -792,15 +838,42 @@ func (s *recordingService) consumeRecordingStocks(ctx context.Context, tx *gorm.
|
||||
if err := s.Repository.UpdateStockUsage(tx, stock.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||
return errors.New("stock log repository is not available")
|
||||
}
|
||||
|
||||
for _, depletion := range depletions {
|
||||
if depletion.Id == 0 {
|
||||
@@ -832,19 +905,67 @@ func (s *recordingService) consumeRecordingDepletions(ctx context.Context, tx *g
|
||||
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, result.PendingQuantity); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
func (s *recordingService) ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
||||
return s.consumeRecordingStocks(ctx, tx, stocks)
|
||||
func (s *recordingService) ConsumeRecordingStocks(
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||
return errors.New("stock log repository is not available")
|
||||
}
|
||||
|
||||
for _, stock := range stocks {
|
||||
if stock.Id == 0 {
|
||||
@@ -863,15 +984,38 @@ func (s *recordingService) releaseRecordingStocks(ctx context.Context, tx *gorm.
|
||||
if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||
return errors.New("stock log repository is not available")
|
||||
}
|
||||
|
||||
for _, depletion := range depletions {
|
||||
if depletion.Id == 0 {
|
||||
@@ -898,13 +1042,52 @@ func (s *recordingService) releaseRecordingDepletions(ctx context.Context, tx *g
|
||||
if err := s.Repository.UpdateDepletionPending(tx, depletion.Id, 0); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
func (s *recordingService) ReleaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
||||
return s.releaseRecordingStocks(ctx, tx, stocks)
|
||||
func (s *recordingService) ReleaseRecordingStocks(
|
||||
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) {
|
||||
@@ -929,6 +1112,40 @@ func (s *recordingService) resolvePopulationWarehouseID(ctx context.Context, pro
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, "Source product warehouse populasi tidak ditemukan")
|
||||
}
|
||||
|
||||
func (s *recordingService) computeRecordingDay(ctx context.Context, projectFlockKandangID uint, recordTime time.Time) (int, error) {
|
||||
if projectFlockKandangID == 0 {
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, "Project flock kandang tidak valid")
|
||||
}
|
||||
|
||||
populations, err := s.ProjectFlockPopulationRepo.GetByProjectFlockKandangID(ctx, projectFlockKandangID)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to fetch populations for project_flock_kandang_id=%d: %+v", projectFlockKandangID, err)
|
||||
return 0, fiber.NewError(fiber.StatusInternalServerError, "Gagal mengambil data populasi")
|
||||
}
|
||||
|
||||
var chickinDate time.Time
|
||||
for _, pop := range populations {
|
||||
if pop.ProjectChickin == nil || pop.ProjectChickin.ChickInDate.IsZero() {
|
||||
continue
|
||||
}
|
||||
if chickinDate.IsZero() || pop.ProjectChickin.ChickInDate.Before(chickinDate) {
|
||||
chickinDate = pop.ProjectChickin.ChickInDate
|
||||
}
|
||||
}
|
||||
if chickinDate.IsZero() {
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, "Tanggal chick in tidak ditemukan")
|
||||
}
|
||||
|
||||
chickinDay := time.Date(chickinDate.Year(), chickinDate.Month(), chickinDate.Day(), 0, 0, 0, 0, time.UTC)
|
||||
recordDay := time.Date(recordTime.Year(), recordTime.Month(), recordTime.Day(), 0, 0, 0, 0, time.UTC)
|
||||
diff := int(recordDay.Sub(chickinDay).Hours() / 24)
|
||||
if diff < 0 {
|
||||
return 0, fiber.NewError(fiber.StatusBadRequest, "Record date tidak boleh sebelum tanggal chick in")
|
||||
}
|
||||
|
||||
return diff + 1, nil
|
||||
}
|
||||
|
||||
func buildWarehouseDeltas(
|
||||
oldDepletions, newDepletions []entity.RecordingDepletion,
|
||||
oldEggs, newEggs []entity.RecordingEgg,
|
||||
@@ -963,27 +1180,48 @@ func (s *recordingService) adjustProductWarehouseQuantities(ctx context.Context,
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(note) != "" && s.StockLogRepo == nil {
|
||||
return errors.New("stock log repository is not available")
|
||||
}
|
||||
|
||||
for _, egg := range eggs {
|
||||
if egg.Id == 0 || egg.ProductWarehouseId == 0 || egg.Qty <= 0 {
|
||||
continue
|
||||
}
|
||||
note := fmt.Sprintf("Recording egg #%d", egg.Id)
|
||||
if _, err := s.FifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
||||
StockableKey: fifo.StockableKeyRecordingEgg,
|
||||
StockableID: egg.Id,
|
||||
ProductWarehouseID: egg.ProductWarehouseId,
|
||||
Quantity: float64(egg.Qty),
|
||||
Note: ¬e,
|
||||
Tx: tx,
|
||||
}); err != nil {
|
||||
s.Log.Errorf("Failed to replenish FIFO stock for recording egg %d: %+v", egg.Id, 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
|
||||
@@ -1034,6 +1272,8 @@ func (s *recordingService) syncRecordingStocks(
|
||||
recordingID uint,
|
||||
existing []entity.RecordingStock,
|
||||
incoming []validation.Stock,
|
||||
note string,
|
||||
actorID uint,
|
||||
) error {
|
||||
if s.FifoSvc == nil {
|
||||
if err := s.Repository.DeleteStocks(tx, recordingID); err != nil {
|
||||
@@ -1080,7 +1320,7 @@ func (s *recordingService) syncRecordingStocks(
|
||||
leftovers = append(leftovers, list...)
|
||||
}
|
||||
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
|
||||
}
|
||||
ids := make([]uint, 0, len(leftovers))
|
||||
@@ -1099,7 +1339,7 @@ func (s *recordingService) syncRecordingStocks(
|
||||
if len(stocksToConsume) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.consumeRecordingStocks(ctx, tx, stocksToConsume)
|
||||
return s.consumeRecordingStocks(ctx, tx, stocksToConsume, note, actorID)
|
||||
}
|
||||
|
||||
type eggTotals struct {
|
||||
@@ -1157,7 +1397,7 @@ func eggsMatch(existing []entity.RecordingEgg, incoming []validation.Egg) bool {
|
||||
}
|
||||
current := existingTotals[egg.ProductWarehouseId]
|
||||
current.Qty += egg.Qty
|
||||
current.Weight += float64(egg.Qty) * weight
|
||||
current.Weight += weight
|
||||
existingTotals[egg.ProductWarehouseId] = current
|
||||
}
|
||||
|
||||
@@ -1169,7 +1409,7 @@ func eggsMatch(existing []entity.RecordingEgg, incoming []validation.Egg) bool {
|
||||
}
|
||||
current := incomingTotals[egg.ProductWarehouseId]
|
||||
current.Qty += egg.Qty
|
||||
current.Weight += float64(egg.Qty) * weight
|
||||
current.Weight += weight
|
||||
incomingTotals[egg.ProductWarehouseId] = current
|
||||
}
|
||||
|
||||
@@ -1328,7 +1568,7 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
||||
|
||||
var eggMass float64
|
||||
if remainingChick > 0 && totalEggWeightGrams > 0 {
|
||||
eggMass = totalEggWeightGrams / remainingChick
|
||||
eggMass = (totalEggWeightGrams / remainingChick) / 1000
|
||||
updates["egg_mass"] = eggMass
|
||||
recording.EggMass = &eggMass
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ func (u *TransferLayingController) GetOne(c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||
}
|
||||
|
||||
result, approval, err := u.TransferLayingService.GetOneWithApproval(c, uint(id))
|
||||
result, approval, err := u.TransferLayingService.GetOne(c, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ import (
|
||||
|
||||
type TransferLayingService interface {
|
||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.LayingTransfer, int64, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, error)
|
||||
GetOneWithApproval(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error)
|
||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error)
|
||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.LayingTransfer, error)
|
||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||
@@ -156,14 +155,15 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
||||
return transferLayings, total, nil
|
||||
}
|
||||
|
||||
func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTransfer, error) {
|
||||
func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error) {
|
||||
transferLaying, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found")
|
||||
return nil, nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found")
|
||||
}
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed get transferLaying by id: %+v", err)
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB())
|
||||
@@ -174,15 +174,6 @@ func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTran
|
||||
transferLaying.LatestApproval = latestApproval
|
||||
}
|
||||
|
||||
return transferLaying, nil
|
||||
}
|
||||
|
||||
func (s transferLayingService) GetOneWithApproval(c *fiber.Ctx, id uint) (*entity.LayingTransfer, *entity.Approval, error) {
|
||||
transferLaying, err := s.GetOne(c, id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return transferLaying, transferLaying.LatestApproval, nil
|
||||
}
|
||||
|
||||
@@ -406,7 +397,12 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Gagal membuat transfer laying")
|
||||
}
|
||||
|
||||
return s.GetOne(c, createBody.Id)
|
||||
laying_transfer, _, err := s.GetOne(c, createBody.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return laying_transfer, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) {
|
||||
@@ -582,7 +578,9 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetOne(c, id)
|
||||
layingTransfer, _, err := s.GetOne(c, id)
|
||||
|
||||
return layingTransfer, err
|
||||
}
|
||||
|
||||
func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
@@ -773,7 +771,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
||||
|
||||
updated := make([]entity.LayingTransfer, 0, len(approvableIDs))
|
||||
for _, approvableID := range approvableIDs {
|
||||
transfer, err := s.GetOne(c, approvableID)
|
||||
transfer, _, err := s.GetOne(c, approvableID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user