codex: initiated changes

This commit is contained in:
Adnan Zahir
2026-03-30 13:40:29 +07:00
parent d76f72050e
commit be00837148
22 changed files with 1762 additions and 328 deletions
@@ -643,6 +643,11 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor
return nil
}
affectedKandangIDs, err := s.marketingPopulationKandangIDsFromActiveAllocations(ctx, tx, deliveryProduct.Id)
if err != nil {
return err
}
deliveryProduct.UsageQty = 0
deliveryProduct.PendingQty = 0
if err := deliveryProductRepo.UpdateOne(ctx, deliveryProduct.Id, deliveryProduct, nil); err != nil {
@@ -670,6 +675,9 @@ func (s deliveryOrdersService) releaseDeliveryStock(ctx context.Context, tx *gor
if err := fifoV2.ReleasePopulationConsumptionByUsable(ctx, tx, fifo.UsableKeyMarketingDelivery.String(), deliveryProduct.Id); err != nil {
return err
}
if err := s.resyncPopulationUsageByKandangIDs(ctx, tx, affectedKandangIDs); err != nil {
return err
}
releasedUsage := currentUsage - deliveryProduct.UsageQty
if actorID > 0 && releasedUsage > 0 {
@@ -725,29 +733,378 @@ func (s deliveryOrdersService) allocatePopulationForMarketingDelivery(
return nil
}
pw, err := s.ProductWarehouseRepo.WithTx(tx).GetByID(ctx, productWarehouseID, nil)
exactAllocations, err := s.findDirectPopulationAllocationsForMarketing(ctx, tx, deliveryProduct.Id)
if err != nil {
return err
}
if pw.ProjectFlockKandangId == nil || *pw.ProjectFlockKandangId == 0 {
if len(exactAllocations) > 0 {
if err := fifoV2.ReleasePopulationConsumptionByUsable(ctx, tx, fifo.UsableKeyMarketingDelivery.String(), deliveryProduct.Id); err != nil {
return err
}
if err := s.applyDirectPopulationAllocationsForMarketing(ctx, tx, productWarehouseID, deliveryProduct.Id, exactAllocations); err != nil {
return err
}
return s.resyncPopulationUsageByKandangIDs(ctx, tx, marketingAllocationKandangIDs(exactAllocations))
}
sourceGroups, err := s.findPopulationSourceGroupsForMarketing(ctx, tx, deliveryProduct.Id, productWarehouseID)
if err != nil {
return err
}
if len(sourceGroups) == 0 {
return nil
}
populations, err := s.ProjectFlockPopulationRepo.WithTx(tx).GetByProjectFlockKandangIDAndProductWarehouseID(ctx, *pw.ProjectFlockKandangId, productWarehouseID)
if err != nil {
if err := fifoV2.ReleasePopulationConsumptionByUsable(ctx, tx, fifo.UsableKeyMarketingDelivery.String(), deliveryProduct.Id); err != nil {
return err
}
if len(populations) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Populasi tidak ditemukan untuk delivery")
for _, group := range sourceGroups {
populations, err := s.ProjectFlockPopulationRepo.WithTx(tx).GetByProjectFlockKandangIDAndProductWarehouseID(
ctx,
group.ProjectFlockKandangID,
group.ProductWarehouseID,
)
if err != nil {
return err
}
if len(populations) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Populasi tidak ditemukan untuk delivery")
}
if err := s.allocatePopulationConsumptionWithoutRelease(
ctx,
tx,
populations,
productWarehouseID,
deliveryProduct.Id,
group.Qty,
); err != nil {
return err
}
}
return s.resyncPopulationUsageByKandangIDs(ctx, tx, marketingSourceGroupKandangIDs(sourceGroups))
}
type marketingPopulationAllocation struct {
ProjectFlockPopulationID uint `gorm:"column:project_flock_population_id"`
ProjectFlockKandangID uint `gorm:"column:project_flock_kandang_id"`
Qty float64 `gorm:"column:qty"`
}
type marketingPopulationSourceGroup struct {
ProjectFlockKandangID uint `gorm:"column:project_flock_kandang_id"`
ProductWarehouseID uint `gorm:"column:product_warehouse_id"`
Qty float64 `gorm:"column:qty"`
}
func (s deliveryOrdersService) findDirectPopulationAllocationsForMarketing(
ctx context.Context,
tx *gorm.DB,
deliveryProductID uint,
) ([]marketingPopulationAllocation, error) {
var rows []marketingPopulationAllocation
err := tx.WithContext(ctx).
Table("stock_allocations sa").
Select(`
pfp.id AS project_flock_population_id,
pc.project_flock_kandang_id AS project_flock_kandang_id,
SUM(sa.qty) AS qty
`).
Joins("JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyProjectFlockPopulation.String()).
Joins("JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
Where("sa.usable_type = ? AND sa.usable_id = ? AND sa.status = ? AND sa.allocation_purpose = ?",
fifo.UsableKeyMarketingDelivery.String(),
deliveryProductID,
entity.StockAllocationStatusActive,
entity.StockAllocationPurposeConsume,
).
Group("pfp.id, pc.project_flock_kandang_id").
Order("pfp.id ASC").
Scan(&rows).Error
return rows, err
}
func (s deliveryOrdersService) findPopulationSourceGroupsForMarketing(
ctx context.Context,
tx *gorm.DB,
deliveryProductID uint,
productWarehouseID uint,
) ([]marketingPopulationSourceGroup, error) {
groups := make(map[string]marketingPopulationSourceGroup)
appendGroup := func(projectFlockKandangID uint, sourceProductWarehouseID uint, qty float64) {
if projectFlockKandangID == 0 || sourceProductWarehouseID == 0 || qty <= 0 {
return
}
key := fmt.Sprintf("%d:%d", projectFlockKandangID, sourceProductWarehouseID)
current := groups[key]
current.ProjectFlockKandangID = projectFlockKandangID
current.ProductWarehouseID = sourceProductWarehouseID
current.Qty += qty
groups[key] = current
}
return fifoV2.AllocatePopulationConsumption(
ctx,
tx,
populations,
productWarehouseID,
fifo.UsableKeyMarketingDelivery.String(),
deliveryProduct.Id,
deliveryProduct.UsageQty,
)
var transferRows []marketingPopulationSourceGroup
if err := tx.WithContext(ctx).
Table("stock_allocations sa").
Select(`
source_pw.project_flock_kandang_id AS project_flock_kandang_id,
std.source_product_warehouse_id AS product_warehouse_id,
SUM(sa.qty) AS qty
`).
Joins("JOIN stock_transfer_details std ON std.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyStockTransferIn.String()).
Joins("JOIN product_warehouses source_pw ON source_pw.id = std.source_product_warehouse_id").
Where("sa.usable_type = ? AND sa.usable_id = ? AND sa.status = ? AND sa.allocation_purpose = ?",
fifo.UsableKeyMarketingDelivery.String(),
deliveryProductID,
entity.StockAllocationStatusActive,
entity.StockAllocationPurposeConsume,
).
Where("source_pw.project_flock_kandang_id IS NOT NULL").
Group("source_pw.project_flock_kandang_id, std.source_product_warehouse_id").
Scan(&transferRows).Error; err != nil {
return nil, err
}
for _, row := range transferRows {
appendGroup(row.ProjectFlockKandangID, row.ProductWarehouseID, row.Qty)
}
var purchaseRows []marketingPopulationSourceGroup
if err := tx.WithContext(ctx).
Table("stock_allocations sa").
Select(`
pi.project_flock_kandang_id AS project_flock_kandang_id,
pi.product_warehouse_id AS product_warehouse_id,
SUM(sa.qty) AS qty
`).
Joins("JOIN purchase_items pi ON pi.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyPurchaseItems.String()).
Where("sa.usable_type = ? AND sa.usable_id = ? AND sa.status = ? AND sa.allocation_purpose = ?",
fifo.UsableKeyMarketingDelivery.String(),
deliveryProductID,
entity.StockAllocationStatusActive,
entity.StockAllocationPurposeConsume,
).
Where("pi.project_flock_kandang_id IS NOT NULL").
Where("pi.product_warehouse_id IS NOT NULL").
Group("pi.project_flock_kandang_id, pi.product_warehouse_id").
Scan(&purchaseRows).Error; err != nil {
return nil, err
}
for _, row := range purchaseRows {
appendGroup(row.ProjectFlockKandangID, row.ProductWarehouseID, row.Qty)
}
var layingRows []marketingPopulationSourceGroup
if err := tx.WithContext(ctx).
Table("stock_allocations sa").
Select(`
ltt.target_project_flock_kandang_id AS project_flock_kandang_id,
ltt.product_warehouse_id AS product_warehouse_id,
SUM(sa.qty) AS qty
`).
Joins("JOIN laying_transfer_targets ltt ON ltt.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyTransferToLayingIn.String()).
Where("sa.usable_type = ? AND sa.usable_id = ? AND sa.status = ? AND sa.allocation_purpose = ?",
fifo.UsableKeyMarketingDelivery.String(),
deliveryProductID,
entity.StockAllocationStatusActive,
entity.StockAllocationPurposeConsume,
).
Where("ltt.product_warehouse_id IS NOT NULL").
Group("ltt.target_project_flock_kandang_id, ltt.product_warehouse_id").
Scan(&layingRows).Error; err != nil {
return nil, err
}
for _, row := range layingRows {
appendGroup(row.ProjectFlockKandangID, row.ProductWarehouseID, row.Qty)
}
if len(groups) == 0 {
pw, err := s.ProductWarehouseRepo.WithTx(tx).GetByID(ctx, productWarehouseID, nil)
if err != nil {
return nil, err
}
if pw.ProjectFlockKandangId != nil && *pw.ProjectFlockKandangId != 0 {
appendGroup(*pw.ProjectFlockKandangId, productWarehouseID, 0)
}
}
result := make([]marketingPopulationSourceGroup, 0, len(groups))
for _, group := range groups {
if group.Qty == 0 {
group.Qty = s.resolveMarketingRequestedUsageQty(ctx, tx, deliveryProductID)
}
if group.Qty > 0 {
result = append(result, group)
}
}
return result, nil
}
func (s deliveryOrdersService) applyDirectPopulationAllocationsForMarketing(
ctx context.Context,
tx *gorm.DB,
productWarehouseID uint,
deliveryProductID uint,
allocations []marketingPopulationAllocation,
) error {
stockAllocationRepo := commonRepo.NewStockAllocationRepository(tx)
for _, allocation := range allocations {
if allocation.ProjectFlockPopulationID == 0 || allocation.Qty <= 0 {
continue
}
record := &entity.StockAllocation{
ProductWarehouseId: productWarehouseID,
StockableType: fifo.StockableKeyProjectFlockPopulation.String(),
StockableId: allocation.ProjectFlockPopulationID,
UsableType: fifo.UsableKeyMarketingDelivery.String(),
UsableId: deliveryProductID,
Qty: allocation.Qty,
Status: entity.StockAllocationStatusActive,
AllocationPurpose: entity.StockAllocationPurposeConsume,
}
if err := stockAllocationRepo.CreateOne(ctx, record, nil); err != nil {
return err
}
if err := tx.WithContext(ctx).
Model(&entity.ProjectFlockPopulation{}).
Where("id = ?", allocation.ProjectFlockPopulationID).
Update("total_used_qty", gorm.Expr("total_used_qty + ?", allocation.Qty)).Error; err != nil {
return err
}
}
return nil
}
func (s deliveryOrdersService) allocatePopulationConsumptionWithoutRelease(
ctx context.Context,
tx *gorm.DB,
populations []entity.ProjectFlockPopulation,
productWarehouseID uint,
deliveryProductID uint,
consumeQty float64,
) error {
if consumeQty <= 0 {
return nil
}
remaining := consumeQty
stockAllocationRepo := commonRepo.NewStockAllocationRepository(tx)
for _, population := range populations {
available := population.TotalQty - population.TotalUsedQty
if available <= 0 {
continue
}
portion := available
if remaining < portion {
portion = remaining
}
if portion <= 0 {
continue
}
record := &entity.StockAllocation{
ProductWarehouseId: productWarehouseID,
StockableType: fifo.StockableKeyProjectFlockPopulation.String(),
StockableId: population.Id,
UsableType: fifo.UsableKeyMarketingDelivery.String(),
UsableId: deliveryProductID,
Qty: portion,
Status: entity.StockAllocationStatusActive,
AllocationPurpose: entity.StockAllocationPurposeConsume,
}
if err := stockAllocationRepo.CreateOne(ctx, record, nil); err != nil {
return err
}
if err := tx.WithContext(ctx).
Model(&entity.ProjectFlockPopulation{}).
Where("id = ?", population.Id).
Update("total_used_qty", gorm.Expr("total_used_qty + ?", portion)).Error; err != nil {
return err
}
remaining -= portion
if remaining <= 0.000001 {
break
}
}
if remaining > 0.000001 {
return fiber.NewError(fiber.StatusBadRequest, "Populasi tidak mencukupi")
}
return nil
}
func (s deliveryOrdersService) marketingPopulationKandangIDsFromActiveAllocations(
ctx context.Context,
tx *gorm.DB,
deliveryProductID uint,
) ([]uint, error) {
var ids []uint
err := tx.WithContext(ctx).
Table("stock_allocations sa").
Distinct("pc.project_flock_kandang_id").
Joins("JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id AND sa.stockable_type = ?", fifo.StockableKeyProjectFlockPopulation.String()).
Joins("JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
Where("sa.usable_type = ? AND sa.usable_id = ? AND sa.status = ? AND sa.allocation_purpose = ?",
fifo.UsableKeyMarketingDelivery.String(),
deliveryProductID,
entity.StockAllocationStatusActive,
entity.StockAllocationPurposeConsume,
).
Pluck("pc.project_flock_kandang_id", &ids).Error
return ids, err
}
func (s deliveryOrdersService) resyncPopulationUsageByKandangIDs(ctx context.Context, tx *gorm.DB, kandangIDs []uint) error {
for _, kandangID := range uniqueUintIDs(kandangIDs) {
if kandangID == 0 {
continue
}
if err := s.ProjectFlockPopulationRepo.WithTx(tx).ResyncUsageByProjectFlockKandangID(ctx, tx, kandangID); err != nil {
return err
}
}
return nil
}
func (s deliveryOrdersService) resolveMarketingRequestedUsageQty(ctx context.Context, tx *gorm.DB, deliveryProductID uint) float64 {
var usageQty float64
if err := tx.WithContext(ctx).
Table("marketing_delivery_products").
Select("usage_qty").
Where("id = ?", deliveryProductID).
Scan(&usageQty).Error; err != nil {
return 0
}
return usageQty
}
func marketingAllocationKandangIDs(rows []marketingPopulationAllocation) []uint {
ids := make([]uint, 0, len(rows))
for _, row := range rows {
ids = append(ids, row.ProjectFlockKandangID)
}
return ids
}
func marketingSourceGroupKandangIDs(rows []marketingPopulationSourceGroup) []uint {
ids := make([]uint, 0, len(rows))
for _, row := range rows {
ids = append(ids, row.ProjectFlockKandangID)
}
return ids
}
func uniqueUintIDs(ids []uint) []uint {
seen := make(map[uint]struct{}, len(ids))
result := make([]uint, 0, len(ids))
for _, id := range ids {
if id == 0 {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
result = append(result, id)
}
return result
}