mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 23:35:43 +00:00
[FEAT/BE] Add restrict purchase chickin
This commit is contained in:
@@ -21,6 +21,7 @@ type PurchaseItem struct {
|
|||||||
Price float64 `gorm:"type:numeric(15,3);default:0"`
|
Price float64 `gorm:"type:numeric(15,3);default:0"`
|
||||||
TotalPrice float64 `gorm:"type:numeric(15,3);default:0"`
|
TotalPrice float64 `gorm:"type:numeric(15,3);default:0"`
|
||||||
ExpenseNonstockId *uint64
|
ExpenseNonstockId *uint64
|
||||||
|
HasChickin bool `gorm:"-" json:"-"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
ExpenseNonstock *ExpenseNonstock `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
|
ExpenseNonstock *ExpenseNonstock `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ type PurchaseItemDTO struct {
|
|||||||
VehicleNumber *string `json:"vehicle_number"`
|
VehicleNumber *string `json:"vehicle_number"`
|
||||||
TransportPerItem *float64 `json:"transport_per_item,omitempty"`
|
TransportPerItem *float64 `json:"transport_per_item,omitempty"`
|
||||||
ExpeditionVendor *supplierDTO.SupplierRelationDTO `json:"expedition_vendor,omitempty"`
|
ExpeditionVendor *supplierDTO.SupplierRelationDTO `json:"expedition_vendor,omitempty"`
|
||||||
|
HasChickin bool `json:"has_chickin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PoExpeditionDTO struct {
|
type PoExpeditionDTO struct {
|
||||||
@@ -100,6 +101,7 @@ func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
|
|||||||
TravelNumber: item.TravelNumber,
|
TravelNumber: item.TravelNumber,
|
||||||
TravelDocumentPath: item.TravelNumberDocs,
|
TravelDocumentPath: item.TravelNumberDocs,
|
||||||
VehicleNumber: item.VehicleNumber,
|
VehicleNumber: item.VehicleNumber,
|
||||||
|
HasChickin: item.HasChickin,
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.Product != nil && item.Product.Id != 0 {
|
if item.Product != nil && item.Product.Id != 0 {
|
||||||
|
|||||||
@@ -255,6 +255,39 @@ func (s *purchaseService) GetOne(c *fiber.Ctx, id uint) (*entity.Purchase, error
|
|||||||
if err := s.attachLatestApproval(c.Context(), purchase); err != nil {
|
if err := s.attachLatestApproval(c.Context(), purchase); err != nil {
|
||||||
s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", id, err)
|
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)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
s.applyTravelDocumentURLs(c.Context(), purchase)
|
s.applyTravelDocumentURLs(c.Context(), purchase)
|
||||||
|
|
||||||
return purchase, nil
|
return purchase, nil
|
||||||
@@ -498,6 +531,54 @@ func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *valid
|
|||||||
return nil, utils.BadRequest("Items must not be empty for staff approval")
|
return nil, utils.BadRequest("Items must not be empty for staff approval")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{}{}
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
payload, err := s.buildStaffAdjustmentPayload(c.Context(), purchase, req, syncReceiving)
|
payload, err := s.buildStaffAdjustmentPayload(c.Context(), purchase, req, syncReceiving)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -745,6 +826,54 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
|||||||
req.Items[idx].TravelDocumentPath = &uploadedURL
|
req.Items[idx].TravelDocumentPath = &uploadedURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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{}{}
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
itemMap := make(map[uint]*entity.PurchaseItem, len(purchase.Items))
|
itemMap := make(map[uint]*entity.PurchaseItem, len(purchase.Items))
|
||||||
for i := range purchase.Items {
|
for i := range purchase.Items {
|
||||||
@@ -1367,6 +1496,30 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transactionErr := s.PurchaseRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) 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)
|
||||||
|
}
|
||||||
|
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 err := s.rollbackPurchaseStock(ctx, tx, itemsToDelete, note, actorID); err != nil {
|
if err := s.rollbackPurchaseStock(ctx, tx, itemsToDelete, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1383,6 +1536,10 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint) error {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if transactionErr != nil {
|
if transactionErr != nil {
|
||||||
|
var fe *fiber.Error
|
||||||
|
if errors.As(transactionErr, &fe) {
|
||||||
|
return fe
|
||||||
|
}
|
||||||
if errors.Is(transactionErr, gorm.ErrRecordNotFound) {
|
if errors.Is(transactionErr, gorm.ErrRecordNotFound) {
|
||||||
return utils.NotFound("Purchase not found")
|
return utils.NotFound("Purchase not found")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user