package fifo_stock_v2 import ( "context" "errors" "math" "sort" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) func ReleasePopulationConsumptionByUsable( ctx context.Context, tx *gorm.DB, usableType string, usableID uint, ) error { if tx == nil { return errors.New("transaction is required") } if usableType == "" || usableID == 0 { return errors.New("usable type and id are required") } stockAllocationRepo := commonRepo.NewStockAllocationRepository(tx) allocations, err := stockAllocationRepo.FindActiveByUsable(ctx, usableType, usableID, nil) if err != nil { return err } for _, allocation := range allocations { if allocation.StockableType != fifo.StockableKeyProjectFlockPopulation.String() || allocation.StockableId == 0 || allocation.Qty <= 0 { continue } if err := tx.WithContext(ctx). Model(&entity.ProjectFlockPopulation{}). Where("id = ?", allocation.StockableId). Update("total_used_qty", gorm.Expr("GREATEST(total_used_qty - ?, 0)", allocation.Qty)).Error; err != nil { return err } } return stockAllocationRepo.ReleaseByUsable(ctx, usableType, usableID, nil, nil) } func AllocatePopulationConsumption( ctx context.Context, tx *gorm.DB, populations []entity.ProjectFlockPopulation, productWarehouseID uint, usableType string, usableID uint, consumeQty float64, ) error { if consumeQty <= 0 { return nil } if tx == nil { return errors.New("transaction is required") } if productWarehouseID == 0 { return fiber.NewError(fiber.StatusBadRequest, "Product warehouse tidak valid") } if usableType == "" || usableID == 0 { return errors.New("usable type and id are required") } if len(populations) == 0 { return fiber.NewError(fiber.StatusBadRequest, "Populasi tidak ditemukan") } if err := ReleasePopulationConsumptionByUsable(ctx, tx, usableType, usableID); err != nil { return err } sort.Slice(populations, func(i, j int) bool { if populations[i].CreatedAt.Equal(populations[j].CreatedAt) { return populations[i].Id < populations[j].Id } return populations[i].CreatedAt.Before(populations[j].CreatedAt) }) stockAllocationRepo := commonRepo.NewStockAllocationRepository(tx) remaining := consumeQty for _, pop := range populations { available := pop.TotalQty - pop.TotalUsedQty if available <= 0 { continue } portion := math.Min(available, remaining) if portion <= 0 { continue } allocation := &entity.StockAllocation{ ProductWarehouseId: productWarehouseID, StockableType: fifo.StockableKeyProjectFlockPopulation.String(), StockableId: pop.Id, UsableType: usableType, UsableId: usableID, Qty: portion, Status: entity.StockAllocationStatusActive, AllocationPurpose: entity.StockAllocationPurposeConsume, } if err := stockAllocationRepo.CreateOne(ctx, allocation, nil); err != nil { return err } if err := tx.WithContext(ctx). Model(&entity.ProjectFlockPopulation{}). Where("id = ?", pop.Id). Update("total_used_qty", gorm.Expr("total_used_qty + ?", portion)).Error; err != nil { return err } remaining -= portion if remaining <= 1e-6 { break } } if remaining > 1e-6 { return fiber.NewError(fiber.StatusBadRequest, "Populasi tidak mencukupi") } return nil }