diff --git a/internal/entities/analytics.go b/internal/entities/analytics.go index 4e4e175..a388fdf 100644 --- a/internal/entities/analytics.go +++ b/internal/entities/analytics.go @@ -216,3 +216,12 @@ type ExclusiveSummaryDailyTransaction struct { Amount float64 Source string } + +type ExclusiveSummaryBankBalance struct { + Bank string + OpeningBalance *float64 + IncomingMutation *float64 + OutgoingMutation *float64 + ClosingBalance *float64 + Notes *string +} diff --git a/internal/processor/analytics_processor.go b/internal/processor/analytics_processor.go index 0681351..880555f 100644 --- a/internal/processor/analytics_processor.go +++ b/internal/processor/analytics_processor.go @@ -700,6 +700,23 @@ func (p *AnalyticsProcessorImpl) GetExclusiveSummaryMonthly(ctx context.Context, }) } + bankBalances, err := p.analyticsRepo.GetExclusiveSummaryBankBalances(ctx, req.OrganizationID, req.OutletID) + if err != nil { + return nil, fmt.Errorf("failed to get exclusive summary bank balances: %w", err) + } + + bankBalance := make([]models.ExclusiveSummaryBankBalance, len(bankBalances)) + for i, item := range bankBalances { + bankBalance[i] = models.ExclusiveSummaryBankBalance{ + Bank: item.Bank, + OpeningBalance: item.OpeningBalance, + IncomingMutation: item.IncomingMutation, + OutgoingMutation: item.OutgoingMutation, + ClosingBalance: item.ClosingBalance, + Notes: item.Notes, + } + } + return &models.ExclusiveSummaryMonthlyResponse{ OrganizationID: req.OrganizationID, OutletID: req.OutletID, @@ -713,11 +730,8 @@ func (p *AnalyticsProcessorImpl) GetExclusiveSummaryMonthly(ctx context.Context, NetProfit: fullPeriod.Summary.NetProfit, NetProfitMargin: percentage(fullPeriod.Summary.NetProfit, fullPeriod.Summary.Sales), }, - Periods: periods, - BankBalance: []models.ExclusiveSummaryBankBalance{ - {Bank: "BCA"}, - {Bank: "BRI"}, - }, + Periods: periods, + BankBalance: bankBalance, }, nil } diff --git a/internal/processor/analytics_processor_test.go b/internal/processor/analytics_processor_test.go index a0d9605..8a4d9c0 100644 --- a/internal/processor/analytics_processor_test.go +++ b/internal/processor/analytics_processor_test.go @@ -16,6 +16,7 @@ type analyticsRepositoryStub struct { purchasingResult *entities.PurchasingAnalytics profitLossResult *entities.ProfitLossAnalytics exclusiveSummaryResults []*entities.ExclusiveSummaryAnalytics + bankBalances []entities.ExclusiveSummaryBankBalance profitLossGroup string exclusiveSummaryCalls int } @@ -59,6 +60,10 @@ func (s *analyticsRepositoryStub) GetExclusiveSummaryAnalytics(context.Context, return &entities.ExclusiveSummaryAnalytics{}, nil } +func (s *analyticsRepositoryStub) GetExclusiveSummaryBankBalances(context.Context, uuid.UUID, *uuid.UUID) ([]entities.ExclusiveSummaryBankBalance, error) { + return s.bankBalances, nil +} + type expenseRepositoryStub struct{} func (expenseRepositoryStub) Create(context.Context, *entities.Expense) error { return nil } @@ -343,6 +348,9 @@ func TestAnalyticsProcessorGetExclusiveSummaryMonthlyBuildsSummaryAndBuckets(t * location, err := time.LoadLocation("Asia/Jakarta") require.NoError(t, err) month := time.Date(2026, 5, 1, 0, 0, 0, 0, location) + openingBalance := 5000000.0 + closingBalance := 5000000.0 + notes := "Main cash account for daily transactions" stub := &analyticsRepositoryStub{ exclusiveSummaryResults: []*entities.ExclusiveSummaryAnalytics{ {SalesTotal: 1000, HPPBreakdown: []entities.ExclusiveSummaryCategoryTotal{{Amount: 400}}, OperationalExpenseBreakdown: []entities.ExclusiveSummaryCategoryTotal{{Amount: 100}}}, @@ -352,6 +360,9 @@ func TestAnalyticsProcessorGetExclusiveSummaryMonthlyBuildsSummaryAndBuckets(t * {SalesTotal: 400, HPPBreakdown: []entities.ExclusiveSummaryCategoryTotal{{Amount: 160}}}, {SalesTotal: 500, HPPBreakdown: []entities.ExclusiveSummaryCategoryTotal{{Amount: 200}}}, }, + bankBalances: []entities.ExclusiveSummaryBankBalance{ + {Bank: "Cash and Bank", OpeningBalance: &openingBalance, ClosingBalance: &closingBalance, Notes: ¬es}, + }, } processor := NewAnalyticsProcessorImpl(stub, expenseRepositoryStub{}) @@ -370,7 +381,15 @@ func TestAnalyticsProcessorGetExclusiveSummaryMonthlyBuildsSummaryAndBuckets(t * require.Len(t, result.Periods, 5) require.Equal(t, "1 - 3 Mei", result.Periods[0].Label) require.Equal(t, "25 - 31 Mei", result.Periods[4].Label) - require.Len(t, result.BankBalance, 2) - require.Equal(t, "BCA", result.BankBalance[0].Bank) + require.Len(t, result.BankBalance, 1) + require.Equal(t, "Cash and Bank", result.BankBalance[0].Bank) + require.NotNil(t, result.BankBalance[0].OpeningBalance) + require.Equal(t, openingBalance, *result.BankBalance[0].OpeningBalance) + require.NotNil(t, result.BankBalance[0].ClosingBalance) + require.Equal(t, closingBalance, *result.BankBalance[0].ClosingBalance) + require.Nil(t, result.BankBalance[0].IncomingMutation) + require.Nil(t, result.BankBalance[0].OutgoingMutation) + require.NotNil(t, result.BankBalance[0].Notes) + require.Equal(t, notes, *result.BankBalance[0].Notes) require.Equal(t, 6, stub.exclusiveSummaryCalls) } diff --git a/internal/repository/analytics_repository.go b/internal/repository/analytics_repository.go index 097cc0a..a402d4f 100644 --- a/internal/repository/analytics_repository.go +++ b/internal/repository/analytics_repository.go @@ -19,6 +19,7 @@ type AnalyticsRepository interface { GetDashboardOverview(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) (*entities.DashboardOverview, error) GetProfitLossAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time, groupBy string) (*entities.ProfitLossAnalytics, error) GetExclusiveSummaryAnalytics(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, dateFrom, dateTo time.Time) (*entities.ExclusiveSummaryAnalytics, error) + GetExclusiveSummaryBankBalances(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]entities.ExclusiveSummaryBankBalance, error) } type AnalyticsRepositoryImpl struct { @@ -854,3 +855,47 @@ func (r *AnalyticsRepositoryImpl) exclusiveSummaryPurchaseOrderItemQuery(organiz return query, args } + +func (r *AnalyticsRepositoryImpl) GetExclusiveSummaryBankBalances(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]entities.ExclusiveSummaryBankBalance, error) { + type accountBalance struct { + Name string + OpeningBalance float64 + CurrentBalance float64 + Description *string + } + + var accounts []accountBalance + query := r.db.WithContext(ctx). + Table("accounts"). + Select("name, opening_balance, current_balance, description"). + Where("organization_id = ?", organizationID). + Where("account_type IN ?", []entities.AccountType{entities.AccountTypeCash, entities.AccountTypeWallet, entities.AccountTypeBank}). + Where("is_active = ?", true) + + if outletID != nil { + query = query.Where("outlet_id = ? OR outlet_id IS NULL", *outletID) + } else { + query = query.Where("outlet_id IS NULL") + } + + err := query. + Order("number ASC, name ASC"). + Scan(&accounts).Error + if err != nil { + return nil, err + } + + balances := make([]entities.ExclusiveSummaryBankBalance, len(accounts)) + for i, account := range accounts { + openingBalance := account.OpeningBalance + closingBalance := account.CurrentBalance + balances[i] = entities.ExclusiveSummaryBankBalance{ + Bank: account.Name, + OpeningBalance: &openingBalance, + ClosingBalance: &closingBalance, + Notes: account.Description, + } + } + + return balances, nil +}