Update expense for product category (non-inventory type)

This commit is contained in:
2026-06-10 12:42:53 +07:00
parent e09feff36d
commit c3db919531
14 changed files with 444 additions and 170 deletions
+99 -14
View File
@@ -18,6 +18,45 @@ type expenseRepositoryCaptureStub struct {
analytics *entities.ExpenseAnalytics
}
type expensePurchaseCategoryRepositoryStub struct {
category *entities.PurchaseCategory
}
func (*expensePurchaseCategoryRepositoryStub) Create(context.Context, *entities.PurchaseCategory) error {
return nil
}
func (s *expensePurchaseCategoryRepositoryStub) GetByIDAndOrganizationID(context.Context, uuid.UUID, uuid.UUID) (*entities.PurchaseCategory, error) {
return s.category, nil
}
func (*expensePurchaseCategoryRepositoryStub) Update(context.Context, *entities.PurchaseCategory) error {
return nil
}
func (*expensePurchaseCategoryRepositoryStub) SoftDelete(context.Context, uuid.UUID, uuid.UUID) error {
return nil
}
func (*expensePurchaseCategoryRepositoryStub) List(context.Context, uuid.UUID, map[string]interface{}, int, int) ([]*entities.PurchaseCategory, int64, error) {
return nil, 0, nil
}
func (*expensePurchaseCategoryRepositoryStub) ExistsByCode(context.Context, uuid.UUID, string, *uuid.UUID) (bool, error) {
return false, nil
}
func newExpensePurchaseCategoryRepo(categoryID uuid.UUID, categoryType entities.PurchaseCategoryType) *expensePurchaseCategoryRepositoryStub {
return &expensePurchaseCategoryRepositoryStub{
category: &entities.PurchaseCategory{
ID: categoryID,
Name: "Operational",
Type: categoryType,
IsActive: true,
},
}
}
func (s *expenseRepositoryCaptureStub) Create(_ context.Context, expense *entities.Expense) error {
if expense.ID == uuid.Nil {
expense.ID = uuid.New()
@@ -62,7 +101,8 @@ func (*expenseRepositoryCaptureStub) DeleteItemsByExpenseID(context.Context, uui
func TestExpenseProcessorCreatePersistsItemName(t *testing.T) {
repo := &expenseRepositoryCaptureStub{}
p := NewExpenseProcessorImpl(repo)
purchaseCategoryID := uuid.New()
p := NewExpenseProcessorImpl(repo, newExpensePurchaseCategoryRepo(purchaseCategoryID, entities.PurchaseCategoryTypeNonInventory))
chartOfAccountID := uuid.New()
resp, err := p.CreateExpense(context.Background(), uuid.New(), &models.CreateExpenseRequest{
@@ -73,9 +113,10 @@ func TestExpenseProcessorCreatePersistsItemName(t *testing.T) {
Total: 10000,
Items: []models.CreateExpenseItemRequest{
{
ChartOfAccountID: chartOfAccountID.String(),
Item: "Cleaning supplies",
Amount: 10000,
ChartOfAccountID: chartOfAccountID.String(),
PurchaseCategoryID: purchaseCategoryID.String(),
Item: "Cleaning supplies",
Amount: 10000,
},
},
})
@@ -84,13 +125,15 @@ func TestExpenseProcessorCreatePersistsItemName(t *testing.T) {
require.NotNil(t, resp)
require.Len(t, repo.createdItems, 1)
require.Equal(t, "Cleaning supplies", repo.createdItems[0].Item)
require.Equal(t, purchaseCategoryID, repo.createdItems[0].PurchaseCategoryID)
require.Len(t, resp.Items, 1)
require.Equal(t, "Cleaning supplies", resp.Items[0].Item)
}
func TestExpenseProcessorCreateDefaultsStatusToDraft(t *testing.T) {
repo := &expenseRepositoryCaptureStub{}
p := NewExpenseProcessorImpl(repo)
purchaseCategoryID := uuid.New()
p := NewExpenseProcessorImpl(repo, newExpensePurchaseCategoryRepo(purchaseCategoryID, entities.PurchaseCategoryTypeNonInventory))
resp, err := p.CreateExpense(context.Background(), uuid.New(), &models.CreateExpenseRequest{
Receiver: "Cashier",
@@ -100,9 +143,10 @@ func TestExpenseProcessorCreateDefaultsStatusToDraft(t *testing.T) {
Total: 10000,
Items: []models.CreateExpenseItemRequest{
{
ChartOfAccountID: uuid.NewString(),
Item: "Cleaning supplies",
Amount: 10000,
ChartOfAccountID: uuid.NewString(),
PurchaseCategoryID: purchaseCategoryID.String(),
Item: "Cleaning supplies",
Amount: 10000,
},
},
})
@@ -115,7 +159,8 @@ func TestExpenseProcessorCreateDefaultsStatusToDraft(t *testing.T) {
func TestExpenseProcessorCreatePersistsProvidedStatus(t *testing.T) {
repo := &expenseRepositoryCaptureStub{}
p := NewExpenseProcessorImpl(repo)
purchaseCategoryID := uuid.New()
p := NewExpenseProcessorImpl(repo, newExpensePurchaseCategoryRepo(purchaseCategoryID, entities.PurchaseCategoryTypeNonInventory))
status := "approved"
resp, err := p.CreateExpense(context.Background(), uuid.New(), &models.CreateExpenseRequest{
@@ -127,9 +172,10 @@ func TestExpenseProcessorCreatePersistsProvidedStatus(t *testing.T) {
Total: 10000,
Items: []models.CreateExpenseItemRequest{
{
ChartOfAccountID: uuid.NewString(),
Item: "Cleaning supplies",
Amount: 10000,
ChartOfAccountID: uuid.NewString(),
PurchaseCategoryID: purchaseCategoryID.String(),
Item: "Cleaning supplies",
Amount: 10000,
},
},
})
@@ -140,8 +186,35 @@ func TestExpenseProcessorCreatePersistsProvidedStatus(t *testing.T) {
require.Equal(t, "approved", resp.Status)
}
func TestExpenseProcessorCreateRejectsRawMaterialPurchaseCategory(t *testing.T) {
repo := &expenseRepositoryCaptureStub{}
purchaseCategoryID := uuid.New()
p := NewExpenseProcessorImpl(repo, newExpensePurchaseCategoryRepo(purchaseCategoryID, entities.PurchaseCategoryTypeRawMaterial))
resp, err := p.CreateExpense(context.Background(), uuid.New(), &models.CreateExpenseRequest{
Receiver: "Cashier",
TransactionDate: "2026-05-29",
CodeNumber: "EXP-001",
OutletID: uuid.NewString(),
Total: 10000,
Items: []models.CreateExpenseItemRequest{
{
ChartOfAccountID: uuid.NewString(),
PurchaseCategoryID: purchaseCategoryID.String(),
Item: "Cleaning supplies",
Amount: 10000,
},
},
})
require.Error(t, err)
require.Nil(t, resp)
require.Contains(t, err.Error(), "non_inventory")
}
func TestExpenseProcessorGetExpenseAnalyticsDefaultsGroupByAndMapsResponse(t *testing.T) {
coaID := uuid.New()
purchaseCategoryID := uuid.New()
outletID := uuid.New()
now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
repo := &expenseRepositoryCaptureStub{
@@ -165,6 +238,16 @@ func TestExpenseProcessorGetExpenseAnalyticsDefaultsGroupByAndMapsResponse(t *te
},
},
CategoryData: []entities.ExpenseAnalyticsCategoryData{
{
PurchaseCategoryID: purchaseCategoryID,
PurchaseCategoryName: "Operational Supplies",
PurchaseCategoryType: "non_inventory",
TotalAmount: 100000,
ExpenseCount: 2,
ItemCount: 2,
},
},
ChartOfAccountData: []entities.ExpenseAnalyticsChartOfAccountData{
{
ChartOfAccountID: coaID,
ChartOfAccountName: "Operational",
@@ -183,7 +266,7 @@ func TestExpenseProcessorGetExpenseAnalyticsDefaultsGroupByAndMapsResponse(t *te
},
},
}
p := NewExpenseProcessorImpl(repo)
p := NewExpenseProcessorImpl(repo, newExpensePurchaseCategoryRepo(purchaseCategoryID, entities.PurchaseCategoryTypeNonInventory))
resp, err := p.GetExpenseAnalytics(context.Background(), &models.ExpenseAnalyticsRequest{
OrganizationID: uuid.New(),
@@ -200,7 +283,9 @@ func TestExpenseProcessorGetExpenseAnalyticsDefaultsGroupByAndMapsResponse(t *te
require.Len(t, resp.Data, 1)
require.Equal(t, int64(2), resp.Data[0].ExpenseCount)
require.Len(t, resp.CategoryData, 1)
require.Equal(t, coaID, resp.CategoryData[0].ChartOfAccountID)
require.Equal(t, purchaseCategoryID, resp.CategoryData[0].PurchaseCategoryID)
require.Len(t, resp.ChartOfAccountData, 1)
require.Equal(t, coaID, resp.ChartOfAccountData[0].ChartOfAccountID)
require.Len(t, resp.ItemData, 1)
require.Equal(t, "Cleaning supplies", resp.ItemData[0].Item)
}