mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat(BE-229,234,235,230,231,232,233): purchase request and purchase order and fix master data dto
This commit is contained in:
@@ -355,7 +355,6 @@ func (s *purchaseService) processStaffPurchaseApproval(c *fiber.Ctx, id uint64,
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase is not ready for staff approval")
|
||||
}
|
||||
|
||||
// Detect if purchase already has any receiving data on its items.
|
||||
hasReceivingData := false
|
||||
for _, item := range purchase.Items {
|
||||
if item.TotalQty > 0 || item.TotalUsed > 0 {
|
||||
@@ -364,8 +363,6 @@ func (s *purchaseService) processStaffPurchaseApproval(c *fiber.Ctx, id uint64,
|
||||
}
|
||||
}
|
||||
|
||||
// After there is receiving data, staff edits are allowed to change quantity
|
||||
// in sync with receiving, without creating new receiving approvals here.
|
||||
syncReceiving := !isInitialApproval && hasReceivingData
|
||||
|
||||
payload, err := s.buildStaffAdjustmentPayload(ctx, purchase, req, syncReceiving)
|
||||
@@ -611,6 +608,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati
|
||||
receivedQty float64
|
||||
}
|
||||
|
||||
visitedItems := make(map[uint64]struct{}, len(req.Items))
|
||||
prepared := make([]preparedReceiving, 0, len(req.Items))
|
||||
for _, payload := range req.Items {
|
||||
item, exists := itemMap[payload.PurchaseItemID]
|
||||
@@ -647,6 +645,11 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Received quantity for item %d cannot exceed ordered quantity (%.3f)", payload.PurchaseItemID, item.SubQty))
|
||||
}
|
||||
|
||||
if _, dup := visitedItems[payload.PurchaseItemID]; dup {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Duplicate receiving data for item %d", payload.PurchaseItemID))
|
||||
}
|
||||
visitedItems[payload.PurchaseItemID] = struct{}{}
|
||||
|
||||
prepared = append(prepared, preparedReceiving{
|
||||
item: item,
|
||||
payload: payload,
|
||||
@@ -657,6 +660,12 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati
|
||||
})
|
||||
}
|
||||
|
||||
// Require receiving payload to cover all purchase items so that
|
||||
// receiving cannot be submitted partially item-by-item.
|
||||
if len(visitedItems) != len(itemMap) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Receiving data must be provided for all purchase items")
|
||||
}
|
||||
|
||||
receivingAction := entity.ApprovalActionApproved
|
||||
completedAction := entity.ApprovalActionApproved
|
||||
actorID := uint(1)
|
||||
@@ -1085,57 +1094,21 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "No available warehouses for this purchase")
|
||||
}
|
||||
|
||||
productSupplierCache := make(map[uint64]bool)
|
||||
|
||||
for _, item := range purchase.Items {
|
||||
data, ok := requestItems[item.Id]
|
||||
if !ok {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Missing pricing data for item %d", item.Id))
|
||||
}
|
||||
if data.ProductID != 0 && data.ProductID != item.ProductId {
|
||||
return nil, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Cannot change product for item %d. Delete the item and create a new one instead", item.Id),
|
||||
)
|
||||
}
|
||||
if data.WarehouseID != 0 && data.WarehouseID != item.WarehouseId {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Warehouse mismatch for item %d", item.Id))
|
||||
}
|
||||
|
||||
effectiveProductID := item.ProductId
|
||||
if data.ProductID != 0 && data.ProductID != item.ProductId {
|
||||
if item.TotalQty > 0 || item.TotalUsed > 0 {
|
||||
return nil, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Cannot change product for item %d because it already has receiving data", item.Id),
|
||||
)
|
||||
}
|
||||
|
||||
if _, checked := productSupplierCache[data.ProductID]; !checked {
|
||||
linked, err := s.ProductRepo.IsLinkedToSupplier(ctx, uint(data.ProductID), uint(purchase.SupplierId))
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to validate product %d for supplier %d: %+v", data.ProductID, purchase.SupplierId, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product for supplier")
|
||||
}
|
||||
if !linked {
|
||||
return nil, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Product %d is not linked to supplier %d", data.ProductID, purchase.SupplierId),
|
||||
)
|
||||
}
|
||||
productSupplierCache[data.ProductID] = true
|
||||
}
|
||||
|
||||
oldKey := fmt.Sprintf("%d:%d", item.ProductId, item.WarehouseId)
|
||||
newKey := fmt.Sprintf("%d:%d", data.ProductID, item.WarehouseId)
|
||||
if _, exists := existingCombos[newKey]; exists && newKey != oldKey {
|
||||
return nil, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
fmt.Sprintf("Product %d in warehouse %d already exists in this purchase", data.ProductID, item.WarehouseId),
|
||||
)
|
||||
}
|
||||
if newKey != oldKey {
|
||||
delete(existingCombos, oldKey)
|
||||
existingCombos[newKey] = struct{}{}
|
||||
}
|
||||
|
||||
effectiveProductID = data.ProductID
|
||||
}
|
||||
|
||||
effectiveQty := item.SubQty
|
||||
if data.Qty != nil {
|
||||
if *data.Qty <= 0 {
|
||||
@@ -1161,10 +1134,6 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
||||
Price: data.Price,
|
||||
TotalPrice: totalPrice,
|
||||
}
|
||||
if effectiveProductID != item.ProductId {
|
||||
productIDCopy := effectiveProductID
|
||||
update.ProductID = &productIDCopy
|
||||
}
|
||||
if data.Qty != nil {
|
||||
qtyCopy := effectiveQty
|
||||
update.Quantity = &qtyCopy
|
||||
@@ -1182,6 +1151,7 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Found pricing data for items that do not belong to this purchase")
|
||||
}
|
||||
|
||||
productSupplierCache := make(map[uint64]bool)
|
||||
newItems := make([]*entity.PurchaseItem, 0, len(newPayloads))
|
||||
|
||||
for _, payload := range newPayloads {
|
||||
|
||||
@@ -3,7 +3,7 @@ package validation
|
||||
type PurchaseItemPayload struct {
|
||||
WarehouseID uint `json:"warehouse_id" validate:"required,gt=0"`
|
||||
ProductID uint `json:"product_id" validate:"required,gt=0"`
|
||||
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
||||
Quantity float64 `json:"qty" validate:"required,gt=0"`
|
||||
}
|
||||
|
||||
type CreatePurchaseRequest struct {
|
||||
@@ -14,12 +14,13 @@ type CreatePurchaseRequest struct {
|
||||
}
|
||||
|
||||
type StaffPurchaseApprovalItem struct {
|
||||
PurchaseItemID uint64 `json:"purchase_item_id,omitempty" validate:"omitempty,gt=0"`
|
||||
ProductID uint64 `json:"product_id" validate:"required,gt=0"`
|
||||
WarehouseID uint64 `json:"warehouse_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
||||
Qty *float64 `json:"qty,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
||||
Price float64 `json:"price" validate:"required,gt=0"`
|
||||
TotalPrice float64 `json:"total_price" validate:"required,gt=0"`
|
||||
PurchaseItemID uint64 `json:"purchase_item_id,omitempty" validate:"omitempty,gt=0"`
|
||||
// For new items (no purchase_item_id), product_id is required.
|
||||
ProductID uint64 `json:"product_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
||||
WarehouseID uint64 `json:"warehouse_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
||||
Qty *float64 `json:"qty,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
||||
Price float64 `json:"price" validate:"required,gt=0"`
|
||||
TotalPrice float64 `json:"total_price" validate:"required,gt=0"`
|
||||
}
|
||||
|
||||
type ApproveStaffPurchaseRequest struct {
|
||||
|
||||
Reference in New Issue
Block a user