mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 15:25:43 +00:00
fixing filter product warehouse transfer, cannot take from population
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -35,7 +36,7 @@ import (
|
||||
|
||||
const (
|
||||
chickinDeletePopulationGuardMessage = "Chickin tidak dapat dihapus karena masih memiliki population aktif"
|
||||
chickinDeleteRecordingGuardMessage = "Chickin tidak dapat dihapus karena masih terkait recording. Lakukan rollback/delete recording terlebih dahulu"
|
||||
chickinDeleteDownstreamGuardMessage = "Chickin tidak bisa dihapus karena masih dipakai oleh transaksi turunan. Hapus/unexecute Marketing, Recording, dan Transfer to Laying terlebih dahulu."
|
||||
)
|
||||
|
||||
type ChickinService interface {
|
||||
@@ -448,21 +449,13 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
||||
|
||||
func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
|
||||
chickin, err := s.Repository.GetByID(c.Context(), id, nil)
|
||||
if err != nil {
|
||||
if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.ensureNotTransferred(c.Context(), chickin.ProjectFlockKandangId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.ensureNoExecutedTransferForDelete(c.Context(), chickin.ProjectFlockKandangId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actorID, err := m.ActorIDFromContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -497,7 +490,7 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
traceAllocBefore,
|
||||
)
|
||||
|
||||
if err := s.ensureNoRelatedRecording(c.Context(), tx, lockedChickin); err != nil {
|
||||
if err := s.ensureNoDownstreamConsumptionForDelete(c.Context(), tx, lockedChickin.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -561,51 +554,6 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s chickinService) ensureNoExecutedTransferForDelete(ctx context.Context, projectFlockKandangID uint) error {
|
||||
if projectFlockKandangID == 0 || s.TransferLayingRepo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete guard by executed transfer hanya untuk kandang kategori growing.
|
||||
if s.ProjectflockKandangRepo != nil {
|
||||
pfk, err := s.ProjectflockKandangRepo.GetByIDLight(ctx, projectFlockKandangID)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.Log.Errorf("Failed to resolve project flock kandang %d for delete guard: %+v", projectFlockKandangID, err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transfer laying")
|
||||
}
|
||||
if err == nil && pfk != nil {
|
||||
category := strings.ToUpper(strings.TrimSpace(pfk.ProjectFlock.Category))
|
||||
if category != strings.ToUpper(string(utils.ProjectFlockCategoryGrowing)) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isExecuted := func(transfer *entity.LayingTransfer) bool {
|
||||
return transfer != nil && transfer.ExecutedAt != nil && !transfer.ExecutedAt.IsZero()
|
||||
}
|
||||
|
||||
sourceTransfer, err := s.TransferLayingRepo.GetLatestApprovedBySourceKandang(ctx, projectFlockKandangID)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.Log.Errorf("Failed to resolve transfer laying by source kandang %d for delete guard: %+v", projectFlockKandangID, err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transfer laying")
|
||||
}
|
||||
targetTransfer, err := s.TransferLayingRepo.GetLatestApprovedByTargetKandang(ctx, projectFlockKandangID)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.Log.Errorf("Failed to resolve transfer laying by target kandang %d for delete guard: %+v", projectFlockKandangID, err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi transfer laying")
|
||||
}
|
||||
|
||||
if isExecuted(sourceTransfer) || isExecuted(targetTransfer) {
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
"Chickin tidak dapat dihapus karena transfer laying sudah dieksekusi. Lakukan unexecute transfer terlebih dahulu",
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *chickinService) resolveLayingTransferAvailableQty(ctx context.Context, tx *gorm.DB, targetProjectFlockKandangID, productWarehouseID uint) (float64, error) {
|
||||
if targetProjectFlockKandangID == 0 || productWarehouseID == 0 {
|
||||
return 0, nil
|
||||
@@ -674,8 +622,8 @@ func (s chickinService) ensurePopulationRouteScope(ctx context.Context, tx *gorm
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *chickinService) ensureNoRelatedRecording(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin) error {
|
||||
if chickin == nil || chickin.ProjectFlockKandangId == 0 {
|
||||
func (s *chickinService) ensureNoDownstreamConsumptionForDelete(ctx context.Context, tx *gorm.DB, chickinID uint) error {
|
||||
if chickinID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -684,30 +632,94 @@ func (s *chickinService) ensureNoRelatedRecording(ctx context.Context, tx *gorm.
|
||||
db = tx.WithContext(ctx)
|
||||
}
|
||||
|
||||
recordDateFloor := normalizeDateOnlyUTC(chickin.ChickInDate)
|
||||
var earliest entity.Recording
|
||||
query := db.Model(&entity.Recording{}).
|
||||
Where("project_flock_kandangs_id = ?", chickin.ProjectFlockKandangId).
|
||||
Where("deleted_at IS NULL")
|
||||
if !recordDateFloor.IsZero() {
|
||||
query = query.Where("record_datetime >= ?", recordDateFloor)
|
||||
}
|
||||
if err := query.Order("record_datetime ASC, id ASC").Take(&earliest).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
s.Log.Errorf("Failed to validate related recordings for chickin %d: %+v", chickin.Id, err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi recording terkait chickin")
|
||||
type downstreamRow struct {
|
||||
UsableType string `gorm:"column:usable_type"`
|
||||
UsableID uint `gorm:"column:usable_id"`
|
||||
}
|
||||
|
||||
return fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf(
|
||||
"%s (recording tanggal %s)",
|
||||
chickinDeleteRecordingGuardMessage,
|
||||
normalizeDateOnlyUTC(earliest.RecordDatetime).Format("2006-01-02"),
|
||||
),
|
||||
)
|
||||
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.UsableKeyTransferToLayingOut.String(),
|
||||
}).
|
||||
Group("sa.usable_type, sa.usable_id").
|
||||
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")
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
marketingIDs := make(map[uint]struct{})
|
||||
recordingIDs := make(map[uint]struct{})
|
||||
transferLayingIDs := make(map[uint]struct{})
|
||||
|
||||
for _, row := range rows {
|
||||
switch row.UsableType {
|
||||
case fifo.UsableKeyMarketingDelivery.String():
|
||||
marketingIDs[row.UsableID] = struct{}{}
|
||||
case fifo.UsableKeyRecordingDepletion.String():
|
||||
recordingIDs[row.UsableID] = struct{}{}
|
||||
case fifo.UsableKeyTransferToLayingOut.String():
|
||||
transferLayingIDs[row.UsableID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
details := make([]string, 0, 3)
|
||||
if ids := sortedIDs(marketingIDs); len(ids) > 0 {
|
||||
details = append(details, fmt.Sprintf("Marketing=%s", joinUint(ids)))
|
||||
}
|
||||
if ids := sortedIDs(recordingIDs); len(ids) > 0 {
|
||||
details = append(details, fmt.Sprintf("Recording=%s", joinUint(ids)))
|
||||
}
|
||||
if ids := sortedIDs(transferLayingIDs); len(ids) > 0 {
|
||||
details = append(details, fmt.Sprintf("TransferToLaying=%s", joinUint(ids)))
|
||||
}
|
||||
|
||||
message := chickinDeleteDownstreamGuardMessage
|
||||
if len(details) > 0 {
|
||||
message = fmt.Sprintf("%s Dependensi aktif: %s.", message, strings.Join(details, ", "))
|
||||
}
|
||||
|
||||
return fiber.NewError(fiber.StatusBadRequest, message)
|
||||
}
|
||||
|
||||
func sortedIDs(input map[uint]struct{}) []uint {
|
||||
if len(input) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]uint, 0, len(input))
|
||||
for id := range input {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
out = append(out, id)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
|
||||
return out
|
||||
}
|
||||
|
||||
func joinUint(values []uint) string {
|
||||
if len(values) == 0 {
|
||||
return "-"
|
||||
}
|
||||
parts := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
parts = append(parts, fmt.Sprintf("%d", value))
|
||||
}
|
||||
return strings.Join(parts, "|")
|
||||
}
|
||||
|
||||
func (s *chickinService) hasActiveChickinConsumeAllocations(ctx context.Context, tx *gorm.DB, chickinID uint) (bool, error) {
|
||||
|
||||
Reference in New Issue
Block a user