mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
implement bop for expedition must recheck and qty in staff purchase need info
This commit is contained in:
@@ -58,7 +58,6 @@ type purchaseService struct {
|
||||
type staffAdjustmentPayload struct {
|
||||
PricingUpdates []rPurchase.PurchasePricingUpdate
|
||||
NewItems []*entity.PurchaseItem
|
||||
GrandTotal float64
|
||||
}
|
||||
|
||||
func NewPurchaseService(
|
||||
@@ -71,9 +70,6 @@ func NewPurchaseService(
|
||||
approvalSvc commonSvc.ApprovalService,
|
||||
expenseBridge PurchaseExpenseBridge,
|
||||
) PurchaseService {
|
||||
if expenseBridge == nil {
|
||||
expenseBridge = NewNoopPurchaseExpenseBridge()
|
||||
}
|
||||
return &purchaseService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
@@ -237,9 +233,9 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase
|
||||
if warehouse, ok := warehouseCache[id]; ok {
|
||||
return warehouse, nil
|
||||
}
|
||||
warehouse, err := s.WarehouseRepo.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Area").Preload("location")
|
||||
})
|
||||
warehouse, err := s.WarehouseRepo.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("Area").Preload("Location")
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@@ -291,21 +287,25 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase
|
||||
indexMap[key] = len(aggregated) - 1
|
||||
}
|
||||
|
||||
creditTermValue := req.CreditTerm
|
||||
creditTerm := &creditTermValue
|
||||
dueDateValue := time.Now().UTC().AddDate(0, 0, creditTermValue)
|
||||
dueDate := &dueDateValue
|
||||
var dueDate *time.Time
|
||||
if req.DueDate != nil && strings.TrimSpace(*req.DueDate) != "" {
|
||||
parsed, err := utils.ParseDateString(strings.TrimSpace(*req.DueDate))
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid due_date, expected YYYY-MM-DD")
|
||||
}
|
||||
parsed = parsed.UTC()
|
||||
dueDate = &parsed
|
||||
}
|
||||
|
||||
purchase := &entity.Purchase{
|
||||
SupplierId: uint(req.SupplierID),
|
||||
CreditTerm: creditTerm,
|
||||
DueDate: dueDate,
|
||||
GrandTotal: 0,
|
||||
Notes: req.Notes,
|
||||
CreatedBy: uint(actorID),
|
||||
DueDate: dueDate,
|
||||
Notes: req.Notes,
|
||||
CreatedBy: uint(actorID),
|
||||
}
|
||||
|
||||
items := make([]*entity.PurchaseItem, 0, len(aggregated))
|
||||
emptyVehicle := ""
|
||||
for _, item := range aggregated {
|
||||
items = append(items, &entity.PurchaseItem{
|
||||
ProductId: item.productId,
|
||||
@@ -315,6 +315,7 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase
|
||||
TotalUsed: 0,
|
||||
Price: 0,
|
||||
TotalPrice: 0,
|
||||
VehicleNumber: &emptyVehicle,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -361,6 +362,8 @@ func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *valid
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx := c.Context()
|
||||
|
||||
action, err := parseApprovalActionInput(req.Action)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -371,7 +374,7 @@ func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *valid
|
||||
return nil, err
|
||||
}
|
||||
|
||||
purchase, err := s.PurchaseRepo.GetByID(c.Context(), id, s.withRelations)
|
||||
purchase, err := s.PurchaseRepo.GetByID(ctx, id, s.withRelations)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Purchase not found")
|
||||
@@ -379,7 +382,7 @@ func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *valid
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get purchase")
|
||||
}
|
||||
|
||||
if err := s.attachLatestApproval(c.Context(), purchase); err != nil {
|
||||
if err := s.attachLatestApproval(ctx, purchase); err != nil {
|
||||
s.Log.Warnf("Unable to load latest approval for purchase %d: %+v", id, err)
|
||||
}
|
||||
|
||||
@@ -418,12 +421,10 @@ func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *valid
|
||||
|
||||
transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
purchaseRepoTx := rPurchase.NewPurchaseRepository(tx)
|
||||
grandTotalUpdated := false
|
||||
if len(payload.PricingUpdates) > 0 {
|
||||
if err := purchaseRepoTx.UpdatePricing(c.Context(), purchase.Id, payload.PricingUpdates, payload.GrandTotal); err != nil {
|
||||
if err := purchaseRepoTx.UpdatePricing(c.Context(), purchase.Id, payload.PricingUpdates); err != nil {
|
||||
return err
|
||||
}
|
||||
grandTotalUpdated = true
|
||||
}
|
||||
|
||||
if len(payload.NewItems) > 0 {
|
||||
@@ -432,12 +433,6 @@ func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *valid
|
||||
}
|
||||
}
|
||||
|
||||
if !grandTotalUpdated {
|
||||
if err := purchaseRepoTx.UpdateGrandTotal(c.Context(), purchase.Id, payload.GrandTotal); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if isInitialApproval {
|
||||
if err := s.createPurchaseApproval(c.Context(), tx, purchase.Id, utils.PurchaseStepStaffPurchase, action, actorID, req.Notes, false); err != nil {
|
||||
return err
|
||||
@@ -481,17 +476,6 @@ func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *valid
|
||||
s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", updated.Id, err)
|
||||
}
|
||||
|
||||
if len(payload.NewItems) > 0 {
|
||||
newItems := make([]entity.PurchaseItem, len(payload.NewItems))
|
||||
for i, item := range payload.NewItems {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
newItems[i] = *item
|
||||
}
|
||||
s.notifyExpenseItemsCreated(c.Context(), purchase.Id, newItems)
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
@@ -611,6 +595,8 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx := c.Context()
|
||||
|
||||
action, err := parseApprovalActionInput(req.Action)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -621,7 +607,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
return nil, err
|
||||
}
|
||||
|
||||
purchase, err := s.PurchaseRepo.GetByID(c.Context(), id, s.withRelations)
|
||||
purchase, err := s.PurchaseRepo.GetByID(ctx, id, s.withRelations)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Purchase not found")
|
||||
@@ -647,14 +633,14 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
}
|
||||
|
||||
if action == entity.ApprovalActionRejected {
|
||||
if err := s.createPurchaseApproval(c.Context(), nil, purchase.Id, utils.PurchaseStepReceiving, action, actorID, req.Notes, true); err != nil {
|
||||
if err := s.createPurchaseApproval(ctx, nil, purchase.Id, utils.PurchaseStepReceiving, action, actorID, req.Notes, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updated, err := s.PurchaseRepo.GetByID(c.Context(), purchase.Id, s.withRelations)
|
||||
updated, err := s.PurchaseRepo.GetByID(ctx, purchase.Id, s.withRelations)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load purchase")
|
||||
}
|
||||
if err := s.attachLatestApproval(c.Context(), updated); err != nil {
|
||||
if err := s.attachLatestApproval(ctx, updated); err != nil {
|
||||
s.Log.Warnf("Unable to load latest approval for purchase %d: %+v", id, err)
|
||||
}
|
||||
return updated, nil
|
||||
@@ -670,6 +656,8 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
payload validation.ReceivePurchaseItemRequest
|
||||
receivedDate time.Time
|
||||
warehouseID uint
|
||||
supplierID uint
|
||||
transportPerItem *float64
|
||||
overrideWarehouse bool
|
||||
receivedQty float64
|
||||
}
|
||||
@@ -682,7 +670,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Purchase item %d not found", payload.PurchaseItemID))
|
||||
}
|
||||
|
||||
receivedDate, err := time.Parse("2006-01-02", payload.ReceivedDate)
|
||||
receivedDate, err := utils.ParseDateString(payload.ReceivedDate)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid received_date for item %d", payload.PurchaseItemID))
|
||||
}
|
||||
@@ -716,11 +704,27 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
}
|
||||
visitedItems[payload.PurchaseItemID] = struct{}{}
|
||||
|
||||
supplierID := purchase.SupplierId
|
||||
if payload.ExpeditionVendorID != nil && *payload.ExpeditionVendorID != 0 {
|
||||
supplierID = *payload.ExpeditionVendorID
|
||||
}
|
||||
|
||||
var transportPerItem *float64
|
||||
if payload.TransportPerItem != nil {
|
||||
if *payload.TransportPerItem < 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("transport_per_item for item %d cannot be negative", payload.PurchaseItemID))
|
||||
}
|
||||
val := *payload.TransportPerItem
|
||||
transportPerItem = &val
|
||||
}
|
||||
|
||||
prepared = append(prepared, preparedReceiving{
|
||||
item: item,
|
||||
payload: payload,
|
||||
receivedDate: receivedDate,
|
||||
warehouseID: warehouseID,
|
||||
supplierID: supplierID,
|
||||
transportPerItem: transportPerItem,
|
||||
overrideWarehouse: overrideWarehouse,
|
||||
receivedQty: receivedQty,
|
||||
})
|
||||
@@ -737,7 +741,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
approvalSvc := commonSvc.NewApprovalService(
|
||||
commonRepo.NewApprovalRepository(s.PurchaseRepo.DB()),
|
||||
)
|
||||
|
||||
|
||||
if approvalSvc != nil {
|
||||
filterStep := func(step approvalutils.ApprovalStep) func(*gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
@@ -830,14 +834,6 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.createPurchaseApproval(c.Context(), tx, purchase.Id, utils.PurchaseStepReceiving, receivingAction, actorID, req.Notes, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.createPurchaseApproval(c.Context(), tx, purchase.Id, utils.PurchaseStepCompleted, completedAction, actorID, req.Notes, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if transactionErr != nil {
|
||||
@@ -863,12 +859,28 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
PurchaseItemID: prep.item.Id,
|
||||
ProductID: prep.item.ProductId,
|
||||
WarehouseID: uint(prep.warehouseID),
|
||||
SupplierID: prep.supplierID,
|
||||
TransportPerItem: prep.transportPerItem,
|
||||
ReceivedQty: prep.receivedQty,
|
||||
ReceivedDate: &date,
|
||||
}
|
||||
receivingPayloads = append(receivingPayloads, payload)
|
||||
}
|
||||
s.notifyExpenseItemsReceived(c.Context(), purchase.Id, receivingPayloads)
|
||||
if err := s.notifyExpenseItemsReceived(c, purchase.Id, receivingPayloads); err != nil {
|
||||
s.Log.Errorf("Failed to sync expense for purchase %d: %+v", purchase.Id, err)
|
||||
if fe, ok := err.(*fiber.Error); ok {
|
||||
return nil, fe
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to sync expense")
|
||||
}
|
||||
|
||||
// Create approvals only after expense sync succeeds
|
||||
if err := s.createPurchaseApproval(c.Context(), nil, purchase.Id, utils.PurchaseStepReceiving, receivingAction, actorID, req.Notes, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.createPurchaseApproval(c.Context(), nil, purchase.Id, utils.PurchaseStepCompleted, completedAction, actorID, req.Notes, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
@@ -918,6 +930,17 @@ func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint, req *validation.Del
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Requested items were not found in this purchase")
|
||||
}
|
||||
|
||||
toDeleteSet := make(map[uint]struct{}, len(toDelete))
|
||||
for _, id := range toDelete {
|
||||
toDeleteSet[id] = struct{}{}
|
||||
}
|
||||
itemsToDelete := make([]entity.PurchaseItem, 0, len(toDelete))
|
||||
for _, item := range purchase.Items {
|
||||
if _, ok := toDeleteSet[item.Id]; ok {
|
||||
itemsToDelete = append(itemsToDelete, item)
|
||||
}
|
||||
}
|
||||
|
||||
if len(purchase.Items)-len(toDelete) <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase must keep at least one item")
|
||||
}
|
||||
@@ -929,10 +952,6 @@ func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint, req *validation.Del
|
||||
return err
|
||||
}
|
||||
|
||||
if err := repoTx.UpdateGrandTotal(ctx, purchase.Id, remainingTotal); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if transactionErr != nil {
|
||||
@@ -942,8 +961,14 @@ func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint, req *validation.Del
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete purchase items")
|
||||
}
|
||||
|
||||
if len(toDelete) > 0 {
|
||||
s.notifyExpenseItemsDeleted(ctx, purchase.Id, toDelete)
|
||||
if len(itemsToDelete) > 0 {
|
||||
if err := s.notifyExpenseItemsDeleted(ctx, purchase.Id, itemsToDelete); err != nil {
|
||||
s.Log.Errorf("Failed to sync expense deletion for purchase %d: %+v", purchase.Id, err)
|
||||
if fe, ok := err.(*fiber.Error); ok {
|
||||
return nil, fe
|
||||
}
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to sync expense")
|
||||
}
|
||||
}
|
||||
|
||||
updated, err := s.PurchaseRepo.GetByID(ctx, purchase.Id, s.withRelations)
|
||||
@@ -972,8 +997,10 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint) error {
|
||||
}
|
||||
|
||||
itemIDs := make([]uint, 0, len(purchase.Items))
|
||||
for _, item := range purchase.Items {
|
||||
itemsToDelete := make([]entity.PurchaseItem, len(purchase.Items))
|
||||
for i, item := range purchase.Items {
|
||||
itemIDs = append(itemIDs, item.Id)
|
||||
itemsToDelete[i] = item
|
||||
}
|
||||
|
||||
transactionErr := s.PurchaseRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
@@ -995,38 +1022,130 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint) error {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete purchase")
|
||||
}
|
||||
|
||||
if len(itemIDs) > 0 {
|
||||
s.notifyExpenseItemsDeleted(ctx, uint(id), itemIDs)
|
||||
if len(itemsToDelete) > 0 {
|
||||
if err := s.notifyExpenseItemsDeleted(ctx, uint(id), itemsToDelete); err != nil {
|
||||
s.Log.Errorf("Failed to sync expense deletion for purchase %d: %+v", id, err)
|
||||
if fe, ok := err.(*fiber.Error); ok {
|
||||
return fe
|
||||
}
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to sync expense")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *purchaseService) notifyExpenseItemsCreated(ctx context.Context, purchaseID uint, items []entity.PurchaseItem) {
|
||||
if s.ExpenseBridge == nil || purchaseID == 0 || len(items) == 0 {
|
||||
return
|
||||
func (s *purchaseService) createPurchaseApproval(
|
||||
ctx context.Context,
|
||||
db *gorm.DB,
|
||||
purchaseID uint,
|
||||
step approvalutils.ApprovalStep,
|
||||
action entity.ApprovalAction,
|
||||
actorID uint,
|
||||
notes *string,
|
||||
allowDuplicate bool,
|
||||
) error {
|
||||
if purchaseID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Purchase is invalid for approval")
|
||||
}
|
||||
if err := s.ExpenseBridge.OnItemsCreated(ctx, purchaseID, items); err != nil {
|
||||
s.Log.Warnf("Failed to notify expense bridge for created purchase %d: %+v", purchaseID, err)
|
||||
if actorID == 0 {
|
||||
actorID = 1
|
||||
}
|
||||
|
||||
svc := s.approvalServiceForDB(db)
|
||||
if svc == nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Approval service not available")
|
||||
}
|
||||
|
||||
modifier := func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("step_number = ?", uint16(step))
|
||||
}
|
||||
|
||||
latest, err := svc.LatestByTarget(ctx, utils.ApprovalWorkflowPurchase, uint(purchaseID), modifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !allowDuplicate && latest != nil &&
|
||||
latest.Action != nil &&
|
||||
*latest.Action == action {
|
||||
return nil
|
||||
}
|
||||
|
||||
actionCopy := action
|
||||
_, err = svc.CreateApproval(ctx, utils.ApprovalWorkflowPurchase, uint(purchaseID), step, &actionCopy, actorID, notes)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *purchaseService) notifyExpenseItemsReceived(ctx context.Context, purchaseID uint, payloads []ExpenseReceivingPayload) {
|
||||
func (s *purchaseService) approvalServiceForDB(db *gorm.DB) commonSvc.ApprovalService {
|
||||
if db != nil {
|
||||
return commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(db))
|
||||
}
|
||||
if s.ApprovalSvc != nil {
|
||||
return s.ApprovalSvc
|
||||
}
|
||||
if s.PurchaseRepo != nil && s.PurchaseRepo.DB() != nil {
|
||||
return commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.PurchaseRepo.DB()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *purchaseService) attachLatestApprovals(ctx context.Context, items []entity.Purchase) error {
|
||||
if len(items) == 0 || s.ApprovalSvc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ids := make([]uint, 0, len(items))
|
||||
visited := make(map[uint]struct{}, len(items))
|
||||
for _, item := range items {
|
||||
if item.Id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := visited[item.Id]; ok {
|
||||
continue
|
||||
}
|
||||
visited[item.Id] = struct{}{}
|
||||
ids = append(ids, uint(item.Id))
|
||||
}
|
||||
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
latestMap, err := s.ApprovalSvc.LatestByTargets(ctx, utils.ApprovalWorkflowPurchase, ids, func(db *gorm.DB) *gorm.DB {
|
||||
return db.Preload("ActionUser")
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range items {
|
||||
if items[i].Id == 0 {
|
||||
continue
|
||||
}
|
||||
if approval, ok := latestMap[uint(items[i].Id)]; ok {
|
||||
items[i].LatestApproval = approval
|
||||
} else {
|
||||
items[i].LatestApproval = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *purchaseService) notifyExpenseItemsReceived(c *fiber.Ctx, purchaseID uint, payloads []ExpenseReceivingPayload) error {
|
||||
if s.ExpenseBridge == nil || purchaseID == 0 || len(payloads) == 0 {
|
||||
return
|
||||
}
|
||||
if err := s.ExpenseBridge.OnItemsReceived(ctx, purchaseID, payloads); err != nil {
|
||||
s.Log.Warnf("Failed to notify expense bridge for received purchase %d: %+v", purchaseID, err)
|
||||
return nil
|
||||
}
|
||||
return s.ExpenseBridge.OnItemsReceived(c, purchaseID, payloads)
|
||||
}
|
||||
|
||||
func (s *purchaseService) notifyExpenseItemsDeleted(ctx context.Context, purchaseID uint, itemIDs []uint) {
|
||||
if s.ExpenseBridge == nil || purchaseID == 0 || len(itemIDs) == 0 {
|
||||
return
|
||||
}
|
||||
if err := s.ExpenseBridge.OnItemsDeleted(ctx, purchaseID, itemIDs); err != nil {
|
||||
s.Log.Warnf("Failed to notify expense bridge for deleted purchase %d: %+v", purchaseID, err)
|
||||
func (s *purchaseService) notifyExpenseItemsDeleted(ctx context.Context, purchaseID uint, items []entity.PurchaseItem) error {
|
||||
if s.ExpenseBridge == nil || purchaseID == 0 || len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.ExpenseBridge.OnItemsDeleted(ctx, purchaseID, items)
|
||||
|
||||
}
|
||||
|
||||
func (s *purchaseService) buildStaffAdjustmentPayload(
|
||||
@@ -1054,7 +1173,6 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
||||
}
|
||||
|
||||
updates := make([]rPurchase.PurchasePricingUpdate, 0, len(purchase.Items))
|
||||
var grandTotal float64
|
||||
|
||||
existingCombos := make(map[string]struct{}, len(purchase.Items)+len(newPayloads))
|
||||
for _, item := range purchase.Items {
|
||||
@@ -1119,16 +1237,16 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
||||
update.TotalQty = &qtyCopy
|
||||
}
|
||||
|
||||
updates = append(updates, update)
|
||||
grandTotal += totalPrice
|
||||
delete(requestItems, item.Id)
|
||||
}
|
||||
updates = append(updates, update)
|
||||
delete(requestItems, item.Id)
|
||||
}
|
||||
if len(requestItems) > 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Found pricing data for items that do not belong to this purchase")
|
||||
}
|
||||
|
||||
productSupplierCache := make(map[uint]bool)
|
||||
newItems := make([]*entity.PurchaseItem, 0, len(newPayloads))
|
||||
emptyVehicle := ""
|
||||
|
||||
for _, payload := range newPayloads {
|
||||
if payload.ProductID == 0 || payload.WarehouseID == 0 {
|
||||
@@ -1183,11 +1301,11 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
||||
TotalUsed: 0,
|
||||
Price: payload.Price,
|
||||
TotalPrice: totalPrice,
|
||||
VehicleNumber: &emptyVehicle,
|
||||
}
|
||||
newItems = append(newItems, newItem)
|
||||
existingCombos[key] = struct{}{}
|
||||
}
|
||||
newItems = append(newItems, newItem)
|
||||
existingCombos[key] = struct{}{}
|
||||
grandTotal += totalPrice
|
||||
}
|
||||
|
||||
if len(updates) == 0 && len(newItems) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase has no items to process")
|
||||
@@ -1196,7 +1314,6 @@ func (s *purchaseService) buildStaffAdjustmentPayload(
|
||||
return &staffAdjustmentPayload{
|
||||
PricingUpdates: updates,
|
||||
NewItems: newItems,
|
||||
GrandTotal: grandTotal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1240,32 +1357,10 @@ func (s *purchaseService) attachLatestApproval(ctx context.Context, item *entity
|
||||
}
|
||||
|
||||
func parseQueryDates(fromStr, toStr string) (*time.Time, *time.Time, error) {
|
||||
var fromPtr *time.Time
|
||||
var toPtr *time.Time
|
||||
const queryDateLayout = "2006-01-02"
|
||||
|
||||
if strings.TrimSpace(fromStr) != "" {
|
||||
parsed, err := time.Parse(queryDateLayout, fromStr)
|
||||
if err != nil {
|
||||
return nil, nil, fiber.NewError(fiber.StatusBadRequest, "created_from must use format YYYY-MM-DD")
|
||||
}
|
||||
fromValue := parsed
|
||||
fromPtr = &fromValue
|
||||
fromPtr, toPtr, err := utils.ParseDateRangeForQuery(fromStr, toStr)
|
||||
if err != nil {
|
||||
return nil, nil, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
if strings.TrimSpace(toStr) != "" {
|
||||
parsed, err := time.Parse(queryDateLayout, toStr)
|
||||
if err != nil {
|
||||
return nil, nil, fiber.NewError(fiber.StatusBadRequest, "created_to must use format YYYY-MM-DD")
|
||||
}
|
||||
toValue := parsed.AddDate(0, 0, 1)
|
||||
toPtr = &toValue
|
||||
}
|
||||
|
||||
if fromPtr != nil && toPtr != nil && fromPtr.After(*toPtr) {
|
||||
return nil, nil, fiber.NewError(fiber.StatusBadRequest, "created_from must be earlier than created_to")
|
||||
}
|
||||
|
||||
return fromPtr, toPtr, nil
|
||||
}
|
||||
|
||||
@@ -1302,53 +1397,3 @@ func (s *purchaseService) rejectAndReload(
|
||||
}
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (s *purchaseService) createPurchaseApproval(
|
||||
ctx context.Context,
|
||||
db *gorm.DB,
|
||||
purchaseID uint,
|
||||
step approvalutils.ApprovalStep,
|
||||
action entity.ApprovalAction,
|
||||
actorID uint,
|
||||
notes *string,
|
||||
allowDuplicate bool,
|
||||
) error {
|
||||
if purchaseID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Purchase is invalid for approval")
|
||||
}
|
||||
if actorID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "ActorId is invalid for approval")
|
||||
}
|
||||
|
||||
var svc commonSvc.ApprovalService
|
||||
switch {
|
||||
case db != nil:
|
||||
svc = commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(db))
|
||||
case s.ApprovalSvc != nil:
|
||||
svc = s.ApprovalSvc
|
||||
case s.PurchaseRepo != nil && s.PurchaseRepo.DB() != nil:
|
||||
svc = commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.PurchaseRepo.DB()))
|
||||
}
|
||||
if svc == nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Approval service not available")
|
||||
}
|
||||
|
||||
modifier := func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("step_number = ?", uint16(step))
|
||||
}
|
||||
|
||||
latest, err := svc.LatestByTarget(ctx, utils.ApprovalWorkflowPurchase, uint(purchaseID), modifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !allowDuplicate && latest != nil &&
|
||||
latest.Action != nil &&
|
||||
*latest.Action == action {
|
||||
return nil
|
||||
}
|
||||
|
||||
actionCopy := action
|
||||
_, err = svc.CreateApproval(ctx, utils.ApprovalWorkflowPurchase, uint(purchaseID), step, &actionCopy, actorID, notes)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user