add restrict create/edit/delete depletion

This commit is contained in:
ragilap
2026-03-14 15:38:47 +07:00
parent 29956528e5
commit 5ba10113c3
12 changed files with 1099 additions and 109 deletions
@@ -646,24 +646,72 @@ func (s *chickinService) ensureNoDownstreamConsumptionForDelete(ctx context.Cont
}
var rows []downstreamRow
if err := db.Table("stock_allocations sa").
Select("sa.usable_type, sa.usable_id").
Joins("JOIN project_flock_populations pfp ON pfp.id = sa.stockable_id").
Where("pfp.project_chickin_id = ?", chickinID).
Where("pfp.deleted_at IS NULL").
Where("sa.stockable_type = ?", fifo.StockableKeyProjectFlockPopulation.String()).
Where("sa.status = ?", entity.StockAllocationStatusActive).
Where("sa.allocation_purpose = ?", entity.StockAllocationPurposeConsume).
Where("sa.deleted_at IS NULL").
Where("sa.usable_type IN ?", []string{
fifo.UsableKeyMarketingDelivery.String(),
fifo.UsableKeyRecordingDepletion.String(),
fifo.UsableKeyStockTransferOut.String(),
fifo.UsableKeyAdjustmentOut.String(),
fifo.UsableKeyTransferToLayingOut.String(),
}).
Group("sa.usable_type, sa.usable_id").
Scan(&rows).Error; err != nil {
dependencyTypes := []string{
fifo.UsableKeyMarketingDelivery.String(),
fifo.UsableKeyRecordingStock.String(),
fifo.UsableKeyRecordingDepletion.String(),
fifo.UsableKeyStockTransferOut.String(),
fifo.UsableKeyAdjustmentOut.String(),
fifo.UsableKeyTransferToLayingOut.String(),
}
query := `
WITH chickin_sources AS (
SELECT DISTINCT sa.stockable_type, sa.stockable_id
FROM stock_allocations sa
WHERE sa.usable_type = ?
AND sa.usable_id = ?
AND sa.status = ?
AND sa.allocation_purpose = ?
AND sa.deleted_at IS NULL
),
downstream_by_population AS (
SELECT sa.usable_type, sa.usable_id
FROM project_flock_populations pfp
JOIN stock_allocations sa
ON sa.stockable_type = ?
AND sa.stockable_id = pfp.id
WHERE pfp.project_chickin_id = ?
AND pfp.deleted_at IS NULL
AND sa.status = ?
AND sa.allocation_purpose = ?
AND sa.deleted_at IS NULL
AND sa.usable_type IN ?
),
downstream_by_source AS (
SELECT sa.usable_type, sa.usable_id
FROM chickin_sources cs
JOIN stock_allocations sa
ON sa.stockable_type = cs.stockable_type
AND sa.stockable_id = cs.stockable_id
WHERE sa.status = ?
AND sa.allocation_purpose = ?
AND sa.deleted_at IS NULL
AND sa.usable_type IN ?
)
SELECT dep.usable_type, dep.usable_id
FROM (
SELECT usable_type, usable_id FROM downstream_by_population
UNION
SELECT usable_type, usable_id FROM downstream_by_source
) dep
`
if err := db.Raw(
query,
fifo.UsableKeyProjectChickin.String(),
chickinID,
entity.StockAllocationStatusActive,
entity.StockAllocationPurposeConsume,
fifo.StockableKeyProjectFlockPopulation.String(),
chickinID,
entity.StockAllocationStatusActive,
entity.StockAllocationPurposeConsume,
dependencyTypes,
entity.StockAllocationStatusActive,
entity.StockAllocationPurposeConsume,
dependencyTypes,
).Scan(&rows).Error; err != nil {
s.Log.Errorf("Failed to validate downstream consumption for chickin %d: %+v", chickinID, err)
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transaksi turunan chickin")
}
@@ -682,7 +730,7 @@ func (s *chickinService) ensureNoDownstreamConsumptionForDelete(ctx context.Cont
switch row.UsableType {
case fifo.UsableKeyMarketingDelivery.String():
marketingIDs[row.UsableID] = struct{}{}
case fifo.UsableKeyRecordingDepletion.String():
case fifo.UsableKeyRecordingStock.String(), fifo.UsableKeyRecordingDepletion.String():
recordingIDs[row.UsableID] = struct{}{}
case fifo.UsableKeyStockTransferOut.String():
transferIDs[row.UsableID] = struct{}{}
@@ -522,9 +522,6 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
}
recordingEntity = recording
if err := s.ensurePopulationMutationAllowed(ctx, recordingEntity, "ubah"); err != nil {
return err
}
pfkForRoute := recordingEntity.ProjectFlockKandang
if pfkForRoute == nil || pfkForRoute.Id == 0 {
fetchedPfk, fetchErr := s.ProjectFlockKandangRepo.GetByIDLight(ctx, recordingEntity.ProjectFlockKandangId)
@@ -537,7 +534,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
}
pfkForRoute = fetchedPfk
}
routePayload := buildRecordingRoutePayloadFromUpdate(req, recordingEntity)
routePayload := buildRecordingRoutePayloadFromUpdate(req)
if err := s.enforceTransferRecordingRoute(ctx, pfkForRoute, recordingEntity.RecordDatetime, routePayload); err != nil {
return err
}
@@ -594,6 +591,9 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
if match {
hasDepletionChanges = false
} else {
if err := s.ensurePopulationMutationAllowed(ctx, recordingEntity, "ubah"); err != nil {
return err
}
if err := s.ensureDepletionMutationAllowed(ctx, recordingEntity, "ubah"); err != nil {
return err
}
@@ -935,15 +935,15 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
s.Log.Errorf("Failed to find recording: %+v", err)
return err
}
if err := s.ensurePopulationMutationAllowed(ctx, recording, "hapus"); err != nil {
return err
}
existingDepletions, err := s.Repository.ListDepletions(tx, recording.Id)
if err != nil {
s.Log.Errorf("Failed to list existing depletions: %+v", err)
return err
}
if len(existingDepletions) > 0 {
if err := s.ensurePopulationMutationAllowed(ctx, recording, "hapus"); err != nil {
return err
}
if err := s.ensureDepletionMutationAllowed(ctx, recording, "hapus"); err != nil {
return err
}
@@ -1376,60 +1376,34 @@ func buildRecordingRoutePayloadFromCreate(req *validation.Create) recordingRoute
return payload
}
func buildRecordingRoutePayloadFromUpdate(req *validation.Update, existing *entity.Recording) recordingRoutePayload {
func buildRecordingRoutePayloadFromUpdate(req *validation.Update) recordingRoutePayload {
payload := recordingRoutePayload{}
if req == nil && existing == nil {
if req == nil {
return payload
}
if req != nil && req.Stocks != nil {
if req.Stocks != nil {
for _, stock := range req.Stocks {
if stock.Qty > 0 {
payload.StockCount++
}
}
} else if existing != nil {
for _, stock := range existing.Stocks {
usageQty := 0.0
if stock.UsageQty != nil {
usageQty = *stock.UsageQty
}
pendingQty := 0.0
if stock.PendingQty != nil {
pendingQty = *stock.PendingQty
}
if usageQty > 0 || pendingQty > 0 {
payload.StockCount++
}
}
}
if req != nil && req.Depletions != nil {
if req.Depletions != nil {
for _, depletion := range req.Depletions {
if depletion.Qty > 0 {
payload.DepletionCount++
}
}
} else if existing != nil {
for _, depletion := range existing.Depletions {
if depletion.Qty > 0 {
payload.DepletionCount++
}
}
}
if req != nil && req.Eggs != nil {
if req.Eggs != nil {
for _, egg := range req.Eggs {
if egg.Qty > 0 {
payload.EggCount++
}
}
} else if existing != nil {
for _, egg := range existing.Eggs {
if egg.Qty > 0 {
payload.EggCount++
}
}
}
return payload