package processor import ( "context" "testing" "time" "apskel-pos-be/internal/entities" "apskel-pos-be/internal/models" "github.com/google/uuid" "github.com/stretchr/testify/require" ) type expenseRepositoryCaptureStub struct { createdExpense *entities.Expense createdItems []*entities.ExpenseItem analytics *entities.ExpenseAnalytics } func (s *expenseRepositoryCaptureStub) Create(_ context.Context, expense *entities.Expense) error { if expense.ID == uuid.Nil { expense.ID = uuid.New() } s.createdExpense = expense return nil } func (s *expenseRepositoryCaptureStub) GetByID(context.Context, uuid.UUID) (*entities.Expense, error) { if s.createdExpense == nil { return nil, nil } items := make([]entities.ExpenseItem, len(s.createdItems)) for i, item := range s.createdItems { items[i] = *item } s.createdExpense.Items = items return s.createdExpense, nil } func (*expenseRepositoryCaptureStub) GetByIDAndOrganizationID(context.Context, uuid.UUID, uuid.UUID) (*entities.Expense, error) { return nil, nil } func (*expenseRepositoryCaptureStub) Update(context.Context, *entities.Expense) error { return nil } func (*expenseRepositoryCaptureStub) Delete(context.Context, uuid.UUID) error { return nil } func (*expenseRepositoryCaptureStub) List(context.Context, uuid.UUID, map[string]interface{}, int, int) ([]*entities.Expense, int64, error) { return nil, 0, nil } func (s *expenseRepositoryCaptureStub) GetAnalytics(context.Context, uuid.UUID, *uuid.UUID, time.Time, time.Time, string) (*entities.ExpenseAnalytics, error) { return s.analytics, nil } func (s *expenseRepositoryCaptureStub) CreateItem(_ context.Context, item *entities.ExpenseItem) error { if item.ID == uuid.Nil { item.ID = uuid.New() } s.createdItems = append(s.createdItems, item) return nil } func (*expenseRepositoryCaptureStub) DeleteItemsByExpenseID(context.Context, uuid.UUID) error { return nil } func TestExpenseProcessorCreatePersistsItemName(t *testing.T) { repo := &expenseRepositoryCaptureStub{} p := NewExpenseProcessorImpl(repo) chartOfAccountID := uuid.New() 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: chartOfAccountID.String(), Item: "Cleaning supplies", Amount: 10000, }, }, }) require.NoError(t, err) require.NotNil(t, resp) require.Len(t, repo.createdItems, 1) require.Equal(t, "Cleaning supplies", repo.createdItems[0].Item) 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) 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(), Item: "Cleaning supplies", Amount: 10000, }, }, }) require.NoError(t, err) require.NotNil(t, resp) require.Equal(t, "draft", repo.createdExpense.Status) require.Equal(t, "draft", resp.Status) } func TestExpenseProcessorCreatePersistsProvidedStatus(t *testing.T) { repo := &expenseRepositoryCaptureStub{} p := NewExpenseProcessorImpl(repo) status := "approved" resp, err := p.CreateExpense(context.Background(), uuid.New(), &models.CreateExpenseRequest{ Receiver: "Cashier", TransactionDate: "2026-05-29", CodeNumber: "EXP-001", OutletID: uuid.NewString(), Status: &status, Total: 10000, Items: []models.CreateExpenseItemRequest{ { ChartOfAccountID: uuid.NewString(), Item: "Cleaning supplies", Amount: 10000, }, }, }) require.NoError(t, err) require.NotNil(t, resp) require.Equal(t, "approved", repo.createdExpense.Status) require.Equal(t, "approved", resp.Status) } func TestExpenseProcessorGetExpenseAnalyticsDefaultsGroupByAndMapsResponse(t *testing.T) { coaID := uuid.New() outletID := uuid.New() now := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC) repo := &expenseRepositoryCaptureStub{ analytics: &entities.ExpenseAnalytics{ Summary: entities.ExpenseAnalyticsSummary{ TotalExpenses: 100000, TotalExpenseCount: 2, TotalTax: 10000, AverageExpenseValue: 50000, TotalCategories: 1, TotalItems: 2, }, Data: []entities.ExpenseAnalyticsData{ { Date: now, Expenses: 100000, ExpenseCount: 2, Tax: 10000, Items: 2, Categories: 1, }, }, CategoryData: []entities.ExpenseAnalyticsCategoryData{ { ChartOfAccountID: coaID, ChartOfAccountName: "Operational", TotalAmount: 100000, ExpenseCount: 2, ItemCount: 2, }, }, ItemData: []entities.ExpenseAnalyticsItemData{ { Item: "Cleaning supplies", TotalAmount: 100000, ExpenseCount: 2, ItemCount: 2, }, }, }, } p := NewExpenseProcessorImpl(repo) resp, err := p.GetExpenseAnalytics(context.Background(), &models.ExpenseAnalyticsRequest{ OrganizationID: uuid.New(), OutletID: &outletID, DateFrom: now, DateTo: now, }) require.NoError(t, err) require.NotNil(t, resp) require.Equal(t, "day", resp.GroupBy) require.Equal(t, &outletID, resp.OutletID) require.Equal(t, float64(100000), resp.Summary.TotalExpenses) 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.Len(t, resp.ItemData, 1) require.Equal(t, "Cleaning supplies", resp.ItemData[0].Item) }