diff --git a/internal/entities/analytics.go b/internal/entities/analytics.go index 14156ce..c763dd9 100644 --- a/internal/entities/analytics.go +++ b/internal/entities/analytics.go @@ -208,3 +208,11 @@ type ExclusiveSummaryDailyTransaction struct { Amount float64 Source string } + +type ExclusiveSummaryBankBalance struct { + Bank string + AccountType string + OpeningBalance float64 + ClosingBalance float64 + Description *string +} diff --git a/internal/processor/analytics_processor.go b/internal/processor/analytics_processor.go index a73016f..8716e15 100644 --- a/internal/processor/analytics_processor.go +++ b/internal/processor/analytics_processor.go @@ -9,6 +9,8 @@ import ( "apskel-pos-be/internal/entities" "apskel-pos-be/internal/models" "apskel-pos-be/internal/repository" + + "github.com/google/uuid" ) type AnalyticsProcessor interface { @@ -669,6 +671,11 @@ func (p *AnalyticsProcessorImpl) GetExclusiveSummaryMonthly(ctx context.Context, return nil, err } + bankBalance, err := p.buildExclusiveSummaryBankBalances(ctx, req.OrganizationID, req.OutletID) + if err != nil { + return nil, err + } + buckets := buildExclusiveSummaryMonthlyBuckets(monthStart) periods := make([]models.ExclusiveSummaryMonthlyPeriod, 0, len(buckets)) for _, bucket := range buckets { @@ -706,14 +713,39 @@ 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 } +func (p *AnalyticsProcessorImpl) buildExclusiveSummaryBankBalances(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.ExclusiveSummaryBankBalance, error) { + balances, err := p.analyticsRepo.GetExclusiveSummaryBankBalances(ctx, organizationID, outletID) + if err != nil { + return nil, fmt.Errorf("failed to get exclusive summary bank balances: %w", err) + } + + result := make([]models.ExclusiveSummaryBankBalance, len(balances)) + for i, balance := range balances { + openingBalance := balance.OpeningBalance + closingBalance := balance.ClosingBalance + notes := strings.TrimSpace(balance.AccountType) + if balance.Description != nil && strings.TrimSpace(*balance.Description) != "" { + notes = strings.TrimSpace(*balance.Description) + } + + result[i] = models.ExclusiveSummaryBankBalance{ + Bank: balance.Bank, + OpeningBalance: &openingBalance, + ClosingBalance: &closingBalance, + } + if notes != "" { + result[i].Notes = ¬es + } + } + + return result, nil +} + func (p *AnalyticsProcessorImpl) buildExclusiveSummaryPeriod(ctx context.Context, req *models.ExclusiveSummaryPeriodRequest) (*models.ExclusiveSummaryPeriodResponse, error) { result, err := p.analyticsRepo.GetExclusiveSummaryAnalytics(ctx, req.OrganizationID, req.OutletID, req.DateFrom, req.DateTo) if err != nil { diff --git a/internal/processor/analytics_processor_test.go b/internal/processor/analytics_processor_test.go index ae29d1a..3b63381 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 exclusiveResult *entities.ExclusiveSummaryAnalytics + bankBalances []entities.ExclusiveSummaryBankBalance profitLossGroup string } @@ -52,6 +53,10 @@ func (s analyticsRepositoryStub) GetExclusiveSummaryAnalytics(context.Context, u return s.exclusiveResult, 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 } @@ -310,6 +315,10 @@ func TestAnalyticsProcessorGetExclusiveSummaryMonthlyBuildsCalendarBucketsAndBan {CategoryCode: "ops", CategoryName: "OPS", Amount: 100}, }, }, + bankBalances: []entities.ExclusiveSummaryBankBalance{ + {Bank: "Rekening Bank", AccountType: "wallet", OpeningBalance: 1000, ClosingBalance: 2500}, + {Bank: "Kas Utama", AccountType: "cash", OpeningBalance: 3000, ClosingBalance: 3500}, + }, }, expenseRepositoryStub{}) result, err := processor.GetExclusiveSummaryMonthly(context.Background(), &models.ExclusiveSummaryMonthlyRequest{ @@ -326,6 +335,14 @@ func TestAnalyticsProcessorGetExclusiveSummaryMonthlyBuildsCalendarBucketsAndBan 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.Equal(t, "BRI", result.BankBalance[1].Bank) + require.Equal(t, "Rekening Bank", result.BankBalance[0].Bank) + require.NotNil(t, result.BankBalance[0].OpeningBalance) + require.Equal(t, float64(1000), *result.BankBalance[0].OpeningBalance) + require.NotNil(t, result.BankBalance[0].ClosingBalance) + require.Equal(t, float64(2500), *result.BankBalance[0].ClosingBalance) + require.NotNil(t, result.BankBalance[0].Notes) + require.Equal(t, "wallet", *result.BankBalance[0].Notes) + require.Nil(t, result.BankBalance[0].IncomingMutation) + require.Nil(t, result.BankBalance[0].OutgoingMutation) + require.Equal(t, "Kas Utama", result.BankBalance[1].Bank) } diff --git a/internal/repository/analytics_repository.go b/internal/repository/analytics_repository.go index c87111e..b5b95fb 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 { @@ -853,3 +854,30 @@ func (r *AnalyticsRepositoryImpl) exclusiveSummaryPurchaseOrderItemsQuery(organi return query, args } + +func (r *AnalyticsRepositoryImpl) GetExclusiveSummaryBankBalances(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]entities.ExclusiveSummaryBankBalance, error) { + var results []entities.ExclusiveSummaryBankBalance + + query := r.db.WithContext(ctx). + Table("accounts"). + Select(` + name as bank, + account_type, + opening_balance, + current_balance as closing_balance, + description + `). + Where("organization_id = ?", organizationID). + Where("is_active = ?", true). + Where("account_type IN ?", []entities.AccountType{entities.AccountTypeBank, entities.AccountTypeWallet, entities.AccountTypeCash}) + + if outletID != nil { + query = query.Where("outlet_id = ? OR outlet_id IS NULL", *outletID) + } + + err := query. + Order("CASE account_type WHEN 'bank' THEN 1 WHEN 'wallet' THEN 2 WHEN 'cash' THEN 3 ELSE 4 END, number ASC, name ASC"). + Scan(&results).Error + + return results, err +}