From 8c4d9c69d057adc0e1319604ed589a3f4ce505ab Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 15 Jun 2026 14:17:48 +0700 Subject: [PATCH] Update analytic to support new categories change --- internal/repository/analytics_repository.go | 17 +++++---- ..._expense_items_expense_categories.down.sql | 5 +++ ...ce_expense_items_expense_categories.up.sql | 38 +++++++++++++++++++ 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 migrations/000082_enforce_expense_items_expense_categories.down.sql create mode 100644 migrations/000082_enforce_expense_items_expense_categories.up.sql diff --git a/internal/repository/analytics_repository.go b/internal/repository/analytics_repository.go index 0ace0cb..9877230 100644 --- a/internal/repository/analytics_repository.go +++ b/internal/repository/analytics_repository.go @@ -659,11 +659,11 @@ func (r *AnalyticsRepositoryImpl) getExpenseByCategory(ctx context.Context, orga query := r.db.WithContext(ctx). Table("expense_items ei"). - Select(`COALESCE(parent_coa.name, coa.name, 'Lain-lain') as category_name, COALESCE(SUM(ei.amount), 0) as amount`). + Select(`pc.name as category_name, COALESCE(SUM(ei.amount), 0) as amount`). Joins("JOIN expenses e ON ei.expense_id = e.id"). - Joins("JOIN chart_of_accounts coa ON ei.chart_of_account_id = coa.id"). - Joins("LEFT JOIN chart_of_accounts parent_coa ON coa.parent_id = parent_coa.id"). + Joins("JOIN purchase_categories pc ON ei.purchase_category_id = pc.id"). Where("e.organization_id = ?", organizationID). + Where("pc.type = ?", entities.PurchaseCategoryTypeExpense). Where("e.status = ?", "approved"). Where("e.transaction_date >= ? AND e.transaction_date <= ?", dateFrom, dateTo) @@ -672,8 +672,8 @@ func (r *AnalyticsRepositoryImpl) getExpenseByCategory(ctx context.Context, orga } err := query. - Group("COALESCE(parent_coa.name, coa.name, 'Lain-lain')"). - Order("COALESCE(parent_coa.name, coa.name, 'Lain-lain')"). + Group("pc.id, pc.name, pc.sort_order"). + Order("pc.sort_order ASC, pc.name ASC"). Scan(&results).Error return results, err @@ -684,10 +684,11 @@ func (r *AnalyticsRepositoryImpl) getOperationalExpenseItems(ctx context.Context query := r.db.WithContext(ctx). Table("expense_items ei"). - Select(`COALESCE(NULLIF(ei.item, ''), ei.description, coa.name) as item, COALESCE(SUM(ei.amount), 0) as amount`). + Select(`COALESCE(NULLIF(ei.item, ''), ei.description, pc.name) as item, COALESCE(SUM(ei.amount), 0) as amount`). Joins("JOIN expenses e ON ei.expense_id = e.id"). - Joins("JOIN chart_of_accounts coa ON ei.chart_of_account_id = coa.id"). + Joins("JOIN purchase_categories pc ON ei.purchase_category_id = pc.id"). Where("e.organization_id = ?", organizationID). + Where("pc.type = ?", entities.PurchaseCategoryTypeExpense). Where("e.status = ?", "approved"). Where("e.transaction_date >= ? AND e.transaction_date <= ?", dateFrom, dateTo) @@ -696,7 +697,7 @@ func (r *AnalyticsRepositoryImpl) getOperationalExpenseItems(ctx context.Context } err := query. - Group("COALESCE(NULLIF(ei.item, ''), ei.description, coa.name)"). + Group("COALESCE(NULLIF(ei.item, ''), ei.description, pc.name)"). Order("amount DESC"). Scan(&results).Error diff --git a/migrations/000082_enforce_expense_items_expense_categories.down.sql b/migrations/000082_enforce_expense_items_expense_categories.down.sql new file mode 100644 index 0000000..2d79281 --- /dev/null +++ b/migrations/000082_enforce_expense_items_expense_categories.down.sql @@ -0,0 +1,5 @@ +DROP TRIGGER IF EXISTS trigger_validate_expense_item_expense_category ON expense_items; +DROP FUNCTION IF EXISTS validate_expense_item_expense_category(); + +ALTER TABLE expense_items + ALTER COLUMN purchase_category_id DROP NOT NULL; diff --git a/migrations/000082_enforce_expense_items_expense_categories.up.sql b/migrations/000082_enforce_expense_items_expense_categories.up.sql new file mode 100644 index 0000000..73f59ae --- /dev/null +++ b/migrations/000082_enforce_expense_items_expense_categories.up.sql @@ -0,0 +1,38 @@ +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM expense_items ei + LEFT JOIN purchase_categories pc ON pc.id = ei.purchase_category_id + WHERE ei.purchase_category_id IS NULL + OR pc.id IS NULL + OR pc.type <> 'expense' + ) THEN + RAISE EXCEPTION 'expense_items contains missing or non-expense purchase categories. Assign valid expense categories before running this migration.'; + END IF; +END $$; + +ALTER TABLE expense_items + ALTER COLUMN purchase_category_id SET NOT NULL; + +CREATE OR REPLACE FUNCTION validate_expense_item_expense_category() +RETURNS TRIGGER AS $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM purchase_categories pc + WHERE pc.id = NEW.purchase_category_id + AND pc.type = 'expense' + ) THEN + RAISE EXCEPTION 'expense_items.purchase_category_id must reference an expense purchase category'; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS trigger_validate_expense_item_expense_category ON expense_items; +CREATE TRIGGER trigger_validate_expense_item_expense_category + BEFORE INSERT OR UPDATE OF purchase_category_id ON expense_items + FOR EACH ROW + EXECUTE FUNCTION validate_expense_item_expense_category();