mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 15:25:43 +00:00
update purchase triger to expense
This commit is contained in:
@@ -47,6 +47,10 @@ type groupedItem struct {
|
||||
totalPrice float64
|
||||
}
|
||||
|
||||
func groupingKey(supplierID uint, date time.Time, warehouseID uint) string {
|
||||
return fmt.Sprintf("%d:%s:%d", supplierID, utils.FormatDate(date), warehouseID)
|
||||
}
|
||||
|
||||
// expenseBridge is the real implementation that syncs purchases to expenses on receiving/deletion.
|
||||
type expenseBridge struct {
|
||||
db *gorm.DB
|
||||
@@ -232,6 +236,33 @@ func (b *expenseBridge) cleanupExistingNonstocks(ctx context.Context, updates []
|
||||
})
|
||||
}
|
||||
|
||||
// cleanupEmptyExpenses deletes expense headers (and approvals) that have no nonstocks.
|
||||
func (b *expenseBridge) cleanupEmptyExpenses(ctx context.Context, expenseIDs []uint64) error {
|
||||
if len(expenseIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
return b.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
approvalRepoTx := commonRepo.NewApprovalRepository(tx)
|
||||
for _, id := range expenseIDs {
|
||||
var count int64
|
||||
if err := tx.Model(&entity.ExpenseNonstock{}).
|
||||
Where("expense_id = ?", id).
|
||||
Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
if err := approvalRepoTx.DeleteByTarget(ctx, utils.ApprovalWorkflowExpense.String(), uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&entity.Expense{}, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates []ExpenseReceivingPayload) error {
|
||||
if purchaseID == 0 || len(updates) == 0 {
|
||||
return nil
|
||||
@@ -260,6 +291,7 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
}
|
||||
|
||||
itemLinks := make(map[uint]itemLink)
|
||||
existingExpenseByKey := make(map[string]uint64)
|
||||
if len(updates) > 0 {
|
||||
ids := make([]uint, 0, len(updates))
|
||||
for _, upd := range updates {
|
||||
@@ -286,6 +318,7 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
Scan(&rows).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// Build quick lookup per item and per group key for existing expenses.
|
||||
for _, row := range rows {
|
||||
itemLinks[row.ItemID] = itemLink{
|
||||
ExpenseNonstockID: row.ExpenseNonstockID,
|
||||
@@ -295,6 +328,16 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
Qty: row.Qty,
|
||||
Price: row.Price,
|
||||
}
|
||||
if row.ExpenseID != 0 && row.SupplierID != 0 && !row.TransactionDate.IsZero() {
|
||||
// Use warehouse from purchase item; if not found, skip key.
|
||||
for i := range purchase.Items {
|
||||
if purchase.Items[i].Id == row.ItemID {
|
||||
key := groupingKey(row.SupplierID, row.TransactionDate.UTC().Truncate(24*time.Hour), purchase.Items[i].WarehouseId)
|
||||
existingExpenseByKey[key] = row.ExpenseID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,6 +350,8 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
groups := make(map[string][]groupedItem)
|
||||
toRecreate := make([]ExpenseReceivingPayload, 0)
|
||||
|
||||
movedFrom := make([]uint64, 0)
|
||||
|
||||
for _, payload := range updates {
|
||||
if payload.ReceivedDate == nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "received_date is required")
|
||||
@@ -338,40 +383,31 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
pricePerItem = *payload.TransportPerItem
|
||||
}
|
||||
|
||||
// If any change other than received_date / supplier occurs (e.g., price or qty), delete-then-create.
|
||||
if (link.Price != pricePerItem) || (link.Qty != payload.ReceivedQty) {
|
||||
requiresDelete = true
|
||||
} else if oldSupplier != supplierID || !oldDate.Equal(newDate) {
|
||||
// Supplier/date change: keep (update) if this expense only has one nonstock; otherwise recreate to avoid affecting others.
|
||||
var count int64
|
||||
if err := b.db.WithContext(ctx).
|
||||
Model(&entity.ExpenseNonstock{}).
|
||||
Where("expense_id = ?", link.ExpenseID).
|
||||
Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count <= 1 {
|
||||
// Update expense header supplier/date in-place.
|
||||
if err := b.db.WithContext(ctx).
|
||||
Model(&entity.Expense{}).
|
||||
Where("id = ?", link.ExpenseID).
|
||||
Updates(map[string]interface{}{
|
||||
"supplier_id": supplierID,
|
||||
"transaction_date": newDate,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// Update note just in case.
|
||||
// Supplier/date change: prefer re-link to existing expense in target group; otherwise recreate.
|
||||
if oldSupplier != supplierID || !oldDate.Equal(newDate) {
|
||||
newKey := groupingKey(supplierID, newDate, payload.WarehouseID)
|
||||
if targetExpenseID, ok := existingExpenseByKey[newKey]; ok && targetExpenseID != 0 {
|
||||
// Move nonstock to existing expense header in the target group.
|
||||
note := fmt.Sprintf("purchase_item:%d", payload.PurchaseItemID)
|
||||
pricePerItem := item.Price
|
||||
if payload.TransportPerItem != nil {
|
||||
pricePerItem = *payload.TransportPerItem
|
||||
}
|
||||
if err := b.db.WithContext(ctx).
|
||||
Model(&entity.ExpenseNonstock{}).
|
||||
Where("id = ?", link.ExpenseNonstockID).
|
||||
Updates(map[string]interface{}{
|
||||
"notes": note,
|
||||
"expense_id": targetExpenseID,
|
||||
"qty": payload.ReceivedQty,
|
||||
"price": pricePerItem,
|
||||
"notes": note,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// Continue to grouping with updated header.
|
||||
// Track cleanup for old header if it becomes empty.
|
||||
movedFrom = append(movedFrom, link.ExpenseID)
|
||||
existingExpenseByKey[newKey] = targetExpenseID
|
||||
handledUpdate = true
|
||||
} else {
|
||||
requiresDelete = true
|
||||
}
|
||||
@@ -379,10 +415,6 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
|
||||
// If we reach here and no delete is required, update the existing nonstock fields and skip creation.
|
||||
if !requiresDelete {
|
||||
pricePerItem := item.Price
|
||||
if payload.TransportPerItem != nil {
|
||||
pricePerItem = *payload.TransportPerItem
|
||||
}
|
||||
note := fmt.Sprintf("purchase_item:%d", payload.PurchaseItemID)
|
||||
if err := b.db.WithContext(ctx).
|
||||
Model(&entity.ExpenseNonstock{}).
|
||||
@@ -511,6 +543,13 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup old expense headers that became empty after re-link.
|
||||
if len(movedFrom) > 0 {
|
||||
if err := b.cleanupEmptyExpenses(ctx, movedFrom); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user