fix: all implemented fifo v2

This commit is contained in:
Hafizh A. Y
2026-03-02 12:44:20 +07:00
parent dd61b66af0
commit d5a1751868
11 changed files with 319 additions and 369 deletions
@@ -256,35 +256,13 @@ func (s *purchaseService) GetOne(c *fiber.Ctx, id uint) (*entity.Purchase, error
s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", id, err)
}
if len(purchase.Items) > 0 {
itemIDs := make([]uint, 0, len(purchase.Items))
for i := range purchase.Items {
if purchase.Items[i].Id == 0 {
continue
}
itemIDs = append(itemIDs, purchase.Items[i].Id)
lockedIDs, err := s.resolveChickinLockedItemIDs(c.Context(), s.PurchaseRepo.DB(), purchase.Items)
if err != nil {
return nil, err
}
if len(itemIDs) > 0 {
var usedIDs []uint
if err := s.PurchaseRepo.DB().WithContext(c.Context()).
Model(&entity.StockAllocation{}).
Distinct("stockable_id").
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
fifo.StockableKeyPurchaseItems.String(),
itemIDs,
fifo.UsableKeyProjectChickin.String(),
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
).
Pluck("stockable_id", &usedIDs).Error; err != nil {
return nil, err
}
usedSet := make(map[uint]struct{}, len(usedIDs))
for _, id := range usedIDs {
usedSet[id] = struct{}{}
}
for i := range purchase.Items {
if _, ok := usedSet[purchase.Items[i].Id]; ok {
purchase.Items[i].HasChickin = true
}
for i := range purchase.Items {
if _, ok := lockedIDs[purchase.Items[i].Id]; ok {
purchase.Items[i].HasChickin = true
}
}
}
@@ -532,48 +510,31 @@ func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *valid
}
if action == entity.ApprovalActionApproved {
itemIDs := make([]uint, 0, len(purchase.Items))
itemByID := make(map[uint]entity.PurchaseItem, len(purchase.Items))
for i := range purchase.Items {
if purchase.Items[i].Id == 0 {
continue
}
itemIDs = append(itemIDs, purchase.Items[i].Id)
itemByID[purchase.Items[i].Id] = purchase.Items[i]
}
if len(itemIDs) > 0 {
var usedIDs []uint
if err := s.PurchaseRepo.DB().WithContext(ctx).
Model(&entity.StockAllocation{}).
Distinct("stockable_id").
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
fifo.StockableKeyPurchaseItems.String(),
itemIDs,
fifo.UsableKeyProjectChickin.String(),
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
).
Pluck("stockable_id", &usedIDs).Error; err != nil {
return nil, err
}
if len(usedIDs) > 0 {
usedSet := make(map[uint]struct{}, len(usedIDs))
for _, id := range usedIDs {
usedSet[id] = struct{}{}
lockedIDs, err := s.resolveChickinLockedItemIDs(ctx, s.PurchaseRepo.DB(), purchase.Items)
if err != nil {
return nil, err
}
if len(lockedIDs) > 0 {
for _, payload := range req.Items {
if payload.PurchaseItemID == 0 || payload.Qty == nil {
continue
}
for _, payload := range req.Items {
if payload.PurchaseItemID == 0 || payload.Qty == nil {
continue
}
if _, used := usedSet[payload.PurchaseItemID]; !used {
continue
}
item, ok := itemByID[payload.PurchaseItemID]
if !ok {
continue
}
if *payload.Qty != item.SubQty {
return nil, utils.BadRequest("Purchase sudah chickin, qty tidak bisa diubah")
}
if _, locked := lockedIDs[payload.PurchaseItemID]; !locked {
continue
}
item, ok := itemByID[payload.PurchaseItemID]
if !ok {
continue
}
if *payload.Qty != item.SubQty {
return nil, utils.BadRequest("Purchase sudah chickin, qty tidak bisa diubah")
}
}
}
@@ -827,49 +788,32 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
}
}
if action == entity.ApprovalActionApproved {
itemIDs := make([]uint, 0, len(purchase.Items))
itemByID := make(map[uint]entity.PurchaseItem, len(purchase.Items))
for i := range purchase.Items {
if purchase.Items[i].Id == 0 {
continue
}
itemIDs = append(itemIDs, purchase.Items[i].Id)
itemByID[purchase.Items[i].Id] = purchase.Items[i]
}
if len(itemIDs) > 0 {
var usedIDs []uint
if err := s.PurchaseRepo.DB().WithContext(ctx).
Model(&entity.StockAllocation{}).
Distinct("stockable_id").
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
fifo.StockableKeyPurchaseItems.String(),
itemIDs,
fifo.UsableKeyProjectChickin.String(),
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
).
Pluck("stockable_id", &usedIDs).Error; err != nil {
return nil, err
}
if len(usedIDs) > 0 {
usedSet := make(map[uint]struct{}, len(usedIDs))
for _, id := range usedIDs {
usedSet[id] = struct{}{}
lockedIDs, err := s.resolveChickinLockedItemIDs(ctx, s.PurchaseRepo.DB(), purchase.Items)
if err != nil {
return nil, err
}
if len(lockedIDs) > 0 {
for _, payload := range req.Items {
if _, used := lockedIDs[payload.PurchaseItemID]; !used {
continue
}
for _, payload := range req.Items {
if _, used := usedSet[payload.PurchaseItemID]; !used {
continue
}
item, ok := itemByID[payload.PurchaseItemID]
if !ok {
continue
}
receivedQty := item.SubQty
if payload.ReceivedQty != nil {
receivedQty = *payload.ReceivedQty
}
if receivedQty != item.TotalQty {
return nil, utils.BadRequest("Purchase sudah chickin, qty penerimaan tidak bisa diubah")
}
item, ok := itemByID[payload.PurchaseItemID]
if !ok {
continue
}
receivedQty := item.SubQty
if payload.ReceivedQty != nil {
receivedQty = *payload.ReceivedQty
}
if receivedQty != item.TotalQty {
return nil, utils.BadRequest("Purchase sudah chickin, qty penerimaan tidak bisa diubah")
}
}
}
@@ -1437,28 +1381,12 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint) error {
}
transactionErr := s.PurchaseRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
itemIDs := make([]uint, 0, len(itemsToDelete))
for _, item := range itemsToDelete {
if item.Id == 0 {
continue
}
itemIDs = append(itemIDs, item.Id)
lockedIDs, err := s.resolveChickinLockedItemIDs(ctx, tx, itemsToDelete)
if err != nil {
return err
}
if len(itemIDs) > 0 {
var count int64
if err := tx.Model(&entity.StockAllocation{}).
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
fifo.StockableKeyPurchaseItems.String(),
itemIDs,
fifo.UsableKeyProjectChickin.String(),
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
).
Count(&count).Error; err != nil {
return err
}
if count > 0 {
return utils.BadRequest("Purchase already chickin, failed to delete purchase")
}
if len(lockedIDs) > 0 {
return utils.BadRequest("Purchase already chickin, failed to delete purchase")
}
if err := s.rollbackPurchaseStock(ctx, tx, itemsToDelete, note, actorID); err != nil {
@@ -1957,6 +1885,67 @@ func (s *purchaseService) applyTravelDocumentURLs(ctx context.Context, purchase
}
}
func collectPurchaseItemIDs(items []entity.PurchaseItem) []uint {
itemIDs := make([]uint, 0, len(items))
for i := range items {
if items[i].Id == 0 {
continue
}
itemIDs = append(itemIDs, items[i].Id)
}
return itemIDs
}
func (s *purchaseService) resolveChickinLockedItemIDs(ctx context.Context, db *gorm.DB, items []entity.PurchaseItem) (map[uint]struct{}, error) {
itemIDs := collectPurchaseItemIDs(items)
return s.resolveChickinLockedItemIDsByItemID(ctx, db, itemIDs)
}
func (s *purchaseService) resolveChickinLockedItemIDsByItemID(ctx context.Context, db *gorm.DB, itemIDs []uint) (map[uint]struct{}, error) {
locked := make(map[uint]struct{})
if len(itemIDs) == 0 {
return locked, nil
}
if db == nil {
return nil, errors.New("database is required")
}
var allocationLockedIDs []uint
if err := db.WithContext(ctx).
Model(&entity.StockAllocation{}).
Distinct("stockable_id").
Where("stockable_type = ? AND stockable_id IN ? AND usable_type = ? AND status IN ?",
fifo.StockableKeyPurchaseItems.String(),
itemIDs,
fifo.UsableKeyProjectChickin.String(),
[]string{entity.StockAllocationStatusActive, entity.StockAllocationStatusPending},
).
Pluck("stockable_id", &allocationLockedIDs).Error; err != nil {
return nil, err
}
for _, itemID := range allocationLockedIDs {
locked[itemID] = struct{}{}
}
var conversionLockedIDs []uint
if err := db.WithContext(ctx).
Table("purchase_items pi").
Distinct("pi.id").
Joins("JOIN project_chickins pc ON pc.product_warehouse_id = pi.product_warehouse_id AND pc.deleted_at IS NULL").
Joins("JOIN project_flock_populations pfp ON pfp.project_chickin_id = pc.id AND pfp.deleted_at IS NULL").
Where("pi.id IN ?", itemIDs).
Where("pi.project_flock_kandang_id IS NOT NULL").
Where("pc.project_flock_kandang_id = pi.project_flock_kandang_id").
Pluck("pi.id", &conversionLockedIDs).Error; err != nil {
return nil, err
}
for _, itemID := range conversionLockedIDs {
locked[itemID] = struct{}{}
}
return locked, nil
}
func collectPFKIDsFromPurchase(p *entity.Purchase) []uint {
seen := make(map[uint]struct{})
ids := make([]uint, 0)