| Type | Name | Status | PK | FK | Columns | Index | Script | Diff Script |
|---|---|---|---|---|---|---|---|---|
| Table | module_permissions | Missing in Target |
|
|||||
| Table | contacts | Match | ||||||
| Table | countries | Match | ||||||
| Table | currencies | Match | ||||||
| Table | __EFMigrationsHistory | Match | ||||||
| Table | company_users | Mismatch |
|
|||||
| Table | company_upis | Match | ||||||
| Table | customers | Match | ||||||
| Table | default_organization_users | Match | ||||||
| Table | departments | Match | ||||||
| Table | user_roles | Mismatch |
Source Script
Target Script
1
CREATE TABLE "user_roles" ("user_id" uuid NOT NULL, "role_id" integer NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "end_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "start_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "organization_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "id" integer NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "user_roles" ("id" integer NOT NULL, "user_id" uuid NOT NULL, "role_id" integer NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "end_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "start_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | description_templates | Mismatch |
Source Script
Target Script
1
CREATE TABLE "description_templates" ("id" integer DEFAULT nextval('description_templates_id_seq'::regclass) NOT NULL, "template_text" text NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "name" text DEFAULT ''::text NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "description_templates" ("id" integer DEFAULT nextval('description_templates_id_seq'::regclass) NOT NULL, "template_text" text NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | role_permissions | Missing in Target |
|
|||||
| Table | chart_of_accounts | Mismatch |
Source Script
Target Script
1
CREATE TABLE "chart_of_accounts" ("id" uuid DEFAULT gen_random_uuid() NOT NULL, "account_number" text NOT NULL, "name" text NOT NULL, "organization_id" uuid NOT NULL, "parent_account_id" uuid, "description" text DEFAULT ''::text NOT NULL, "account_type_id" integer NOT NULL, "account_group_code" integer DEFAULT 0 NOT NULL, "second_group_code" integer DEFAULT 0 NOT NULL, "alternative_name" text, "is_ledger_total" boolean DEFAULT false NOT NULL, "is_show_outs" boolean DEFAULT false NOT NULL, "is_tds_tcs" boolean DEFAULT false NOT NULL, "opening_balance" numeric(,) DEFAULT 0.0 NOT NULL, "current_balance" numeric(,) DEFAULT 0.00, "created_by" uuid NOT NULL, "modified_by" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "is_default_account" boolean DEFAULT false NOT NULL, "is_pg_bank_account" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "chart_of_accounts" ("id" uuid DEFAULT gen_random_uuid() NOT NULL, "account_number" text NOT NULL, "name" text NOT NULL, "organization_id" uuid NOT NULL, "parent_account_id" uuid, "description" text DEFAULT ''::text NOT NULL, "account_type_id" integer NOT NULL, "account_group_code" integer DEFAULT 0 NOT NULL, "second_group_code" integer DEFAULT 0 NOT NULL, "alternative_name" text, "is_ledger_total" boolean DEFAULT false NOT NULL, "is_show_outs" boolean DEFAULT false NOT NULL, "is_tds_tcs" boolean DEFAULT false NOT NULL, "opening_balance" numeric(,) DEFAULT 0.0 NOT NULL, "current_balance" numeric(15,2) DEFAULT 0.00, "created_by" uuid NOT NULL, "modified_by" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "is_default_account" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | users_bk | Missing in Target |
|
|||||
| Table | v_finance_year_id | Match | ||||||
| Table | v_is_second_last_leaf | Match | ||||||
| Table | v_permission_id | Missing in Target |
|
|||||
| Table | users | Mismatch |
Source Script
Target Script
1
CREATE TABLE "users" ("id" uuid NOT NULL, "first_name" varchar(100) NOT NULL, "last_name" varchar(100) NOT NULL, "email" varchar(256) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "password_hash" text NOT NULL, "third_party_id" text DEFAULT ''::text NOT NULL, "roles" ARRAY DEFAULT ARRAY[]::text[], "address_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "date_of_birth" timestamp without time zone DEFAULT '0001-01-01'::date NOT NULL, "note" text, "role_id" integer DEFAULT 0, "designation_id" integer DEFAULT 0 NOT NULL, "department_id" integer DEFAULT 0 NOT NULL, "phone_number" varchar(15) DEFAULT ''::character varying NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "user_name" text DEFAULT ''::text NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "users" ("id" uuid NOT NULL, "first_name" varchar(100) NOT NULL, "last_name" varchar(100) NOT NULL, "email" varchar(256) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "password_hash" text NOT NULL, "third_party_id" text DEFAULT ''::text NOT NULL, "roles" ARRAY DEFAULT ARRAY[]::text[], "phone_number" varchar(15) DEFAULT ''::character varying NOT NULL, "address_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "date_of_birth" timestamp without time zone DEFAULT '0001-01-01'::date NOT NULL, "note" text, "role_id" integer DEFAULT 0, "designation_id" integer DEFAULT 0 NOT NULL, "department_id" integer DEFAULT 0 NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "user_name" text DEFAULT ''::text NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | user_permissions | Mismatch |
Source Script
Target Script
1
CREATE TABLE "user_permissions" ("id" integer NOT NULL, "user_id" uuid NOT NULL, "permission_id" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "organization_id" uuid, "is_explicit" boolean DEFAULT false);
1
CREATE TABLE "user_permissions" ("id" integer NOT NULL, "user_id" uuid NOT NULL, "organization_id" uuid NOT NULL, "permission_id" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, PRIMARY KEY ("id"));
|
|||||
| Table | companies | Mismatch |
Source Script
Target Script
1
CREATE TABLE "companies" ("id" uuid NOT NULL, "name" text NOT NULL, "organization_id" uuid NOT NULL, "description" text, "account_id" uuid, "billing_address_id" uuid, "shipping_address_id" uuid, "gstin" text NOT NULL, "pan" text, "tan" text, "currency" text, "short_name" text, "tag_line" text, "proprietor_name" text, "outstanding_limit" numeric(,) NOT NULL, "is_non_work" boolean, "interest_percentage" numeric(,) NOT NULL, "is_apartment" boolean DEFAULT false, "has_gstin" boolean DEFAULT false, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "image_id" integer, PRIMARY KEY ("id"));
1
CREATE TABLE "companies" ("id" uuid NOT NULL, "name" text NOT NULL, "organization_id" uuid NOT NULL, "description" text, "account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "gstin" text NOT NULL, "pan" text, "tan" text, "currency" text, "short_name" text, "tag_line" text, "proprietor_name" text, "outstanding_limit" numeric(,) NOT NULL, "is_non_work" boolean, "interest_percentage" numeric(,) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "is_apartment" boolean DEFAULT false, "has_gstin" boolean DEFAULT false, "billing_address_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "shipping_address_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "image_id" integer, PRIMARY KEY ("id"));
|
|||||
| Table | account_categories | Match | ||||||
| Table | account_groups | Match | ||||||
| Table | document_meta_datas | Mismatch |
|
|||||
| Table | bank_transfers | Match | ||||||
| Table | journal_voucher_details | Match | ||||||
| Table | schema_versions | Match | ||||||
| Table | states | Match | ||||||
| Table | payment_references | Missing in Target |
|
|||||
| Table | temp_paymnet_data | Missing in Target |
|
|||||
| Table | temp_organization_logs | Mismatch |
|
|||||
| Table | bank_reconciliation_stagings | Missing in Target |
|
|||||
| Table | account_opening_balances | Mismatch |
|
|||||
| Table | bank_reconciliation_links | Missing in Target |
|
|||||
| Table | journal_narrations | Missing in Target |
|
|||||
| Table | transaction_header_2024_2025 | Mismatch |
Source Script
Target Script
1
CREATE TABLE "transaction_header_2024_2025" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "transaction_header_2024_2025" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | banks | Match | ||||||
| Table | organization_users | Mismatch |
|
|||||
| Table | cities | Mismatch |
|
|||||
| Table | budget_statuses | Match | ||||||
| Table | budgets | Mismatch |
|
|||||
| Table | budget_workflow | Mismatch |
|
|||||
| Table | budget_lines | Mismatch |
|
|||||
| Table | bank_transfer_ids | Mismatch |
|
|||||
| Table | cron_dummy_log | Missing in Target |
|
|||||
| Table | permission_groups | Match | ||||||
| Table | company_bank_accounts | Match | ||||||
| Table | company_finance_year | Match | ||||||
| Table | journal_entries | Mismatch |
Source Script
Target Script
1
CREATE TABLE "journal_entries" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "journal_entries" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | company_contacts | Mismatch |
|
|||||
| Table | warehouses | Match | ||||||
| Table | transaction_header_2022_2023 | Mismatch |
Source Script
Target Script
1
CREATE TABLE "transaction_header_2022_2023" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "transaction_header_2022_2023" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | transaction_header_2023_2024 | Mismatch |
Source Script
Target Script
1
CREATE TABLE "transaction_header_2023_2024" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "transaction_header_2023_2024" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | feature_toggles | Missing in Target |
|
|||||
| Table | user_permissions_backup | Missing in Target |
|
|||||
| Table | bank_reconciliations | Missing in Target |
|
|||||
| Table | journal_entries_2022_2023 | Mismatch |
Source Script
Target Script
1
CREATE TABLE "journal_entries_2022_2023" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "journal_entries_2022_2023" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | feature_toggle_overrides | Missing in Target |
|
|||||
| Table | user_permission_sync_runs | Missing in Target |
|
|||||
| Table | scope_types | Missing in Target |
|
|||||
| Table | journal_entries_2023_2024 | Mismatch |
Source Script
Target Script
1
CREATE TABLE "journal_entries_2023_2024" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "journal_entries_2023_2024" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | journal_entries_2024_2025 | Mismatch |
Source Script
Target Script
1
CREATE TABLE "journal_entries_2024_2025" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "journal_entries_2024_2025" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | journal_entries_2025_2026 | Mismatch |
Source Script
Target Script
1
CREATE TABLE "journal_entries_2025_2026" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "journal_entries_2025_2026" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | journal_voucher_header_id | Match | ||||||
| Table | journal_voucher_headers | Match | ||||||
| Table | actions | Missing in Target |
|
|||||
| Table | feature_toggle_audits | Missing in Target |
|
|||||
| Table | user_global_permissions | Mismatch |
|
|||||
| Table | employees | Missing in Target |
|
|||||
| Table | audit_query_responses | Missing in Target |
|
|||||
| Table | system_settings | Missing in Target |
|
|||||
| Table | roles | Match | ||||||
| Table | organization_accounts | Mismatch |
Source Script
Target Script
1
CREATE TABLE "organization_accounts" ("id" bigint NOT NULL, "organization_id" uuid NOT NULL, "accounts_receivable_account_id" uuid NOT NULL, "accounts_payable_account_id" uuid NOT NULL, "sales_revenue_account_id" uuid NOT NULL, "cgst_receivable_account_id" uuid NOT NULL, "sgst_receivable_account_id" uuid NOT NULL, "igst_receivable_account_id" uuid NOT NULL, "cgst_payable_account_id" uuid NOT NULL, "sgst_payable_account_id" uuid NOT NULL, "igst_payable_account_id" uuid NOT NULL, "round_off_gain_account_id" uuid NOT NULL, "round_off_loss_account_id" uuid, "sales_tax_payable_account_id" uuid, "purchase_tax_receivable_account_id" uuid, "discounts_given_account_id" uuid, "discounts_received_account_id" uuid, "interest_income_account_id" uuid, "interest_expense_account_id" uuid, "depreciation_expense_account_id" uuid, "bad_debt_expense_account_id" uuid, "bank_charges_account_id" uuid, "foreign_exchange_gain_loss_account_id" uuid, "created_on_utc" timestamp without time zone, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "cost_of_goods_sold_account_id" uuid, "inventory_account_id" uuid, "salary_expense_account_id" uuid, "salary_payable_account_id" uuid, "tds_receivable_account_id" uuid, "penalty_receivable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "tds_payable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "customer_advance_account_id" uuid, "customer_security_deposit_account_id" uuid, "employee_advance_account_id" uuid, "employee_loan_account_id" uuid, "vendor_advance_account_id" uuid, "vendor_security_deposit_account_id" uuid, "esi_payable_employee_contribution_account_id" uuid, "esi_payable_employer_contribution_account_id" uuid, "lwf_payable_employee_contribution_account_id" uuid, "lwf_payable_employer_contribution_account_id" uuid, "pf_payable_employee_contribution_account_id" uuid, "pf_payable_employer_contribution_account_id" uuid, "tds_on_contractors_account_id" uuid, "opening_balance_equity_account_id" uuid, "tds_on_salaries_account_id" uuid, PRIMARY KEY ("id"));
1
CREATE TABLE "organization_accounts" ("id" bigint NOT NULL, "organization_id" uuid NOT NULL, "accounts_receivable_account_id" uuid NOT NULL, "accounts_payable_account_id" uuid NOT NULL, "sales_revenue_account_id" uuid NOT NULL, "cgst_receivable_account_id" uuid NOT NULL, "sgst_receivable_account_id" uuid NOT NULL, "igst_receivable_account_id" uuid NOT NULL, "cgst_payable_account_id" uuid NOT NULL, "sgst_payable_account_id" uuid NOT NULL, "igst_payable_account_id" uuid NOT NULL, "round_off_gain_account_id" uuid NOT NULL, "round_off_loss_account_id" uuid, "sales_tax_payable_account_id" uuid, "purchase_tax_receivable_account_id" uuid, "discounts_given_account_id" uuid, "discounts_received_account_id" uuid, "interest_income_account_id" uuid, "interest_expense_account_id" uuid, "depreciation_expense_account_id" uuid, "bad_debt_expense_account_id" uuid, "bank_charges_account_id" uuid, "foreign_exchange_gain_loss_account_id" uuid, "created_on_utc" timestamp without time zone, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "cost_of_goods_sold_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "inventory_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "salary_expense_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "salary_payable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "tds_receivable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "penalty_receivable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "tds_payable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | opening_balances | Mismatch |
Source Script
Target Script
1
CREATE TABLE "opening_balances" ("id" uuid DEFAULT gen_random_uuid() NOT NULL, "organization_id" uuid NOT NULL, "finyear_id" integer NOT NULL, "account_id" uuid NOT NULL, "customer_id" uuid, "vendor_id" uuid, "employee_id" uuid, "balance" numeric(15,2) DEFAULT 0 NOT NULL, "entry_type" character DEFAULT 'D'::bpchar NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "created_by" uuid NOT NULL, "modified_by" uuid, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "opening_balances" ("id" uuid DEFAULT gen_random_uuid() NOT NULL, "organization_id" uuid NOT NULL, "finyear_id" integer NOT NULL, "account_id" uuid NOT NULL, "customer_id" uuid, "vendor_id" uuid, "employee_id" uuid, "balance" numeric(15,2) DEFAULT 0 NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "created_by" uuid NOT NULL, "modified_by" uuid, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "entry_type" character DEFAULT 'D'::bpchar NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | audit_queries | Missing in Target |
|
|||||
| Table | account_types | Match | ||||||
| Table | addresses | Match | ||||||
| Table | audit_query_statuses | Missing in Target |
|
|||||
| Table | bank_accounts | Match | ||||||
| Table | bank_statements | Mismatch |
Source Script
Target Script
1
CREATE TABLE "bank_statements" ("id" integer NOT NULL, "organization_id" uuid NOT NULL, "bank_id" uuid NOT NULL, "txn_date" timestamp without time zone NOT NULL, "cheque_number" varchar(50), "description" text NOT NULL, "value_date" timestamp without time zone NOT NULL, "branch_code" varchar(20), "debit_amount" numeric(14,2), "credit_amount" numeric(14,2), "balance" numeric(14,2), "has_reconciled" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_by" uuid, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "bank_statements" ("id" integer NOT NULL, "company_id" uuid NOT NULL, "bank_id" integer NOT NULL, "txn_date" date NOT NULL, "cheque_number" varchar(50), "description" text NOT NULL, "value_date" date NOT NULL, "branch_code" varchar(20), "debit_amount" numeric(14,2), "credit_amount" numeric(14,2), "balance" numeric(14,2), "has_reconciled" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_by" uuid, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | transaction_header_2025_2026 | Mismatch |
Source Script
Target Script
1
CREATE TABLE "transaction_header_2025_2026" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "transaction_header_2025_2026" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | transaction_headers | Mismatch |
Source Script
Target Script
1
CREATE TABLE "transaction_headers" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
1
CREATE TABLE "transaction_headers" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
|
|||||
| Table | logs | Match | ||||||
| Table | messages | Missing in Target |
|
|||||
| Table | notification_types | Match | ||||||
| Table | notifications | Mismatch |
|
|||||
| Table | reconciliation_statuses | Missing in Target |
|
|||||
| Table | organization_types | Match | ||||||
| Table | organizations | Mismatch |
|
|||||
| Table | transaction_headers_19_05_25 | Missing in Target |
|
|||||
| Table | transaction_headers_bk_20_5_25 | Missing in Target |
|
|||||
| Table | vendors | Match | ||||||
| Table | transaction_source_types | Mismatch |
Source Script
Target Script
1
CREATE TABLE "transaction_source_types" ("id" integer NOT NULL, "name" text NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "sort_order" integer DEFAULT 100 NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "transaction_source_types" ("id" integer NOT NULL, "name" text NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | upis | Match | ||||||
| Table | permissions | Mismatch |
|
|||||
| Table | user_deletion_requests | Mismatch |
|
|||||
| Table | user_deletion_request_statuses | Match | ||||||
| Table | company_preferences | Match | ||||||
| Table | designations | Match | ||||||
| Table | dummy_log_table | Match | ||||||
| Table | dummy_test_table | Match | ||||||
| Table | email_templates | Mismatch |
|
|||||
| Table | general_ledgers | Mismatch |
|
|||||
| Table | entry_sources | Match | ||||||
| Table | finance_year | Match | ||||||
| Table | fixed_deposits | Mismatch |
|
|||||
| Table | email_notification_rules | Missing in Target |
|
|||||
| Table | images | Match | ||||||
| Table | user_permission_sync_deltas | Missing in Target |
|
|||||
| Table | email_audit_logs | Missing in Target |
|
|||||
| Table | transaction_header_2026_2027 | Missing in Target |
|
|||||
| Table | transaction_header_2027_2028 | Missing in Target |
|
|||||
| Table | transaction_header_2028_2029 | Missing in Target |
|
|||||
| Table | journal_entries_2026_2027 | Missing in Target |
|
|||||
| Table | transaction_header_2029_2030 | Missing in Target |
|
|||||
| Table | journal_entries_2027_2028 | Missing in Target |
|
|||||
| Table | journal_entries_2028_2029 | Missing in Target |
|
|||||
| Table | journal_entries_2029_2030 | Missing in Target |
|
|||||
| Table | journal_entries_2021_2022 | Missing in Target |
|
|||||
| Table | d_permission_details | Missing in Source |
|
|||||
| Table | organizations_to_be_cleaned | Missing in Source |
|
|||||
| Table | permission_templates | Missing in Source |
|
|||||
| Table | permission_template_mappings | Missing in Source |
|
|||||
| Function | pgp_sym_decrypt_bytea | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_sym_decrypt_bytea(bytea, text, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_sym_decrypt_bytea$function$
|
|||||
| Function | pgp_pub_encrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_encrypt(text, bytea)
2
RETURNS bytea
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_encrypt_text$function$
|
|||||
| Function | pgp_pub_encrypt_bytea | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_encrypt_bytea(bytea, bytea)
2
RETURNS bytea
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_encrypt_bytea$function$
|
|||||
| Function | pgp_pub_encrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_encrypt(text, bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_encrypt_text$function$
|
|||||
| Function | pgp_pub_decrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt(bytea, bytea, text)
2
RETURNS text
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_text$function$
|
|||||
| Function | pgp_pub_decrypt_bytea | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt_bytea(bytea, bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_bytea$function$
|
|||||
| Function | pgp_pub_decrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt(bytea, bytea, text, text)
2
RETURNS text
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_text$function$
|
|||||
| Function | pgp_pub_decrypt_bytea | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt_bytea(bytea, bytea, text, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_bytea$function$
|
|||||
| Function | pgp_key_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_key_id(bytea)
2
RETURNS text
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_key_id_w$function$
|
|||||
| Function | armor | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.armor(bytea)
2
RETURNS text
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_armor$function$
|
|||||
| Function | armor | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.armor(bytea, text[], text[])
2
RETURNS text
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_armor$function$
|
|||||
| Function | dearmor | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dearmor(text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_dearmor$function$
|
|||||
| Function | pgp_armor_headers | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_armor_headers(text, OUT key text, OUT value text)
2
RETURNS SETOF record
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_armor_headers$function$
|
|||||
| Function | fips_mode | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.fips_mode()
2
RETURNS boolean
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_check_fipsmode$function$
|
|||||
| Function | uuid_nil | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_nil()
2
RETURNS uuid
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_nil$function$
|
|||||
| Function | create_user | Match | ||||||
| Function | uuid_ns_dns | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_ns_dns()
2
RETURNS uuid
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_ns_dns$function$
|
|||||
| Function | uuid_ns_url | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_ns_url()
2
RETURNS uuid
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_ns_url$function$
|
|||||
| Function | uuid_ns_oid | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_ns_oid()
2
RETURNS uuid
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_ns_oid$function$
|
|||||
| Function | uuid_ns_x500 | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_ns_x500()
2
RETURNS uuid
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_ns_x500$function$
|
|||||
| Function | uuid_generate_v1 | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_generate_v1()
2
RETURNS uuid
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_generate_v1$function$
|
|||||
| Function | uuid_generate_v1mc | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_generate_v1mc()
2
RETURNS uuid
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_generate_v1mc$function$
|
|||||
| Function | uuid_generate_v3 | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_generate_v3(namespace uuid, name text)
2
RETURNS uuid
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_generate_v3$function$
|
|||||
| Function | uuid_generate_v4 | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_generate_v4()
2
RETURNS uuid
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_generate_v4$function$
|
|||||
| Function | uuid_generate_v5 | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.uuid_generate_v5(namespace uuid, name text)
2
RETURNS uuid
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/uuid-ossp', $function$uuid_generate_v5$function$
|
|||||
| Function | generate_permission_description | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.generate_permission_description(p_name text)
2
RETURNS text
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
parts text[];
7
module text;
8
tail text[];
9
action text;
10
object_tokens text[];
11
object_raw text;
12
object_phrase text;
13
is_myspace boolean := false;
14
15
-- scratch vars for “humanize” and “pluralize”
16
t text;
17
obj_lc text;
18
BEGIN
19
-- Expect Dhanman.<Module>...
20
parts := regexp_split_to_array(p_name, '\.');
21
IF array_length(parts, 1) IS NULL OR parts[1] <> 'Dhanman' THEN
22
RETURN NULL; -- not our namespace
23
END IF;
24
25
module := parts[2];
26
is_myspace := (module = 'MySpace');
27
28
IF array_length(parts, 1) < 3 THEN
29
-- e.g., Dhanman.Read / Dhanman.Write / Dhanman.Delete
30
RETURN trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g')) || ' permission';
31
END IF;
32
33
tail := parts[3:array_length(parts,1)]; -- everything after module
34
35
-- detect multi-word actions first
36
IF tail[array_length(tail,1)] = 'SendForApproval' THEN
37
action := 'SendForApproval';
38
IF array_length(tail,1) > 1 THEN
39
object_tokens := tail[1:array_length(tail,1)-1];
40
ELSE
41
object_tokens := ARRAY[]::text[];
42
END IF;
43
44
ELSIF tail[array_length(tail,1)] IN ('IdRead','TokenRead') THEN
45
action := 'Read';
46
IF array_length(tail,1) > 1 THEN
47
object_tokens := tail[1:array_length(tail,1)-1];
48
ELSE
49
object_tokens := ARRAY[]::text[];
50
END IF;
51
52
ELSE
53
-- simple actions (last token)
54
IF tail[array_length(tail,1)] IN ('Read','Write','Delete','Approve','Cancel','Reject','Resolve','Assign','Copy','Pay','Import') THEN
55
action := tail[array_length(tail,1)];
56
IF array_length(tail,1) > 1 THEN
57
object_tokens := tail[1:array_length(tail,1)-1];
58
ELSE
59
object_tokens := ARRAY[]::text[];
60
END IF;
61
ELSE
62
-- No explicit action => treat the last token as (maybe) object and assume base Read on module
63
action := NULL;
64
object_tokens := tail;
65
END IF;
66
END IF;
67
68
-- join tokens with '.' to get a raw object handle
69
object_raw := NULL;
70
IF object_tokens IS NOT NULL AND array_length(object_tokens,1) IS NOT NULL THEN
71
SELECT string_agg(x, '.' ORDER BY ord)
72
INTO object_raw
73
FROM (
74
SELECT object_tokens[i] AS x, i AS ord
75
FROM generate_subscripts(object_tokens,1) AS i
76
) s;
77
END IF;
78
79
-- If action is NULL, try to interpret tail as a “Read/Write/Delete” on module (rare fallback)
80
IF action IS NULL THEN
81
IF object_raw ILIKE 'Read' THEN
82
t := trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'));
83
RETURN 'Read ' || lower(t) || ' data';
84
ELSIF object_raw ILIKE 'Write' THEN
85
t := trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'));
86
RETURN 'Add/Update ' || lower(t) || ' data';
87
ELSIF object_raw ILIKE 'Delete' THEN
88
RETURN 'Delete general data';
89
END IF;
90
-- else continue with generic handling below
91
END IF;
92
93
-- ===== normalize object to a nicer phrase =====
94
object_phrase := NULL;
95
IF object_raw IS NOT NULL AND object_raw <> '' THEN
96
obj_lc := lower(object_raw);
97
98
-- specific aliases
99
IF obj_lc ~* '^basic(\.read|\.write|\.delete)?$' THEN
100
object_phrase := 'Basic Info';
101
ELSIF obj_lc LIKE 'ticketstatus%' THEN
102
object_phrase := 'Ticket Status';
103
ELSIF obj_lc = 'invoicetemplate' THEN
104
object_phrase := 'Invoice Template';
105
ELSIF obj_lc = 'groupedinvoice' THEN
106
object_phrase := 'Grouped Invoice';
107
ELSIF obj_lc = 'recurringinvoice' THEN
108
object_phrase := 'Recurring Invoice';
109
ELSIF obj_lc = 'customeraccountconfig' THEN
110
object_phrase := 'Customer Account Configuration';
111
ELSIF obj_lc = 'vendoraccountconfig' THEN
112
object_phrase := 'Vendor Account Configuration';
113
ELSIF obj_lc = 'bankstatement' THEN
114
object_phrase := 'Bank Statement';
115
ELSIF obj_lc = 'banktransfer' THEN
116
object_phrase := 'Bank Transfer';
117
ELSIF obj_lc = 'fixeddeposit' THEN
118
object_phrase := 'Fixed Deposit';
119
ELSE
120
-- generic humanize: add spaces before capitals
121
object_phrase := trim(both ' ' FROM regexp_replace(object_raw, '([a-z])([A-Z])', '\1 \2', 'g'));
122
END IF;
123
END IF;
124
125
-- quick helpers inlined:
126
-- humanize(module)
127
t := trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'));
128
-- module phrase
129
t := t || ' module';
130
131
-- MySpace special-casing (root objects)
132
IF is_myspace AND (object_phrase IS NULL OR object_phrase = '') THEN
133
IF action = 'Read' THEN
134
RETURN 'View own My Space data. (myspace)';
135
ELSIF action = 'Write' THEN
136
RETURN 'Create or update own data. (myspace)';
137
ELSIF action = 'Delete' THEN
138
RETURN 'Delete own entries. (myspace)';
139
END IF;
140
END IF;
141
142
-- pluralize (very naive) if we need a plural in messages
143
-- compute plural into obj_lc (reuse var)
144
IF object_phrase IS NOT NULL AND object_phrase <> '' THEN
145
obj_lc := lower(object_phrase);
146
-- don’t pluralize these:
147
IF obj_lc NOT IN ('info','data','ledger','cash flow','journal','basicinfo','basic info') THEN
148
IF obj_lc !~ '(s|x|z|ch|sh)$' THEN
149
object_phrase := object_phrase || 's';
150
END IF;
151
END IF;
152
END IF;
153
154
-- Render phrases by action
155
IF is_myspace THEN
156
-- MySpace + specific objects
157
IF action = 'Read' AND object_phrase IS NOT NULL THEN
158
IF lower(object_phrase) IN ('user', 'users') THEN
159
RETURN 'View user (own) details. (myspace)';
160
ELSIF lower(object_phrase) IN ('due','dues') THEN
161
RETURN 'View dues summary. (myspace)';
162
ELSE
163
RETURN 'View own ' || lower(object_phrase) || '. (myspace)';
164
END IF;
165
END IF;
166
-- For other MySpace actions on specific objects, fall through to generic wording below
167
END IF;
168
169
-- rebuild a fresh humanized module text for generic lines
170
t := trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'));
171
t := t || ' module';
172
173
CASE action
174
WHEN 'Read' THEN
175
IF object_phrase IS NULL OR object_phrase = '' THEN
176
RETURN 'Read ' || lower(trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'))) || ' data';
177
ELSE
178
RETURN 'Read ' || lower(object_phrase) || ' in ' || t;
179
END IF;
180
181
WHEN 'Write' THEN
182
IF object_phrase IS NULL OR object_phrase = '' THEN
183
RETURN 'Add/Update ' || lower(trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'))) || ' data';
184
ELSE
185
RETURN 'Add/Update ' || lower(object_phrase) || ' in ' || t;
186
END IF;
187
188
WHEN 'Delete' THEN
189
IF object_phrase IS NULL OR object_phrase = '' THEN
190
RETURN 'Delete general data';
191
ELSE
192
RETURN 'Delete ' || lower(object_phrase) || ' in ' || t;
193
END IF;
194
195
WHEN 'Approve' THEN
196
IF object_phrase IS NULL OR object_phrase = '' THEN
197
RETURN 'Approve ' || lower(trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'))) || ' actions';
198
ELSE
199
RETURN 'Approve ' || lower(object_phrase) || ' in the ' || t;
200
END IF;
201
202
WHEN 'Cancel' THEN
203
RETURN 'Cancel ' || lower(COALESCE(object_phrase,'items')) || ' in ' || t;
204
205
WHEN 'Reject' THEN
206
RETURN 'Reject ' || lower(COALESCE(object_phrase,'items')) || ' in ' || t;
207
208
WHEN 'Resolve' THEN
209
RETURN 'Mark ' || lower(COALESCE(object_phrase,'items')) || ' as resolved in the ' || t;
210
211
WHEN 'Assign' THEN
212
RETURN 'Assign ' || lower(COALESCE(object_phrase,'items')) || ' in the ' || t;
213
214
WHEN 'Copy' THEN
215
RETURN 'Copy ' || lower(COALESCE(object_phrase,'items')) || ' in ' || t;
216
217
WHEN 'Pay' THEN
218
RETURN 'Make payments for ' || lower(COALESCE(object_phrase,'items'));
219
220
WHEN 'SendForApproval' THEN
221
RETURN 'Send ' || lower(COALESCE(object_phrase,'items')) || ' for approval';
222
223
WHEN 'Import' THEN
224
RETURN 'Import ' || lower(COALESCE(object_phrase,'items')) || ' data';
225
226
ELSE
227
-- Fallback
228
IF object_phrase IS NULL OR object_phrase = '' THEN
229
RETURN trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g')) || ' permission';
230
ELSE
231
RETURN trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'))
232
|| ' ' || trim(both ' ' FROM regexp_replace(object_phrase, '([a-z])([A-Z])', '\1 \2', 'g'))
233
|| ' permission';
234
END IF;
235
END CASE;
236
END;
237
$function$
|
|||||
| Function | get_account_expense_monthly_breakdown | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_account_expense_monthly_breakdown(p_company_id uuid, p_finance_year_id integer)
1
CREATE OR REPLACE FUNCTION public.get_account_expense_monthly_breakdown(p_company_id uuid, p_finance_year_id integer)
2
RETURNS TABLE(account_id uuid, account_name text, year integer, apr numeric, apr_diff numeric, may numeric, may_diff numeric, jun numeric, jun_diff numeric, jul numeric, jul_diff numeric, aug numeric, aug_diff numeric, sep numeric, sep_diff numeric, oct numeric, oct_diff numeric, nov numeric, nov_diff numeric, "dec" numeric, dec_diff numeric, jan numeric, jan_diff numeric, feb numeric, feb_diff numeric, mar numeric, mar_diff numeric, total numeric, difference numeric)
2
RETURNS TABLE(account_name text, year integer, apr numeric, apr_diff numeric, may numeric, may_diff numeric, jun numeric, jun_diff numeric, jul numeric, jul_diff numeric, aug numeric, aug_diff numeric, sep numeric, sep_diff numeric, oct numeric, oct_diff numeric, nov numeric, nov_diff numeric, "dec" numeric, dec_diff numeric, jan numeric, jan_diff numeric, feb numeric, feb_diff numeric, mar numeric, mar_diff numeric, total numeric, difference numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
v_organization_id UUID;
6
v_organization_id UUID;
7
v_start_date DATE;
7
v_start_date DATE;
8
v_end_date DATE;
8
v_end_date DATE;
9
v_prev_start_date DATE;
9
v_prev_start_date DATE;
10
v_prev_end_date DATE;
10
v_prev_end_date DATE;
11
BEGIN
11
BEGIN
12
-- Get organization and financial year details
12
-- Get the organization_id for the given company_id
13
SELECT c.organization_id INTO v_organization_id FROM public.companies c WHERE c.id = p_company_id;
13
SELECT c.organization_id INTO v_organization_id
14
SELECT fy.start_date, fy.end_date INTO v_start_date, v_end_date FROM public.finance_year fy WHERE fy.id = p_finance_year_id;
14
FROM public.companies c
15
SELECT pfy.start_date, pfy.end_date INTO v_prev_start_date, v_prev_end_date FROM public.finance_year pfy WHERE pfy.id = (p_finance_year_id - 1);
15
WHERE c.id = p_company_id;
16
17
-- Get the start and end dates for the selected financial year
18
SELECT fy.start_date, fy.end_date INTO v_start_date, v_end_date
19
FROM public.finance_year fy
20
WHERE fy.id = p_finance_year_id;
21
22
-- Get the start and end dates for the previous financial year
23
SELECT pfy.start_date, pfy.end_date INTO v_prev_start_date, v_prev_end_date
24
FROM public.finance_year pfy
25
WHERE pfy.id = (p_finance_year_id - 1);
16
26
17
RETURN QUERY
27
RETURN QUERY
18
WITH expense_accounts AS (
28
WITH expense_accounts AS (
29
-- Fetch all expense accounts
19
SELECT DISTINCT coa.id AS account_id, coa.name
30
SELECT DISTINCT coa.id AS account_id, coa.name
20
FROM chart_of_accounts coa
31
FROM chart_of_accounts coa
21
JOIN account_types at ON coa.account_type_id = at.id
32
JOIN account_types at ON coa.account_type_id = at.id
22
WHERE at.id IN (SELECT id FROM get_account_type_hierarchy(5)) AND coa.organization_id = v_organization_id
33
WHERE at.id IN (SELECT id FROM get_account_type_hierarchy(5))
34
AND coa.organization_id = v_organization_id
23
),
35
),
24
years AS (
36
years AS (
25
SELECT p_finance_year_id AS year UNION ALL SELECT p_finance_year_id - 1
37
-- Ensure two years are always present
38
SELECT p_finance_year_id AS year
39
UNION ALL
40
SELECT p_finance_year_id - 1
26
),
41
),
27
all_accounts AS (
42
all_accounts AS (
43
-- Ensure all accounts have both years
28
SELECT ea.account_id, ea.name, y.year
44
SELECT ea.account_id, ea.name, y.year
29
FROM expense_accounts ea CROSS JOIN years y
45
FROM expense_accounts ea
46
CROSS JOIN years y
30
),
47
),
31
monthly_expenses AS (
48
monthly_expenses AS (
32
-- Get monthly totals, excluding opening balance entries
49
-- Get monthly totals for the selected and previous financial years
33
SELECT
50
SELECT
34
je.account_id,
51
je.account_id,
35
CASE
52
CASE
36
WHEN je.transaction_date BETWEEN v_start_date AND v_end_date THEN p_finance_year_id
53
WHEN je.transaction_date BETWEEN v_start_date AND v_end_date THEN p_finance_year_id
37
WHEN je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date THEN (p_finance_year_id - 1)
54
WHEN je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date THEN (p_finance_year_id - 1)
38
END AS year,
55
END AS year,
39
EXTRACT(MONTH FROM je.transaction_date) AS month,
56
EXTRACT(MONTH FROM je.transaction_date) AS month,
40
SUM(je.amount) AS amount
57
SUM(je.amount) AS amount
41
FROM journal_entries je
58
FROM journal_entries je
42
JOIN transaction_headers th ON je.transaction_id = th.id
59
JOIN transaction_headers th ON je.transaction_id = th.id
43
WHERE th.company_id = p_company_id
60
WHERE th.company_id = p_company_id
44
AND je.transaction_date BETWEEN v_prev_start_date AND v_end_date
61
AND je.transaction_date BETWEEN v_prev_start_date AND v_end_date
45
AND th.transaction_source_type != 18
46
AND th.is_deleted = FALSE
47
AND je.is_deleted = FALSE
48
GROUP BY je.account_id, year, EXTRACT(MONTH FROM je.transaction_date)
62
GROUP BY je.account_id, year, EXTRACT(MONTH FROM je.transaction_date)
49
),
63
),
50
pivoted_expenses AS (
64
pivoted_expenses AS (
51
-- Pivot data to month-wise columns
65
-- Pivoting data to month-wise columns
52
SELECT
66
SELECT
53
aa.account_id, aa.name AS account_name, aa.year,
67
aa.name AS account_name,
68
aa.year,
54
COALESCE(SUM(CASE WHEN me.month = 4 THEN me.amount ELSE 0 END), 0) AS apr,
69
COALESCE(SUM(CASE WHEN me.month = 4 THEN me.amount ELSE 0 END), 0) AS apr,
55
COALESCE(SUM(CASE WHEN me.month = 5 THEN me.amount ELSE 0 END), 0) AS may,
70
COALESCE(SUM(CASE WHEN me.month = 5 THEN me.amount ELSE 0 END), 0) AS may,
56
COALESCE(SUM(CASE WHEN me.month = 6 THEN me.amount ELSE 0 END), 0) AS jun,
71
COALESCE(SUM(CASE WHEN me.month = 6 THEN me.amount ELSE 0 END), 0) AS jun,
57
COALESCE(SUM(CASE WHEN me.month = 7 THEN me.amount ELSE 0 END), 0) AS jul,
72
COALESCE(SUM(CASE WHEN me.month = 7 THEN me.amount ELSE 0 END), 0) AS jul,
58
COALESCE(SUM(CASE WHEN me.month = 8 THEN me.amount ELSE 0 END), 0) AS aug,
73
COALESCE(SUM(CASE WHEN me.month = 8 THEN me.amount ELSE 0 END), 0) AS aug,
59
COALESCE(SUM(CASE WHEN me.month = 9 THEN me.amount ELSE 0 END), 0) AS sep,
74
COALESCE(SUM(CASE WHEN me.month = 9 THEN me.amount ELSE 0 END), 0) AS sep,
60
COALESCE(SUM(CASE WHEN me.month = 10 THEN me.amount ELSE 0 END), 0) AS oct,
75
COALESCE(SUM(CASE WHEN me.month = 10 THEN me.amount ELSE 0 END), 0) AS oct,
61
COALESCE(SUM(CASE WHEN me.month = 11 THEN me.amount ELSE 0 END), 0) AS nov,
76
COALESCE(SUM(CASE WHEN me.month = 11 THEN me.amount ELSE 0 END), 0) AS nov,
62
COALESCE(SUM(CASE WHEN me.month = 12 THEN me.amount ELSE 0 END), 0) AS "dec",
77
COALESCE(SUM(CASE WHEN me.month = 12 THEN me.amount ELSE 0 END), 0) AS "dec",
63
COALESCE(SUM(CASE WHEN me.month = 1 THEN me.amount ELSE 0 END), 0) AS jan,
78
COALESCE(SUM(CASE WHEN me.month = 1 THEN me.amount ELSE 0 END), 0) AS jan,
64
COALESCE(SUM(CASE WHEN me.month = 2 THEN me.amount ELSE 0 END), 0) AS feb,
79
COALESCE(SUM(CASE WHEN me.month = 2 THEN me.amount ELSE 0 END), 0) AS feb,
65
COALESCE(SUM(CASE WHEN me.month = 3 THEN me.amount ELSE 0 END), 0) AS mar,
80
COALESCE(SUM(CASE WHEN me.month = 3 THEN me.amount ELSE 0 END), 0) AS mar,
66
COALESCE(SUM(me.amount), 0) AS total
81
COALESCE(SUM(me.amount), 0) AS total
67
FROM all_accounts aa
82
FROM all_accounts aa
68
LEFT JOIN monthly_expenses me ON aa.account_id = me.account_id AND aa.year = me.year
83
LEFT JOIN monthly_expenses me ON aa.account_id = me.account_id AND aa.year = me.year
69
GROUP BY aa.account_id, aa.name, aa.year
84
GROUP BY aa.account_id, aa.name, aa.year
70
)
85
)
71
-- Final table with difference calculation
86
-- Final table with the difference calculation
72
SELECT
87
SELECT
73
pe.account_id, pe.account_name, pe.year,
88
pe.account_name,
89
pe.year,
74
pe.apr, COALESCE(pe.apr - LAG(pe.apr) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS apr_diff,
90
pe.apr, COALESCE(pe.apr - LAG(pe.apr) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS apr_diff,
75
pe.may, COALESCE(pe.may - LAG(pe.may) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS may_diff,
91
pe.may, COALESCE(pe.may - LAG(pe.may) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS may_diff,
76
pe.jun, COALESCE(pe.jun - LAG(pe.jun) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jun_diff,
92
pe.jun, COALESCE(pe.jun - LAG(pe.jun) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jun_diff,
77
pe.jul, COALESCE(pe.jul - LAG(pe.jul) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jul_diff,
93
pe.jul, COALESCE(pe.jul - LAG(pe.jul) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jul_diff,
78
pe.aug, COALESCE(pe.aug - LAG(pe.aug) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS aug_diff,
94
pe.aug, COALESCE(pe.aug - LAG(pe.aug) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS aug_diff,
79
pe.sep, COALESCE(pe.sep - LAG(pe.sep) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS sep_diff,
95
pe.sep, COALESCE(pe.sep - LAG(pe.sep) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS sep_diff,
80
pe.oct, COALESCE(pe.oct - LAG(pe.oct) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS oct_diff,
96
pe.oct, COALESCE(pe.oct - LAG(pe.oct) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS oct_diff,
81
pe.nov, COALESCE(pe.nov - LAG(pe.nov) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS nov_diff,
97
pe.nov, COALESCE(pe.nov - LAG(pe.nov) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS nov_diff,
82
pe.dec, COALESCE(pe.dec - LAG(pe.dec) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS dec_diff,
98
pe.dec, COALESCE(pe.dec - LAG(pe.dec) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS dec_diff,
83
pe.jan, COALESCE(pe.jan - LAG(pe.jan) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jan_diff,
99
pe.jan, COALESCE(pe.jan - LAG(pe.jan) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jan_diff,
84
pe.feb, COALESCE(pe.feb - LAG(pe.feb) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS feb_diff,
100
pe.feb, COALESCE(pe.feb - LAG(pe.feb) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS feb_diff,
85
pe.mar, COALESCE(pe.mar - LAG(pe.mar) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS mar_diff,
101
pe.mar, COALESCE(pe.mar - LAG(pe.mar) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS mar_diff,
86
pe.total,
102
pe.total,
87
COALESCE(pe.total - LAG(pe.total) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS difference
103
COALESCE(pe.total - LAG(pe.total) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS difference
88
FROM pivoted_expenses pe
104
FROM pivoted_expenses pe
89
ORDER BY pe.account_name, pe.year;
105
ORDER BY pe.account_name, pe.year;
90
END;
106
END;
91
$function$
107
$function$
|
|||||
| Function | get_permissions | Match | ||||||
| Function | get_income_expense_overview | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_income_expense_overview(p_company_id uuid, p_period_type integer, p_finance_id integer)
1
CREATE OR REPLACE FUNCTION public.get_income_expense_overview(p_company_id uuid, p_period_type integer, p_finance_id integer)
2
RETURNS TABLE(period text, year numeric, total_income numeric, total_expense numeric, actual_income numeric, actual_expense numeric)
2
RETURNS TABLE(period text, year numeric, total_income numeric, total_expense numeric, actual_income numeric, actual_expense numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
v_financial_year_start timestamp;
6
v_financial_year_start date;
7
v_financial_year_end timestamp;
7
v_financial_year_end date;
8
v_finance_year_start numeric;
9
v_finance_year_end numeric;
8
10
9
CONST_MONTHLY CONSTANT INTEGER := 1;
11
CONST_MONTHLY CONSTANT INTEGER := 1;
10
CONST_QUARTERLY CONSTANT INTEGER := 2;
12
CONST_QUARTERLY CONSTANT INTEGER := 2;
11
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
13
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
12
CONST_YEARLY CONSTANT INTEGER := 4;
14
CONST_YEARLY CONSTANT INTEGER := 4;
13
CONST_YTD CONSTANT INTEGER := 5;
15
CONST_YTD CONSTANT INTEGER := 5;
14
BEGIN
16
BEGIN
15
------------------------------------------------------------------
17
IF p_period_type != CONST_YTD THEN
16
-- Rolling 12-month window for dashboard behavior
18
SELECT start_date, end_date,
17
-- Keeps API contract same, handles period internally
19
EXTRACT(YEAR FROM start_date),
18
------------------------------------------------------------------
20
EXTRACT(YEAR FROM end_date)
19
IF p_period_type IN (CONST_MONTHLY, CONST_QUARTERLY, CONST_HALF_YEARLY, CONST_YEARLY) THEN
21
INTO v_financial_year_start, v_financial_year_end,
20
v_financial_year_start := date_trunc('month', CURRENT_DATE) - interval '11 months';
22
v_finance_year_start, v_finance_year_end
21
v_financial_year_end := date_trunc('month', CURRENT_DATE) + interval '1 month';
23
FROM public.finance_year
24
WHERE id = p_finance_id;
22
END IF;
25
END IF;
23
26
24
------------------------------------------------------------------
25
-- YTD (actual data using selected finance year)
26
------------------------------------------------------------------
27
IF p_period_type = CONST_YTD THEN
27
IF p_period_type = CONST_YTD THEN
28
RETURN QUERY
28
RETURN QUERY
29
WITH recent_years AS (
29
WITH recent_years AS (
30
SELECT
30
SELECT fy.id, fy.start_date, fy.end_date,
31
fy.id,
31
EXTRACT(YEAR FROM fy.start_date) AS year_start,
32
fy.start_date,
32
EXTRACT(YEAR FROM fy.end_date) AS year_end
33
fy.end_date,
33
FROM finance_year fy
34
EXTRACT(YEAR FROM fy.start_date) AS fiscal_year
35
FROM public.finance_year fy
36
WHERE EXTRACT(YEAR FROM fy.start_date) >= EXTRACT(YEAR FROM CURRENT_DATE) - 4
34
WHERE EXTRACT(YEAR FROM fy.start_date) >= EXTRACT(YEAR FROM CURRENT_DATE) - 4
37
AND EXTRACT(YEAR FROM fy.start_date) <= EXTRACT(YEAR FROM CURRENT_DATE)
35
AND EXTRACT(YEAR FROM fy.start_date) <= EXTRACT(YEAR FROM CURRENT_DATE)
38
ORDER BY fy.start_date DESC
36
ORDER BY fy.start_date DESC
39
LIMIT 5
37
LIMIT 5
40
),
38
),
41
quarters AS (
39
ytd_periods AS (
42
SELECT
40
SELECT id AS finance_id, 'YTD H1' AS period,
43
id AS finance_id,
41
(start_date + INTERVAL '5 months 29 days')::date AS period_end,
44
fiscal_year,
42
EXTRACT(YEAR FROM start_date) AS year,
45
'Q1' AS period,
43
start_date AS fy_start
46
make_date(fiscal_year::int, 4, 1) AS period_start,
47
make_date(fiscal_year::int, 6, 30) AS period_end
48
FROM recent_years
44
FROM recent_years
49
50
UNION ALL
45
UNION ALL
51
46
SELECT id, 'YTD H2', end_date,
52
SELECT
47
EXTRACT(YEAR FROM end_date) AS year,
53
id,
48
start_date
54
fiscal_year,
55
'Q2',
56
make_date(fiscal_year::int, 7, 1),
57
make_date(fiscal_year::int, 9, 30)
58
FROM recent_years
49
FROM recent_years
59
50
WHERE end_date <= CURRENT_DATE
60
UNION ALL
51
AND CURRENT_DATE >= (start_date + INTERVAL '6 months') -- skip if it's a future YTD H2
61
62
SELECT
63
id,
64
fiscal_year,
65
'Q3',
66
make_date(fiscal_year::int, 10, 1),
67
make_date(fiscal_year::int, 12, 31)
68
FROM recent_years
69
70
UNION ALL
71
72
SELECT
73
id,
74
fiscal_year,
75
'Q4',
76
make_date((fiscal_year::int) + 1, 1, 1),
77
make_date((fiscal_year::int) + 1, 3, 31)
78
FROM recent_years
79
),
52
),
80
filtered_je AS (
53
filtered_je AS (
81
SELECT
54
SELECT
82
je.amount,
55
je.amount,
83
je.entry_type,
56
je.entry_type,
84
tr.company_id,
57
tr.company_id,
85
je.transaction_date,
58
tr.transaction_date,
86
coa.account_type_id
59
coa.account_type_id
87
FROM journal_entries je
60
FROM journal_entries je
88
JOIN transaction_headers tr
61
JOIN transaction_headers tr ON je.transaction_id = tr.id
89
ON je.transaction_id = tr.id
62
JOIN chart_of_accounts coa ON je.account_id = coa.id
90
JOIN chart_of_accounts coa
63
WHERE je.is_deleted = FALSE
91
ON je.account_id = coa.id
92
WHERE COALESCE(je.is_deleted, FALSE) = FALSE
93
AND tr.company_id = p_company_id
94
AND COALESCE(tr.is_reversed, FALSE) = FALSE
95
)
64
)
96
SELECT
65
SELECT
97
CONCAT(q.period, ' ', q.fiscal_year)::text AS period,
66
CONCAT(yp.period, ' ', yp.year) AS period,
98
q.fiscal_year::numeric AS year,
67
yp.year,
99
COALESCE(SUM(
68
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
100
CASE
69
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0),
101
WHEN je.entry_type = 'C'
70
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
102
AND je.account_type_id IN (4,14,15,19,20)
71
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0),
103
AND je.transaction_date BETWEEN q.period_start AND q.period_end
72
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
104
THEN je.amount
73
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0),
105
ELSE 0
74
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
106
END
75
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0)
107
), 0)::numeric AS total_income,
76
FROM ytd_periods yp
108
COALESCE(SUM(
77
LEFT JOIN filtered_je je ON je.company_id = p_company_id
109
CASE
78
GROUP BY yp.period, yp.year, yp.period_end
110
WHEN je.entry_type = 'D'
111
AND je.account_type_id IN (5,16,17,18,21,22)
112
AND je.transaction_date BETWEEN q.period_start AND q.period_end
113
THEN je.amount
114
ELSE 0
115
END
116
), 0)::numeric AS total_expense,
117
COALESCE(SUM(
118
CASE
119
WHEN je.entry_type = 'D'
120
AND je.account_type_id IN (8,9)
121
AND je.transaction_date BETWEEN q.period_start AND q.period_end
122
THEN je.amount
123
ELSE 0
124
END
125
), 0)::numeric AS actual_income,
126
COALESCE(SUM(
127
CASE
128
WHEN je.entry_type = 'C'
129
AND je.account_type_id IN (8,9)
130
AND je.transaction_date BETWEEN q.period_start AND q.period_end
131
THEN je.amount
132
ELSE 0
133
END
134
), 0)::numeric AS actual_expense
135
FROM quarters q
136
LEFT JOIN filtered_je je
137
ON je.transaction_date BETWEEN q.period_start AND q.period_end
138
GROUP BY q.period, q.fiscal_year
139
HAVING
79
HAVING
140
COALESCE(SUM(
80
SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
141
CASE
81
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
142
WHEN je.entry_type = 'C'
82
OR SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
143
AND je.account_type_id IN (4,14,15,19,20)
83
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
144
AND je.transaction_date BETWEEN q.period_start AND q.period_end
84
OR SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
145
THEN je.amount
85
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
146
ELSE 0
86
OR SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
147
END
87
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
148
), 0) > 0
88
ORDER BY yp.year, yp.period;
149
OR COALESCE(SUM(
150
CASE
151
WHEN je.entry_type = 'D'
152
AND je.account_type_id IN (5,16,17,18,21,22)
153
AND je.transaction_date BETWEEN q.period_start AND q.period_end
154
THEN je.amount
155
ELSE 0
156
END
157
), 0) > 0
158
OR COALESCE(SUM(
159
CASE
160
WHEN je.entry_type = 'D'
161
AND je.account_type_id IN (8,9)
162
AND je.transaction_date BETWEEN q.period_start AND q.period_end
163
THEN je.amount
164
ELSE 0
165
END
166
), 0) > 0
167
OR COALESCE(SUM(
168
CASE
169
WHEN je.entry_type = 'C'
170
AND je.account_type_id IN (8,9)
171
AND je.transaction_date BETWEEN q.period_start AND q.period_end
172
THEN je.amount
173
ELSE 0
174
END
175
), 0) > 0
176
ORDER BY q.fiscal_year, q.period;
177
89
178
------------------------------------------------------------------
179
-- MONTHLY (Rolling last 12 months)
180
------------------------------------------------------------------
181
ELSIF p_period_type = CONST_MONTHLY THEN
90
ELSIF p_period_type = CONST_MONTHLY THEN
182
RETURN QUERY
91
RETURN QUERY
183
WITH months AS (
92
WITH months AS (
184
SELECT
93
SELECT
185
gs::timestamp AS month_start,
94
generate_series::date AS month_start,
186
(gs + interval '1 month')::timestamp AS month_end
95
(generate_series + interval '1 month')::date AS month_end
187
FROM generate_series(
96
FROM generate_series(
188
v_financial_year_start,
97
v_financial_year_start,
189
v_financial_year_end - interval '1 month',
98
v_financial_year_end,
190
interval '1 month'
99
interval '1 month'
191
) gs
100
)
192
),
101
),
193
filtered_je AS (
102
filtered_je AS (
194
SELECT
103
SELECT
195
je.amount,
104
je.*,
196
je.entry_type,
197
je.transaction_date,
198
coa.account_type_id
105
coa.account_type_id
199
FROM journal_entries je
106
FROM journal_entries je
200
JOIN transaction_headers tr
107
JOIN transaction_headers tr ON je.transaction_id = tr.id
201
ON je.transaction_id = tr.id
108
JOIN chart_of_accounts coa ON je.account_id = coa.id
202
JOIN chart_of_accounts coa
203
ON je.account_id = coa.id
204
WHERE tr.company_id = p_company_id
109
WHERE tr.company_id = p_company_id
205
AND COALESCE(tr.is_reversed, FALSE) = FALSE
110
AND je.is_deleted = FALSE
206
AND COALESCE(je.is_deleted, FALSE) = FALSE
111
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
207
AND je.transaction_date >= v_financial_year_start
208
AND je.transaction_date < v_financial_year_end
209
)
112
)
210
SELECT
113
SELECT
211
to_char(m.month_start, 'Mon YYYY')::text AS period,
114
to_char(m.month_start, 'Mon YYYY') AS period,
212
EXTRACT(YEAR FROM m.month_start)::numeric AS year,
115
EXTRACT(YEAR FROM m.month_start) AS year,
213
COALESCE(SUM(
116
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20) THEN je.amount ELSE 0 END), 0),
214
CASE
117
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22) THEN je.amount ELSE 0 END), 0),
215
WHEN je.entry_type = 'C'
118
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
216
AND je.account_type_id IN (4, 14, 15, 19, 20)
119
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
217
THEN je.amount
218
ELSE 0
219
END
220
), 0)::numeric AS total_income,
221
COALESCE(SUM(
222
CASE
223
WHEN je.entry_type = 'D'
224
AND je.account_type_id IN (5, 16, 17, 18, 21, 22)
225
THEN je.amount
226
ELSE 0
227
END
228
), 0)::numeric AS total_expense,
229
COALESCE(SUM(
230
CASE
231
WHEN je.entry_type = 'D'
232
AND je.account_type_id IN (8, 9)
233
THEN je.amount
234
ELSE 0
235
END
236
), 0)::numeric AS actual_income,
237
COALESCE(SUM(
238
CASE
239
WHEN je.entry_type = 'C'
240
AND je.account_type_id IN (8, 9)
241
THEN je.amount
242
ELSE 0
243
END
244
), 0)::numeric AS actual_expense
245
FROM months m
120
FROM months m
246
LEFT JOIN filtered_je je
121
LEFT JOIN filtered_je je ON je.transaction_date >= m.month_start AND je.transaction_date < m.month_end
247
ON je.transaction_date >= m.month_start
248
AND je.transaction_date < m.month_end
249
GROUP BY m.month_start
122
GROUP BY m.month_start
250
ORDER BY m.month_start;
123
ORDER BY m.month_start;
251
124
252
------------------------------------------------------------------
253
-- QUARTERLY (Rolling)
254
------------------------------------------------------------------
255
ELSIF p_period_type = CONST_QUARTERLY THEN
125
ELSIF p_period_type = CONST_QUARTERLY THEN
256
RETURN QUERY
126
RETURN QUERY
257
WITH months AS (
127
WITH quarters AS (
258
SELECT
128
SELECT 'Q1' AS period, 4 AS start_month, 6 AS end_month, v_finance_year_start AS year
259
gs::timestamp AS month_start,
129
UNION ALL SELECT 'Q2', 7, 9, v_finance_year_start
260
(gs + interval '1 month')::timestamp AS month_end,
130
UNION ALL SELECT 'Q3', 10, 12, v_finance_year_start
261
date_trunc('quarter', gs)::timestamp AS quarter_start
131
UNION ALL SELECT 'Q4', 1, 3, v_finance_year_end
262
FROM generate_series(
263
v_financial_year_start,
264
v_financial_year_end - interval '1 month',
265
interval '1 month'
266
) gs
267
),
132
),
268
filtered_je AS (
133
filtered_je AS (
269
SELECT
134
SELECT
270
je.amount,
135
je.*,
271
je.entry_type,
272
je.transaction_date,
273
coa.account_type_id
136
coa.account_type_id
274
FROM journal_entries je
137
FROM journal_entries je
275
JOIN transaction_headers tr
138
JOIN transaction_headers tr ON je.transaction_id = tr.id
276
ON je.transaction_id = tr.id
139
JOIN chart_of_accounts coa ON je.account_id = coa.id
277
JOIN chart_of_accounts coa
278
ON je.account_id = coa.id
279
WHERE tr.company_id = p_company_id
140
WHERE tr.company_id = p_company_id
280
AND COALESCE(tr.is_reversed, FALSE) = FALSE
141
AND je.is_deleted = FALSE
281
AND COALESCE(je.is_deleted, FALSE) = FALSE
142
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
282
AND je.transaction_date >= v_financial_year_start
283
AND je.transaction_date < v_financial_year_end
284
)
143
)
285
SELECT
144
SELECT
286
to_char(m.quarter_start, '"Q"Q YYYY')::text AS period,
145
CONCAT(q.period, ' ', q.year),
287
EXTRACT(YEAR FROM m.quarter_start)::numeric AS year,
146
q.year,
288
COALESCE(SUM(
147
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20) THEN je.amount ELSE 0 END), 0),
289
CASE
148
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22) THEN je.amount ELSE 0 END), 0),
290
WHEN je.entry_type = 'C'
149
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
291
AND je.account_type_id IN (4, 14, 15, 19, 20)
150
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
292
THEN je.amount
151
FROM quarters q
293
ELSE 0
152
JOIN filtered_je je ON EXTRACT(MONTH FROM je.transaction_date) BETWEEN q.start_month AND q.end_month
294
END
153
AND EXTRACT(YEAR FROM je.transaction_date) = q.year
295
), 0)::numeric AS total_income,
154
GROUP BY q.period, q.year
296
COALESCE(SUM(
155
ORDER BY q.year, q.period;
297
CASE
298
WHEN je.entry_type = 'D'
299
AND je.account_type_id IN (5, 16, 17, 18, 21, 22)
300
THEN je.amount
301
ELSE 0
302
END
303
), 0)::numeric AS total_expense,
304
COALESCE(SUM(
305
CASE
306
WHEN je.entry_type = 'D'
307
AND je.account_type_id IN (8, 9)
308
THEN je.amount
309
ELSE 0
310
END
311
), 0)::numeric AS actual_income,
312
COALESCE(SUM(
313
CASE
314
WHEN je.entry_type = 'C'
315
AND je.account_type_id IN (8, 9)
316
THEN je.amount
317
ELSE 0
318
END
319
), 0)::numeric AS actual_expense
320
FROM months m
321
LEFT JOIN filtered_je je
322
ON je.transaction_date >= m.month_start
323
AND je.transaction_date < m.month_end
324
GROUP BY m.quarter_start
325
ORDER BY m.quarter_start;
326
156
327
------------------------------------------------------------------
328
-- HALF YEARLY (Rolling)
329
------------------------------------------------------------------
330
ELSIF p_period_type = CONST_HALF_YEARLY THEN
157
ELSIF p_period_type = CONST_HALF_YEARLY THEN
331
RETURN QUERY
158
RETURN QUERY
332
WITH months AS (
159
WITH half_years AS (
333
SELECT
160
SELECT 'H1' AS period, 4 AS start_month, 9 AS end_month, v_finance_year_start AS year
334
gs::timestamp AS month_start,
161
UNION ALL SELECT 'H2', 10, 12, v_finance_year_start
335
(gs + interval '1 month')::timestamp AS month_end,
162
UNION ALL SELECT 'H2', 1, 3, v_finance_year_end
336
CASE
337
WHEN EXTRACT(MONTH FROM gs) <= 6
338
THEN date_trunc('year', gs)::timestamp
339
ELSE (date_trunc('year', gs) + interval '6 months')::timestamp
340
END AS half_start
341
FROM generate_series(
342
v_financial_year_start,
343
v_financial_year_end - interval '1 month',
344
interval '1 month'
345
) gs
346
),
163
),
347
filtered_je AS (
164
filtered_je AS (
348
SELECT
165
SELECT
349
je.amount,
166
je.*,
350
je.entry_type,
351
je.transaction_date,
352
coa.account_type_id
167
coa.account_type_id
353
FROM journal_entries je
168
FROM journal_entries je
354
JOIN transaction_headers tr
169
JOIN transaction_headers tr ON je.transaction_id = tr.id
355
ON je.transaction_id = tr.id
170
JOIN chart_of_accounts coa ON je.account_id = coa.id
356
JOIN chart_of_accounts coa
357
ON je.account_id = coa.id
358
WHERE tr.company_id = p_company_id
171
WHERE tr.company_id = p_company_id
359
AND COALESCE(tr.is_reversed, FALSE) = FALSE
172
AND je.is_deleted = FALSE
360
AND COALESCE(je.is_deleted, FALSE) = FALSE
173
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
361
AND je.transaction_date >= v_financial_year_start
362
AND je.transaction_date < v_financial_year_end
363
)
174
)
364
SELECT
175
SELECT
365
(
176
CONCAT(h.period, ' ', h.year),
366
CASE
177
h.year,
367
WHEN EXTRACT(MONTH FROM m.half_start) = 1 THEN 'H1 '
178
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20) THEN je.amount ELSE 0 END), 0),
368
ELSE 'H2 '
179
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22) THEN je.amount ELSE 0 END), 0),
369
END || EXTRACT(YEAR FROM m.half_start)::int
180
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
370
)::text AS period,
181
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
371
EXTRACT(YEAR FROM m.half_start)::numeric AS year,
182
FROM half_years h
372
COALESCE(SUM(
183
JOIN filtered_je je ON EXTRACT(MONTH FROM je.transaction_date) BETWEEN h.start_month AND h.end_month
373
CASE
184
AND EXTRACT(YEAR FROM je.transaction_date) = h.year
374
WHEN je.entry_type = 'C'
185
GROUP BY h.period, h.year
375
AND je.account_type_id IN (4, 14, 15, 19, 20)
186
ORDER BY h.year, h.period;
376
THEN je.amount
377
ELSE 0
378
END
379
), 0)::numeric AS total_income,
380
COALESCE(SUM(
381
CASE
382
WHEN je.entry_type = 'D'
383
AND je.account_type_id IN (5, 16, 17, 18, 21, 22)
384
THEN je.amount
385
ELSE 0
386
END
387
), 0)::numeric AS total_expense,
388
COALESCE(SUM(
389
CASE
390
WHEN je.entry_type = 'D'
391
AND je.account_type_id IN (8, 9)
392
THEN je.amount
393
ELSE 0
394
END
395
), 0)::numeric AS actual_income,
396
COALESCE(SUM(
397
CASE
398
WHEN je.entry_type = 'C'
399
AND je.account_type_id IN (8, 9)
400
THEN je.amount
401
ELSE 0
402
END
403
), 0)::numeric AS actual_expense
404
FROM months m
405
LEFT JOIN filtered_je je
406
ON je.transaction_date >= m.month_start
407
AND je.transaction_date < m.month_end
408
GROUP BY m.half_start
409
ORDER BY m.half_start;
410
187
411
------------------------------------------------------------------
412
-- YEARLY (Rolling)
413
------------------------------------------------------------------
414
ELSIF p_period_type = CONST_YEARLY THEN
188
ELSIF p_period_type = CONST_YEARLY THEN
415
RETURN QUERY
189
RETURN QUERY
416
WITH months AS (
190
WITH filtered_je AS (
417
SELECT
418
gs::timestamp AS month_start,
419
(gs + interval '1 month')::timestamp AS month_end,
420
date_trunc('year', gs)::timestamp AS year_start
421
FROM generate_series(
422
v_financial_year_start,
423
v_financial_year_end - interval '1 month',
424
interval '1 month'
425
) gs
426
),
427
filtered_je AS (
428
SELECT
191
SELECT
429
je.amount,
192
je.*,
430
je.entry_type,
431
je.transaction_date,
432
coa.account_type_id
193
coa.account_type_id
433
FROM journal_entries je
194
FROM journal_entries je
434
JOIN transaction_headers tr
195
JOIN transaction_headers tr ON je.transaction_id = tr.id
435
ON je.transaction_id = tr.id
196
JOIN chart_of_accounts coa ON je.account_id = coa.id
436
JOIN chart_of_accounts coa
437
ON je.account_id = coa.id
438
WHERE tr.company_id = p_company_id
197
WHERE tr.company_id = p_company_id
439
AND COALESCE(tr.is_reversed, FALSE) = FALSE
198
AND je.is_deleted = FALSE
440
AND COALESCE(je.is_deleted, FALSE) = FALSE
199
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
441
AND je.transaction_date >= v_financial_year_start
442
AND je.transaction_date < v_financial_year_end
443
)
200
)
444
SELECT
201
SELECT
445
to_char(m.year_start, 'YYYY')::text AS period,
202
CONCAT('Year ', EXTRACT(YEAR FROM je.transaction_date)),
446
EXTRACT(YEAR FROM m.year_start)::numeric AS year,
203
EXTRACT(YEAR FROM je.transaction_date),
447
COALESCE(SUM(
204
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20) THEN je.amount ELSE 0 END), 0),
448
CASE
205
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22) THEN je.amount ELSE 0 END), 0),
449
WHEN je.entry_type = 'C'
206
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
450
AND je.account_type_id IN (4, 14, 15, 19, 20)
207
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
451
THEN je.amount
208
FROM filtered_je je
452
ELSE 0
209
GROUP BY EXTRACT(YEAR FROM je.transaction_date)
453
END
210
ORDER BY EXTRACT(YEAR FROM je.transaction_date);
454
), 0)::numeric AS total_income,
455
COALESCE(SUM(
456
CASE
457
WHEN je.entry_type = 'D'
458
AND je.account_type_id IN (5, 16, 17, 18, 21, 22)
459
THEN je.amount
460
ELSE 0
461
END
462
), 0)::numeric AS total_expense,
463
COALESCE(SUM(
464
CASE
465
WHEN je.entry_type = 'D'
466
AND je.account_type_id IN (8, 9)
467
THEN je.amount
468
ELSE 0
469
END
470
), 0)::numeric AS actual_income,
471
COALESCE(SUM(
472
CASE
473
WHEN je.entry_type = 'C'
474
AND je.account_type_id IN (8, 9)
475
THEN je.amount
476
ELSE 0
477
END
478
), 0)::numeric AS actual_expense
479
FROM months m
480
LEFT JOIN filtered_je je
481
ON je.transaction_date >= m.month_start
482
AND je.transaction_date < m.month_end
483
GROUP BY m.year_start
484
ORDER BY m.year_start;
485
211
486
ELSE
212
ELSE
487
RAISE EXCEPTION 'Unsupported period type %', p_period_type;
213
RAISE EXCEPTION 'Unsupported period type %', p_period_type;
488
END IF;
214
END IF;
489
END;
215
END;
490
$function$
216
$function$
|
|||||
| Function | get_income_expense_monthly_breakdown | Match | ||||||
| Function | get_income_expense_overview_test | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_income_expense_overview_test(p_company_id uuid, p_period_type integer, p_finance_id integer)
2
RETURNS TABLE(period text, year numeric, total_income numeric, total_expense numeric, actual_income numeric, actual_expense numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date timestamp;
7
v_end_date timestamp;
8
9
CONST_MONTHLY CONSTANT INTEGER := 1;
10
BEGIN
11
------------------------------------------------------------------
12
-- STEP 1: DEFINE DATE RANGE (TEMP - will be removed later)
13
------------------------------------------------------------------
14
v_start_date := date_trunc('month', CURRENT_DATE) - interval '11 months';
15
v_end_date := date_trunc('month', CURRENT_DATE) + interval '1 month';
16
17
------------------------------------------------------------------
18
-- STEP 2: BASE DATASET (SINGLE SOURCE INSIDE FUNCTION)
19
------------------------------------------------------------------
20
RETURN QUERY
21
WITH base_data AS (
22
SELECT
23
th.transaction_date,
24
je.amount,
25
je.entry_type,
26
coa.account_type_id
27
FROM journal_entries je
28
JOIN transaction_headers th ON th.id = je.transaction_id
29
JOIN chart_of_accounts coa ON coa.id = je.account_id
30
WHERE
31
th.company_id = p_company_id
32
AND COALESCE(th.is_reversed, FALSE) = FALSE
33
AND COALESCE(je.is_deleted, FALSE) = FALSE
34
AND th.transaction_date >= v_start_date
35
AND th.transaction_date < v_end_date
36
),
37
38
------------------------------------------------------------------
39
-- STEP 3: MONTH BUCKETS
40
------------------------------------------------------------------
41
months AS (
42
SELECT
43
gs AS month_start,
44
(gs + interval '1 month') AS month_end
45
FROM generate_series(
46
v_start_date,
47
v_end_date - interval '1 month',
48
interval '1 month'
49
) gs
50
)
51
52
------------------------------------------------------------------
53
-- STEP 4: FINAL AGGREGATION
54
------------------------------------------------------------------
55
SELECT
56
to_char(m.month_start, 'Mon YYYY') AS period,
57
EXTRACT(YEAR FROM m.month_start)::numeric AS year,
58
59
ROUND(SUM(
60
CASE WHEN b.entry_type = 'C'
61
AND b.account_type_id IN (4,14,15,19,20)
62
THEN b.amount ELSE 0 END
63
), 2) AS total_income,
64
65
ROUND(SUM(
66
CASE WHEN b.entry_type = 'D'
67
AND b.account_type_id IN (5,16,17,18,21,22)
68
THEN b.amount ELSE 0 END
69
), 2) AS total_expense,
70
71
ROUND(SUM(
72
CASE WHEN b.entry_type = 'D'
73
AND b.account_type_id IN (8,9)
74
THEN b.amount ELSE 0 END
75
), 2) AS actual_income,
76
77
ROUND(SUM(
78
CASE WHEN b.entry_type = 'C'
79
AND b.account_type_id IN (8,9)
80
THEN b.amount ELSE 0 END
81
), 2) AS actual_expense
82
83
FROM months m
84
LEFT JOIN base_data b
85
ON b.transaction_date >= m.month_start
86
AND b.transaction_date < m.month_end
87
88
GROUP BY m.month_start
89
ORDER BY m.month_start;
90
END;
91
$function$
|
|||||
| Function | get_expense_categorization | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_expense_categorization(p_company_id uuid, p_period_type integer, p_finance_id integer)
1
CREATE OR REPLACE FUNCTION public.get_expense_categorization(p_company_id uuid, p_period_type integer, p_finance_id integer)
2
RETURNS TABLE(period text, year numeric, account_id uuid, account_name text, total_expense numeric)
2
RETURNS TABLE(period text, year numeric, account_id uuid, account_name text, total_expense numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
v_financial_year_start DATE;
6
v_financial_year_start DATE;
7
v_financial_year_end DATE;
7
v_financial_year_end DATE;
8
v_organization_id uuid;
9
10
CONST_MONTHLY CONSTANT INTEGER := 1;
11
CONST_QUARTERLY CONSTANT INTEGER := 2;
12
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
13
CONST_YEARLY CONSTANT INTEGER := 4;
14
CONST_YTD CONSTANT INTEGER := 5;
15
BEGIN
8
BEGIN
16
9
-- Step 1: Get financial year boundaries
17
-- 🔥 Rolling 12 months (for dashboard types)
10
SELECT start_date, end_date INTO v_financial_year_start, v_financial_year_end
18
IF p_period_type IN (CONST_MONTHLY, CONST_QUARTERLY, CONST_HALF_YEARLY, CONST_YEARLY) THEN
19
v_financial_year_start := date_trunc('month', CURRENT_DATE) - INTERVAL '11 months';
20
v_financial_year_end := date_trunc('month', CURRENT_DATE) + INTERVAL '1 month';
21
ELSE
22
-- YTD fallback
23
SELECT start_date, end_date
24
INTO v_financial_year_start, v_financial_year_end
25
FROM public.finance_year
11
FROM public.finance_year
26
WHERE id = p_finance_id;
12
WHERE id = p_finance_id;
27
END IF;
28
13
29
-- Org fetch
14
-- Raise an exception if financial year is not found
30
SELECT organization_id INTO v_organization_id
15
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
31
FROM public.companies
16
RAISE EXCEPTION 'Financial year with ID % not found', p_finance_id;
32
WHERE id = p_company_id;
33
34
IF v_organization_id IS NULL THEN
35
RAISE EXCEPTION 'Company % not found or missing organization_id', p_company_id;
36
END IF;
17
END IF;
37
18
38
------------------------------------------------------------------
19
-- Step 2: Categorize expenses based on period type
39
-- 🔹 MONTHLY (ROLLING 12 MONTHS)
20
IF p_period_type = 1 THEN -- Monthly Categorization
40
------------------------------------------------------------------
41
IF p_period_type = CONST_MONTHLY THEN
42
RETURN QUERY
21
RETURN QUERY
43
WITH months AS (
22
WITH months AS (
44
SELECT
23
SELECT
45
gs AS month_start,
24
TO_CHAR(m, 'Mon') AS period,
46
gs + interval '1 month' AS month_end,
25
EXTRACT(YEAR FROM m) AS year,
47
to_char(gs, 'Mon') AS period,
26
EXTRACT(MONTH FROM m) AS month_num
48
EXTRACT(YEAR FROM gs) AS year
27
FROM generate_series(v_financial_year_start, v_financial_year_end, '1 month'::interval) AS m
49
FROM generate_series(
50
v_financial_year_start,
51
v_financial_year_end - interval '1 month',
52
interval '1 month'
53
) gs
54
)
28
)
55
SELECT
29
SELECT
56
m.period,
30
months.period,
57
m.year,
31
months.year,
58
coa.id,
32
coa.id AS account_id, -- 🔹 Added missing account_id
59
coa.name,
33
coa.name AS account_name,
60
COALESCE(SUM(je.amount), 0)
34
COALESCE(SUM(je.amount), 0) AS total_expense
61
FROM months m
35
FROM months
62
LEFT JOIN journal_entries je
36
LEFT JOIN public.journal_entries je
63
ON je.transaction_date >= m.month_start
37
ON EXTRACT(MONTH FROM je.transaction_date) = months.month_num
64
AND je.transaction_date < m.month_end
38
AND EXTRACT(YEAR FROM je.transaction_date) = months.year
65
AND je.is_deleted = FALSE
39
INNER JOIN public.transaction_headers th
66
INNER JOIN transaction_headers th
67
ON je.transaction_id = th.id
40
ON je.transaction_id = th.id
68
AND th.company_id = p_company_id
41
AND th.company_id = p_company_id
69
AND th.is_reversed = FALSE
42
INNER JOIN public.chart_of_accounts coa
70
INNER JOIN chart_of_accounts coa
71
ON je.account_id = coa.id
43
ON je.account_id = coa.id
72
AND coa.organization_id = v_organization_id
44
WHERE
73
AND coa.is_deleted = FALSE
45
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
74
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
46
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
75
AND je.entry_type = 'D'
47
AND je.entry_type = 'D'
76
GROUP BY m.period, m.year, m.month_start, coa.id, coa.name
48
AND je.is_deleted = FALSE
77
ORDER BY m.month_start, coa.name;
49
GROUP BY months.year, months.month_num, months.period, coa.id, coa.name -- 🔹 Added `coa.id` to GROUP BY
50
ORDER BY months.year, months.month_num, coa.name;
51
78
52
79
------------------------------------------------------------------
53
ELSIF p_period_type = 2 THEN -- Quarterly Categorization
80
-- 🔹 QUARTERLY (ROLLING)
81
------------------------------------------------------------------
82
ELSIF p_period_type = CONST_QUARTERLY THEN
83
RETURN QUERY
54
RETURN QUERY
84
WITH months AS (
55
WITH quarters AS (
85
SELECT
56
SELECT 'Q1' AS period, v_financial_year_start AS start_date, v_financial_year_start + interval '2 month' AS end_date
86
gs AS month_start,
57
UNION ALL
87
gs + interval '1 month' AS month_end,
58
SELECT 'Q2', v_financial_year_start + interval '3 month', v_financial_year_start + interval '5 month'
88
date_trunc('quarter', gs) AS quarter_start
59
UNION ALL
89
FROM generate_series(
60
SELECT 'Q3', v_financial_year_start + interval '6 month', v_financial_year_start + interval '8 month'
90
v_financial_year_start,
61
UNION ALL
91
v_financial_year_end - interval '1 month',
62
SELECT 'Q4', v_financial_year_start + interval '9 month', v_financial_year_end
92
interval '1 month'
93
) gs
94
)
63
)
95
SELECT
64
SELECT
96
to_char(m.quarter_start, '"Q"Q') AS period,
65
quarters.period,
97
EXTRACT(YEAR FROM m.quarter_start),
66
EXTRACT(YEAR FROM quarters.start_date) AS year,
98
coa.id,
67
coa.id AS account_id, -- Added account_id
99
coa.name,
68
coa.name AS account_name,
100
COALESCE(SUM(je.amount), 0)
69
COALESCE(SUM(je.amount), 0) AS total_expense
101
FROM months m
70
FROM quarters
102
LEFT JOIN journal_entries je
71
LEFT JOIN public.journal_entries je
103
ON je.transaction_date >= m.month_start
72
ON je.transaction_date BETWEEN quarters.start_date AND quarters.end_date
104
AND je.transaction_date < m.month_end
73
INNER JOIN public.transaction_headers th
105
AND je.is_deleted = FALSE
106
INNER JOIN transaction_headers th
107
ON je.transaction_id = th.id
74
ON je.transaction_id = th.id
108
AND th.company_id = p_company_id
75
AND th.company_id = p_company_id
109
AND th.is_reversed = FALSE
76
INNER JOIN public.chart_of_accounts coa
110
INNER JOIN chart_of_accounts coa
111
ON je.account_id = coa.id
77
ON je.account_id = coa.id
112
AND coa.organization_id = v_organization_id
78
WHERE
113
AND coa.is_deleted = FALSE
79
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
114
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
80
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
115
AND je.entry_type = 'D'
81
AND je.entry_type = 'D'
116
GROUP BY m.quarter_start, coa.id, coa.name
82
AND je.is_deleted = FALSE
117
ORDER BY m.quarter_start, coa.name;
83
GROUP BY quarters.period, year, coa.id, coa.name
84
ORDER BY year, quarters.period, coa.name;
118
85
119
------------------------------------------------------------------
86
ELSIF p_period_type = 3 THEN -- Half-Yearly Categorization
120
-- 🔹 HALF YEARLY (ROLLING)
121
------------------------------------------------------------------
122
ELSIF p_period_type = CONST_HALF_YEARLY THEN
123
RETURN QUERY
87
RETURN QUERY
124
WITH months AS (
88
WITH half_years AS (
125
SELECT
89
SELECT 'H1' AS period, v_financial_year_start AS start_date, v_financial_year_start + interval '5 month' AS end_date
126
gs AS month_start,
90
UNION ALL
127
gs + interval '1 month' AS month_end,
91
SELECT 'H2', v_financial_year_start + interval '6 month', v_financial_year_end
128
CASE
129
WHEN EXTRACT(MONTH FROM gs) <= 6
130
THEN date_trunc('year', gs)
131
ELSE date_trunc('year', gs) + interval '6 months'
132
END AS half_start
133
FROM generate_series(
134
v_financial_year_start,
135
v_financial_year_end - interval '1 month',
136
interval '1 month'
137
) gs
138
)
92
)
139
SELECT
93
SELECT
140
to_char(m.half_start, '"H"1') AS period,
94
half_years.period,
141
EXTRACT(YEAR FROM m.half_start),
95
EXTRACT(YEAR FROM half_years.start_date) AS year,
142
coa.id,
96
coa.id AS account_id, -- Added account_id
143
coa.name,
97
coa.name AS account_name,
144
COALESCE(SUM(je.amount), 0)
98
COALESCE(SUM(je.amount), 0) AS total_expense
145
FROM months m
99
FROM half_years
146
LEFT JOIN journal_entries je
100
LEFT JOIN public.journal_entries je
147
ON je.transaction_date >= m.month_start
101
ON je.transaction_date BETWEEN half_years.start_date AND half_years.end_date
148
AND je.transaction_date < m.month_end
102
INNER JOIN public.transaction_headers th
149
AND je.is_deleted = FALSE
150
INNER JOIN transaction_headers th
151
ON je.transaction_id = th.id
103
ON je.transaction_id = th.id
152
AND th.company_id = p_company_id
104
AND th.company_id = p_company_id
153
AND th.is_reversed = FALSE
105
INNER JOIN public.chart_of_accounts coa
154
INNER JOIN chart_of_accounts coa
155
ON je.account_id = coa.id
106
ON je.account_id = coa.id
156
AND coa.organization_id = v_organization_id
107
WHERE
157
AND coa.is_deleted = FALSE
108
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
158
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
109
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
159
AND je.entry_type = 'D'
110
AND je.entry_type = 'D'
160
GROUP BY m.half_start, coa.id, coa.name
111
AND je.is_deleted = FALSE
161
ORDER BY m.half_start, coa.name;
112
GROUP BY half_years.period, year, coa.id, coa.name
113
ORDER BY year, half_years.period, coa.name;
162
114
163
------------------------------------------------------------------
115
ELSIF p_period_type = 4 THEN -- Yearly Categorization
164
-- 🔹 YEARLY (ROLLING)
165
------------------------------------------------------------------
166
ELSIF p_period_type = CONST_YEARLY THEN
167
RETURN QUERY
116
RETURN QUERY
168
WITH months AS (
169
SELECT
117
SELECT
170
gs AS month_start,
118
'Year' AS period,
171
gs + interval '1 month' AS month_end,
119
EXTRACT(YEAR FROM je.transaction_date) AS year,
172
date_trunc('year', gs) AS year_start
120
coa.id AS account_id, -- Added account_id
173
FROM generate_series(
121
coa.name AS account_name,
174
v_financial_year_start,
122
COALESCE(SUM(je.amount), 0) AS total_expense
175
v_financial_year_end - interval '1 month',
123
FROM public.journal_entries je
176
interval '1 month'
124
INNER JOIN public.transaction_headers th
177
) gs
178
)
179
SELECT
180
to_char(m.year_start, 'YYYY') AS period,
181
EXTRACT(YEAR FROM m.year_start),
182
coa.id,
183
coa.name,
184
COALESCE(SUM(je.amount), 0)
185
FROM months m
186
LEFT JOIN journal_entries je
187
ON je.transaction_date >= m.month_start
188
AND je.transaction_date < m.month_end
189
AND je.is_deleted = FALSE
190
INNER JOIN transaction_headers th
191
ON je.transaction_id = th.id
125
ON je.transaction_id = th.id
192
AND th.company_id = p_company_id
126
AND th.company_id = p_company_id
193
AND th.is_reversed = FALSE
127
INNER JOIN public.chart_of_accounts coa
194
INNER JOIN chart_of_accounts coa
195
ON je.account_id = coa.id
128
ON je.account_id = coa.id
196
AND coa.organization_id = v_organization_id
129
WHERE
197
AND coa.is_deleted = FALSE
130
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
198
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
131
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
199
AND je.entry_type = 'D'
200
GROUP BY m.year_start, coa.id, coa.name
201
ORDER BY m.year_start, coa.name;
202
203
------------------------------------------------------------------
204
-- 🔹 YTD (UNCHANGED)
205
------------------------------------------------------------------
206
ELSIF p_period_type = CONST_YTD THEN
207
RETURN QUERY
208
SELECT
209
'YTD',
210
EXTRACT(YEAR FROM CURRENT_DATE),
211
coa.id,
212
coa.name,
213
COALESCE(SUM(je.amount), 0)
214
FROM journal_entries je
215
JOIN transaction_headers th ON je.transaction_id = th.id
216
JOIN chart_of_accounts coa ON je.account_id = coa.id
217
WHERE th.company_id = p_company_id
218
AND coa.organization_id = v_organization_id
219
AND je.entry_type = 'D'
132
AND je.entry_type = 'D'
220
AND je.is_deleted = FALSE
133
AND je.is_deleted = FALSE
221
AND th.is_reversed = FALSE
134
GROUP BY year, coa.id, coa.name
222
GROUP BY coa.id, coa.name
135
ORDER BY year, coa.name;
223
ORDER BY total_expense DESC;
224
136
225
ELSE
226
RAISE EXCEPTION 'Unsupported period type %', p_period_type;
227
END IF;
137
END IF;
228
229
END;
138
END;
230
$function$
139
$function$
|
|||||
| Function | get_top_expense_categories | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_top_expense_categories(p_company_id uuid, p_finance_year_id integer)
1
CREATE OR REPLACE FUNCTION public.get_top_expense_categories(p_company_id uuid, p_finance_year_id integer)
2
RETURNS TABLE(account_id uuid, account_name text, account_number text, total_expense numeric)
2
RETURNS TABLE(account_id uuid, account_name text, account_number text, total_expense numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
-- 🔥 Rolling window instead of financial year
6
v_financial_year_start DATE;
7
v_start_date DATE := date_trunc('month', CURRENT_DATE) - INTERVAL '11 months';
7
v_financial_year_end DATE;
8
v_end_date DATE := date_trunc('month', CURRENT_DATE) + INTERVAL '1 month';
9
BEGIN
8
BEGIN
9
-- Fetch financial year start and end dates
10
SELECT start_date, end_date
11
INTO v_financial_year_start, v_financial_year_end
12
FROM public.finance_year
13
WHERE id = p_finance_year_id;
10
14
11
-- 🔥 Top 10 Expense Accounts (Rolling 12 Months)
15
-- Raise an exception if financial year is not found
16
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
17
RAISE EXCEPTION 'Financial year with ID % not found', p_finance_year_id;
18
END IF;
19
20
-- Calculate and return top 10 Expense accounts using `get_account_type_hierarchy(5)`
12
RETURN QUERY
21
RETURN QUERY
13
SELECT
22
SELECT
14
coa.id AS account_id,
23
coa.id As account_id,
15
coa.name AS account_name,
24
coa.name AS account_name,
16
coa.account_number,
25
coa.account_number AS account_number,
17
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense
26
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense
18
FROM journal_entries je
27
FROM public.journal_entries je
19
INNER JOIN chart_of_accounts coa
28
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
20
ON je.account_id = coa.id
29
INNER JOIN public.account_types at ON coa.account_type_id = at.id
21
INNER JOIN account_types at
30
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
22
ON coa.account_type_id = at.id
31
WHERE at.id IN (SELECT id FROM get_account_type_hierarchy(5)) -- Dynamically fetch all expense types
23
INNER JOIN transaction_headers th
24
ON je.transaction_id = th.id
25
WHERE at.id IN (SELECT id FROM get_account_type_hierarchy(5))
26
AND th.company_id = p_company_id
32
AND th.company_id = p_company_id
27
AND je.transaction_date >= v_start_date
33
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
28
AND je.transaction_date < v_end_date
29
AND je.is_deleted = FALSE
34
AND je.is_deleted = FALSE
30
AND th.is_reversed = FALSE
31
AND je.entry_type = 'D'
32
GROUP BY coa.id, coa.name, coa.account_number
35
GROUP BY coa.id, coa.name, coa.account_number
33
ORDER BY total_expense DESC
36
ORDER BY total_expense DESC
34
LIMIT 10;
37
LIMIT 10;
35
38
36
END;
39
END;
37
$function$
40
$function$
|
|||||
| Function | get_account_expense_breakdown | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_account_expense_breakdown(p_company_id uuid, p_finance_id integer, p_period_type integer, p_period integer)
1
CREATE OR REPLACE FUNCTION public.get_account_expense_breakdown(p_company_id uuid, p_finance_id integer, p_period_type integer, p_period integer)
2
RETURNS TABLE(account_id uuid, account_name text, account_number text, total_expense numeric, financial_year text)
2
RETURNS TABLE(account_id uuid, account_name text, account_number text, total_expense numeric, financial_year text)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
STABLE PARALLEL SAFE
4
STABLE PARALLEL SAFE
5
AS $function$
5
AS $function$
6
DECLARE
6
DECLARE
7
v_rolling_start DATE;
7
v_financial_year_start DATE;
8
v_rolling_end DATE; -- exclusive
8
v_financial_year_end DATE;
9
v_finance_year TEXT;
9
v_finance_year TEXT;
10
v_period_start DATE;
11
v_period_end DATE;
12
10
11
-- Period type constants
13
CONST_MONTHLY CONSTANT INTEGER := 1;
12
CONST_MONTHLY CONSTANT INTEGER := 1;
14
CONST_QUARTERLY CONSTANT INTEGER := 2;
13
CONST_QUARTERLY CONSTANT INTEGER := 2;
15
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
14
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
16
CONST_YEARLY CONSTANT INTEGER := 4;
15
CONST_YEARLY CONSTANT INTEGER := 4;
17
CONST_YTD CONSTANT INTEGER := 5;
18
BEGIN
16
BEGIN
19
-- Rolling 12-month window
17
-- Fetch financial year details
20
v_rolling_start := date_trunc('month', CURRENT_DATE) - interval '11 months';
18
SELECT fy.start_date, fy.end_date, fy.year
21
v_rolling_end := date_trunc('month', CURRENT_DATE) + interval '1 month';
19
INTO v_financial_year_start, v_financial_year_end, v_finance_year
20
FROM public.finance_year fy
21
WHERE fy.id = p_finance_id;
22
22
23
-- Show visible inclusive month range (e.g. May 2025 - Apr 2026)
23
-- Validate financial year
24
v_finance_year := to_char(v_rolling_start, 'Mon YYYY')
24
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
25
|| ' - ' ||
25
RAISE EXCEPTION 'Invalid financial year ID: %', p_finance_id;
26
to_char(v_rolling_end - interval '1 month', 'Mon YYYY');
27
28
-- Validate period ranges by type
29
IF p_period_type = CONST_MONTHLY AND (p_period < 1 OR p_period > 12) THEN
30
RAISE EXCEPTION 'Invalid period for monthly: %. Expected 1..12', p_period;
31
ELSIF p_period_type = CONST_QUARTERLY AND (p_period < 1 OR p_period > 4) THEN
32
RAISE EXCEPTION 'Invalid period for quarterly: %. Expected 1..4', p_period;
33
ELSIF p_period_type = CONST_HALF_YEARLY AND (p_period < 1 OR p_period > 2) THEN
34
RAISE EXCEPTION 'Invalid period for half-yearly: %. Expected 1..2', p_period;
35
END IF;
26
END IF;
36
27
28
-- Period-based filtering
37
IF p_period_type = CONST_MONTHLY THEN
29
IF p_period_type = CONST_MONTHLY THEN
38
v_period_start := v_rolling_start + ((p_period - 1) * interval '1 month');
39
v_period_end := v_period_start + interval '1 month';
40
41
RETURN QUERY
30
RETURN QUERY
42
SELECT
31
SELECT
43
coa.id AS account_id,
32
coa.id AS account_id,
44
coa.name::text AS account_name,
33
coa.name AS account_name,
45
coa.account_number::text AS account_number,
34
coa.account_number,
46
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
35
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
47
v_finance_year::text AS financial_year
36
v_finance_year AS financial_year
48
FROM public.journal_entries je
37
FROM
49
JOIN public.transaction_headers th ON je.transaction_id = th.id
38
public.journal_entries je
50
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
39
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
51
WHERE th.company_id = p_company_id
40
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
52
AND COALESCE(th.is_reversed, FALSE) = FALSE
41
WHERE
53
AND COALESCE(je.is_deleted, FALSE) = FALSE
42
th.company_id = p_company_id
54
AND je.entry_type = 'D'
43
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
55
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
44
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
45
AND EXTRACT(MONTH FROM je.transaction_date) = (p_period + 3) % 12 + 1
46
AND je.is_deleted = FALSE
56
AND coa.name NOT IN ('Rounding Gain')
47
AND coa.name NOT IN ('Rounding Gain')
57
AND je.transaction_date >= v_period_start
48
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
58
AND je.transaction_date < v_period_end
59
GROUP BY coa.id, coa.name, coa.account_number
60
ORDER BY total_expense DESC;
49
ORDER BY total_expense DESC;
61
50
62
ELSIF p_period_type = CONST_QUARTERLY THEN
51
ELSIF p_period_type = CONST_QUARTERLY THEN
63
v_period_start := v_rolling_start + ((p_period - 1) * interval '3 months');
64
v_period_end := v_period_start + interval '3 months';
65
66
RETURN QUERY
52
RETURN QUERY
67
SELECT
53
SELECT
68
coa.id AS account_id,
54
coa.id AS account_id,
69
coa.name::text AS account_name,
55
coa.name AS account_name,
70
coa.account_number::text AS account_number,
56
coa.account_number,
71
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
57
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
72
v_finance_year::text AS financial_year
58
v_finance_year AS financial_year
73
FROM public.journal_entries je
59
FROM
74
JOIN public.transaction_headers th ON je.transaction_id = th.id
60
public.journal_entries je
75
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
61
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
76
WHERE th.company_id = p_company_id
62
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
77
AND COALESCE(th.is_reversed, FALSE) = FALSE
63
WHERE
78
AND COALESCE(je.is_deleted, FALSE) = FALSE
64
th.company_id = p_company_id
79
AND je.entry_type = 'D'
65
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
80
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
66
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
67
AND (
68
(p_period = 1 AND EXTRACT(MONTH FROM je.transaction_date) IN (4, 5, 6)) OR
69
(p_period = 2 AND EXTRACT(MONTH FROM je.transaction_date) IN (7, 8, 9)) OR
70
(p_period = 3 AND EXTRACT(MONTH FROM je.transaction_date) IN (10, 11, 12)) OR
71
(p_period = 4 AND EXTRACT(MONTH FROM je.transaction_date) IN (1, 2, 3))
72
)
73
AND je.is_deleted = FALSE
81
AND coa.name NOT IN ('Rounding Gain')
74
AND coa.name NOT IN ('Rounding Gain')
82
AND je.transaction_date >= v_period_start
75
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
83
AND je.transaction_date < v_period_end
84
GROUP BY coa.id, coa.name, coa.account_number
85
ORDER BY total_expense DESC;
76
ORDER BY total_expense DESC;
86
77
87
ELSIF p_period_type = CONST_HALF_YEARLY THEN
78
ELSIF p_period_type = CONST_HALF_YEARLY THEN
88
v_period_start := v_rolling_start + ((p_period - 1) * interval '6 months');
89
v_period_end := v_period_start + interval '6 months';
90
91
RETURN QUERY
79
RETURN QUERY
92
SELECT
80
SELECT
93
coa.id AS account_id,
81
coa.id AS account_id,
94
coa.name::text AS account_name,
82
coa.name AS account_name,
95
coa.account_number::text AS account_number,
83
coa.account_number,
96
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
84
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
97
v_finance_year::text AS financial_year
85
v_finance_year AS financial_year
98
FROM public.journal_entries je
86
FROM
99
JOIN public.transaction_headers th ON je.transaction_id = th.id
87
public.journal_entries je
100
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
88
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
101
WHERE th.company_id = p_company_id
89
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
102
AND COALESCE(th.is_reversed, FALSE) = FALSE
90
WHERE
103
AND COALESCE(je.is_deleted, FALSE) = FALSE
91
th.company_id = p_company_id
104
AND je.entry_type = 'D'
92
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
105
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
93
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
94
AND (
95
(p_period = 1 AND EXTRACT(MONTH FROM je.transaction_date) BETWEEN 4 AND 9) OR
96
(p_period = 2 AND (EXTRACT(MONTH FROM je.transaction_date) BETWEEN 10 AND 12 OR EXTRACT(MONTH FROM je.transaction_date) BETWEEN 1 AND 3))
97
)
98
AND je.is_deleted = FALSE
106
AND coa.name NOT IN ('Rounding Gain')
99
AND coa.name NOT IN ('Rounding Gain')
107
AND je.transaction_date >= v_period_start
100
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
108
AND je.transaction_date < v_period_end
109
GROUP BY coa.id, coa.name, coa.account_number
110
ORDER BY total_expense DESC;
101
ORDER BY total_expense DESC;
111
102
112
ELSIF p_period_type = CONST_YEARLY THEN
103
ELSIF p_period_type = CONST_YEARLY THEN
113
RETURN QUERY
104
RETURN QUERY
114
SELECT
105
SELECT
115
coa.id AS account_id,
106
coa.id AS account_id,
116
coa.name::text AS account_name,
107
coa.name AS account_name,
117
coa.account_number::text AS account_number,
108
coa.account_number,
118
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
109
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
119
v_finance_year::text AS financial_year
110
v_finance_year AS financial_year
120
FROM public.journal_entries je
111
FROM
121
JOIN public.transaction_headers th ON je.transaction_id = th.id
112
public.journal_entries je
122
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
113
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
123
WHERE th.company_id = p_company_id
114
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
124
AND COALESCE(th.is_reversed, FALSE) = FALSE
115
WHERE
125
AND COALESCE(je.is_deleted, FALSE) = FALSE
116
th.company_id = p_company_id
126
AND je.entry_type = 'D'
117
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
127
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
118
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
119
AND je.is_deleted = FALSE
128
AND coa.name NOT IN ('Rounding Gain')
120
AND coa.name NOT IN ('Rounding Gain')
129
AND je.transaction_date >= v_rolling_start
121
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
130
AND je.transaction_date < v_rolling_end
131
GROUP BY coa.id, coa.name, coa.account_number
132
ORDER BY total_expense DESC;
133
134
ELSIF p_period_type = CONST_YTD THEN
135
RETURN QUERY
136
SELECT
137
coa.id AS account_id,
138
coa.name::text AS account_name,
139
coa.account_number::text AS account_number,
140
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
141
v_finance_year::text AS financial_year
142
FROM public.journal_entries je
143
JOIN public.transaction_headers th ON je.transaction_id = th.id
144
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
145
WHERE th.company_id = p_company_id
146
AND COALESCE(th.is_reversed, FALSE) = FALSE
147
AND COALESCE(je.is_deleted, FALSE) = FALSE
148
AND je.entry_type = 'D'
149
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
150
AND coa.name NOT IN ('Rounding Gain')
151
AND je.transaction_date >= v_rolling_start
152
AND je.transaction_date <= CURRENT_DATE
153
GROUP BY coa.id, coa.name, coa.account_number
154
ORDER BY total_expense DESC;
122
ORDER BY total_expense DESC;
155
123
156
ELSE
124
ELSE
157
RAISE EXCEPTION 'Invalid period type: %', p_period_type;
125
RAISE EXCEPTION 'Invalid period type: %', p_period_type;
158
END IF;
126
END IF;
159
END;
127
END;
160
$function$
128
$function$
|
|||||
| Function | get_five_years_expenses | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_five_years_expenses(p_company_id uuid, p_finance_year_id integer)
2
RETURNS TABLE(id uuid, financial_year_range text, account_name text, y1_apr numeric, y1_may numeric, y1_jun numeric, y1_jul numeric, y1_aug numeric, y1_sep numeric, y1_oct numeric, y1_nov numeric, y1_dec numeric, y1_jan numeric, y1_feb numeric, y1_mar numeric, y2_apr numeric, y2_may numeric, y2_jun numeric, y2_jul numeric, y2_aug numeric, y2_sep numeric, y2_oct numeric, y2_nov numeric, y2_dec numeric, y2_jan numeric, y2_feb numeric, y2_mar numeric, y3_apr numeric, y3_may numeric, y3_jun numeric, y3_jul numeric, y3_aug numeric, y3_sep numeric, y3_oct numeric, y3_nov numeric, y3_dec numeric, y3_jan numeric, y3_feb numeric, y3_mar numeric, y4_apr numeric, y4_may numeric, y4_jun numeric, y4_jul numeric, y4_aug numeric, y4_sep numeric, y4_oct numeric, y4_nov numeric, y4_dec numeric, y4_jan numeric, y4_feb numeric, y4_mar numeric, y5_apr numeric, y5_may numeric, y5_jun numeric, y5_jul numeric, y5_aug numeric, y5_sep numeric, y5_oct numeric, y5_nov numeric, y5_dec numeric, y5_jan numeric, y5_feb numeric, y5_mar numeric)
3
LANGUAGE plpgsql
4
STABLE PARALLEL SAFE
5
AS $function$
6
DECLARE
7
current_year_start DATE;
8
current_year_end DATE;
9
previous_year1_start DATE;
10
previous_year1_end DATE;
11
previous_year2_start DATE;
12
previous_year2_end DATE;
13
previous_year3_start DATE;
14
previous_year3_end DATE;
15
previous_year4_start DATE;
16
previous_year4_end DATE;
17
CONST_EXPENSE_TYPE_ID INTEGER := 5;
18
BEGIN
19
-- Get start/end of current year
20
SELECT fy.start_date, fy.end_date
21
INTO current_year_start, current_year_end
22
FROM public.finance_year fy
23
WHERE fy.id = p_finance_year_id;
24
25
IF current_year_start IS NULL OR current_year_end IS NULL THEN
26
RAISE EXCEPTION 'Financial year with ID % not found', p_finance_year_id;
27
END IF;
28
29
-- Previous year 1
30
SELECT fy.start_date, fy.end_date
31
INTO previous_year1_start, previous_year1_end
32
FROM public.finance_year fy
33
WHERE fy.end_date < current_year_start
34
ORDER BY fy.end_date DESC
35
LIMIT 1;
36
37
-- Previous year 2
38
SELECT fy.start_date, fy.end_date
39
INTO previous_year2_start, previous_year2_end
40
FROM public.finance_year fy
41
WHERE fy.end_date < previous_year1_start
42
ORDER BY fy.end_date DESC
43
LIMIT 1;
44
45
-- Previous year 3
46
SELECT fy.start_date, fy.end_date
47
INTO previous_year3_start, previous_year3_end
48
FROM public.finance_year fy
49
WHERE fy.end_date < previous_year2_start
50
ORDER BY fy.end_date DESC
51
LIMIT 1;
52
53
-- Previous year 4
54
SELECT fy.start_date, fy.end_date
55
INTO previous_year4_start, previous_year4_end
56
FROM public.finance_year fy
57
WHERE fy.end_date < previous_year3_start
58
ORDER BY fy.end_date DESC
59
LIMIT 1;
60
61
RETURN QUERY
62
WITH RECURSIVE AccountHierarchy AS (
63
SELECT coa.id AS account_id, coa.name, coa.parent_account_id
64
FROM public.chart_of_accounts coa
65
WHERE coa.account_type_id = CONST_EXPENSE_TYPE_ID
66
UNION ALL
67
SELECT coa.id, coa.name, coa.parent_account_id
68
FROM public.chart_of_accounts coa
69
INNER JOIN AccountHierarchy ah ON coa.parent_account_id = ah.account_id
70
),
71
MonthlyExpenses AS (
72
SELECT
73
ah.name AS account_name,
74
TO_CHAR(je.transaction_date, 'MM') AS transaction_month,
75
CASE
76
WHEN je.transaction_date BETWEEN previous_year4_start AND previous_year3_start - INTERVAL '1 day' THEN 'y1'
77
WHEN je.transaction_date BETWEEN previous_year3_start AND previous_year2_start - INTERVAL '1 day' THEN 'y2'
78
WHEN je.transaction_date BETWEEN previous_year2_start AND previous_year1_start - INTERVAL '1 day' THEN 'y3'
79
WHEN je.transaction_date BETWEEN previous_year1_start AND current_year_start - INTERVAL '1 day' THEN 'y4'
80
WHEN je.transaction_date BETWEEN current_year_start AND current_year_end THEN 'y5'
81
END AS year_group,
82
SUM(je.amount) AS monthly_amount
83
FROM AccountHierarchy ah
84
LEFT JOIN public.journal_entries je ON je.account_id = ah.account_id
85
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
86
WHERE th.company_id = p_company_id
87
AND je.transaction_date BETWEEN previous_year4_start AND current_year_end
88
AND je.is_deleted = FALSE
89
GROUP BY ah.name, year_group, TO_CHAR(je.transaction_date, 'MM')
90
),
91
AggregatedExpenses AS (
92
SELECT
93
me.account_name,
94
-- y1
95
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y1_apr,
96
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y1_may,
97
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y1_jun,
98
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y1_jul,
99
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y1_aug,
100
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y1_sep,
101
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y1_oct,
102
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y1_nov,
103
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y1_dec,
104
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y1_jan,
105
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y1_feb,
106
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y1_mar,
107
-- y2
108
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y2_apr,
109
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y2_may,
110
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y2_jun,
111
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y2_jul,
112
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y2_aug,
113
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y2_sep,
114
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y2_oct,
115
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y2_nov,
116
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y2_dec,
117
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y2_jan,
118
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y2_feb,
119
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y2_mar,
120
-- y3
121
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y3_apr,
122
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y3_may,
123
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y3_jun,
124
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y3_jul,
125
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y3_aug,
126
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y3_sep,
127
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y3_oct,
128
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y3_nov,
129
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y3_dec,
130
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y3_jan,
131
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y3_feb,
132
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y3_mar,
133
-- y4
134
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y4_apr,
135
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y4_may,
136
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y4_jun,
137
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y4_jul,
138
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y4_aug,
139
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y4_sep,
140
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y4_oct,
141
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y4_nov,
142
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y4_dec,
143
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y4_jan,
144
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y4_feb,
145
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y4_mar,
146
-- y5
147
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y5_apr,
148
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y5_may,
149
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y5_jun,
150
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y5_jul,
151
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y5_aug,
152
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y5_sep,
153
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y5_oct,
154
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y5_nov,
155
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y5_dec,
156
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y5_jan,
157
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y5_feb,
158
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y5_mar
159
FROM MonthlyExpenses me
160
GROUP BY me.account_name
161
)
162
SELECT
163
gen_random_uuid() AS id,
164
CONCAT(EXTRACT(YEAR FROM previous_year4_start), '-', EXTRACT(YEAR FROM current_year_end)) AS financial_year_range,
165
ae.account_name,
166
-- y1 months
167
ae.y1_apr, ae.y1_may, ae.y1_jun, ae.y1_jul, ae.y1_aug, ae.y1_sep, ae.y1_oct, ae.y1_nov, ae.y1_dec, ae.y1_jan, ae.y1_feb, ae.y1_mar,
168
-- y2 months
169
ae.y2_apr, ae.y2_may, ae.y2_jun, ae.y2_jul, ae.y2_aug, ae.y2_sep, ae.y2_oct, ae.y2_nov, ae.y2_dec, ae.y2_jan, ae.y2_feb, ae.y2_mar,
170
-- y3 months
171
ae.y3_apr, ae.y3_may, ae.y3_jun, ae.y3_jul, ae.y3_aug, ae.y3_sep, ae.y3_oct, ae.y3_nov, ae.y3_dec, ae.y3_jan, ae.y3_feb, ae.y3_mar,
172
-- y4 months
173
ae.y4_apr, ae.y4_may, ae.y4_jun, ae.y4_jul, ae.y4_aug, ae.y4_sep, ae.y4_oct, ae.y4_nov, ae.y4_dec, ae.y4_jan, ae.y4_feb, ae.y4_mar,
174
-- y5 months
175
ae.y5_apr, ae.y5_may, ae.y5_jun, ae.y5_jul, ae.y5_aug, ae.y5_sep, ae.y5_oct, ae.y5_nov, ae.y5_dec, ae.y5_jan, ae.y5_feb, ae.y5_mar
176
FROM AggregatedExpenses ae
177
ORDER BY
178
COALESCE(ae.y1_apr,0)+COALESCE(ae.y1_may,0)+COALESCE(ae.y1_jun,0)+COALESCE(ae.y1_jul,0)+COALESCE(ae.y1_aug,0)+COALESCE(ae.y1_sep,0)+COALESCE(ae.y1_oct,0)+COALESCE(ae.y1_nov,0)+COALESCE(ae.y1_dec,0)+COALESCE(ae.y1_jan,0)+COALESCE(ae.y1_feb,0)+COALESCE(ae.y1_mar,0)
179
+COALESCE(ae.y2_apr,0)+COALESCE(ae.y2_may,0)+COALESCE(ae.y2_jun,0)+COALESCE(ae.y2_jul,0)+COALESCE(ae.y2_aug,0)+COALESCE(ae.y2_sep,0)+COALESCE(ae.y2_oct,0)+COALESCE(ae.y2_nov,0)+COALESCE(ae.y2_dec,0)+COALESCE(ae.y2_jan,0)+COALESCE(ae.y2_feb,0)+COALESCE(ae.y2_mar,0)
180
+COALESCE(ae.y3_apr,0)+COALESCE(ae.y3_may,0)+COALESCE(ae.y3_jun,0)+COALESCE(ae.y3_jul,0)+COALESCE(ae.y3_aug,0)+COALESCE(ae.y3_sep,0)+COALESCE(ae.y3_oct,0)+COALESCE(ae.y3_nov,0)+COALESCE(ae.y3_dec,0)+COALESCE(ae.y3_jan,0)+COALESCE(ae.y3_feb,0)+COALESCE(ae.y3_mar,0)
181
+COALESCE(ae.y4_apr,0)+COALESCE(ae.y4_may,0)+COALESCE(ae.y4_jun,0)+COALESCE(ae.y4_jul,0)+COALESCE(ae.y4_aug,0)+COALESCE(ae.y4_sep,0)+COALESCE(ae.y4_oct,0)+COALESCE(ae.y4_nov,0)+COALESCE(ae.y4_dec,0)+COALESCE(ae.y4_jan,0)+COALESCE(ae.y4_feb,0)+COALESCE(ae.y4_mar,0)
182
+COALESCE(ae.y5_apr,0)+COALESCE(ae.y5_may,0)+COALESCE(ae.y5_jun,0)+COALESCE(ae.y5_jul,0)+COALESCE(ae.y5_aug,0)+COALESCE(ae.y5_sep,0)+COALESCE(ae.y5_oct,0)+COALESCE(ae.y5_nov,0)+COALESCE(ae.y5_dec,0)+COALESCE(ae.y5_jan,0)+COALESCE(ae.y5_feb,0)+COALESCE(ae.y5_mar,0)
183
DESC
184
LIMIT 12;
185
END;
186
$function$
|
|||||
| Function | mark_original_transaction_reversed | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.mark_original_transaction_reversed()
2
RETURNS trigger
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
-- Only act when inserted row is a reversal
7
IF NEW.is_reversed = true THEN
8
9
UPDATE public.transaction_headers th
10
SET
11
is_reversed = true,
12
modified_on_utc = NOW()
13
WHERE th.company_id = NEW.company_id
14
AND th.document_number = NEW.document_number
15
AND th.id <> NEW.id
16
AND th.is_reversed = false;
17
18
END IF;
19
20
RETURN NEW;
21
END;
22
$function$
|
|||||
| Function | get_three_years_expenses | Match | ||||||
| Function | get_all_coa | Match | ||||||
| Function | pg_get_tabledef | Match | ||||||
| Function | get_cash_flow_statement | Match | ||||||
| Function | get_comparative_accounts_overview | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_comparative_accounts_overview(p_company_id uuid, p_fin_year_id integer)
2
RETURNS TABLE(account_id uuid, account_name text, parent_account_id uuid, hierarchy_path text[], order_sequence text, financial_year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_sum numeric, difference numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_current_year_id INTEGER := p_fin_year_id;
7
v_previous_year_id INTEGER;
8
v_organization_id UUID;
9
BEGIN
10
-- Get organization
11
SELECT c.organization_id INTO v_organization_id
12
FROM public.companies c
13
WHERE c.id = p_company_id;
14
15
-- Find previous financial year (based on start_date)
16
SELECT fy.id
17
INTO v_previous_year_id
18
FROM finance_year fy
19
WHERE fy.start_date < (
20
SELECT start_date FROM finance_year WHERE id = v_current_year_id
21
)
22
ORDER BY fy.start_date DESC
23
LIMIT 1;
24
25
RETURN QUERY
26
WITH RECURSIVE account_with_path AS (
27
-- Root accounts
28
SELECT
29
coa.id,
30
coa.name,
31
coa.parent_account_id,
32
ARRAY[coa.name]::text[] AS path,
33
coa.account_number::text AS order_sequence
34
FROM chart_of_accounts coa
35
WHERE coa.organization_id = v_organization_id
36
AND (coa.parent_account_id IS NULL OR coa.parent_account_id = '00000000-0000-0000-0000-000000000000')
37
AND coa.is_deleted = FALSE
38
39
UNION ALL
40
41
-- Children
42
SELECT
43
c.id,
44
c.name,
45
c.parent_account_id,
46
awp.path || c.name,
47
awp.order_sequence || '.' || c.account_number::text
48
FROM chart_of_accounts c
49
JOIN account_with_path awp ON c.parent_account_id = awp.id
50
WHERE c.organization_id = v_organization_id
51
AND c.is_deleted = FALSE
52
),
53
leaf_accounts AS (
54
SELECT a.id
55
FROM account_with_path a
56
WHERE NOT EXISTS (
57
SELECT 1 FROM chart_of_accounts c
58
WHERE c.parent_account_id = a.id
59
AND c.organization_id = v_organization_id
60
AND c.is_deleted = FALSE
61
)
62
),
63
financial_years AS (
64
SELECT id AS fin_year_id, start_date, end_date
65
FROM finance_year
66
WHERE id IN (v_current_year_id, v_previous_year_id)
67
),
68
aggregated_data AS (
69
SELECT
70
awp.id,
71
awp.name,
72
awp.parent_account_id,
73
awp.path AS hierarchy_path,
74
awp.order_sequence,
75
fy.fin_year_id,
76
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN je.amount END), 0) AS apr,
77
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN je.amount END), 0) AS may,
78
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN je.amount END), 0) AS jun,
79
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN je.amount END), 0) AS jul,
80
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN je.amount END), 0) AS aug,
81
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN je.amount END), 0) AS sep,
82
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN je.amount END), 0) AS oct,
83
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN je.amount END), 0) AS nov,
84
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN je.amount END), 0) AS "dec",
85
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN je.amount END), 0) AS jan,
86
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN je.amount END), 0) AS feb,
87
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN je.amount END), 0) AS mar
88
FROM account_with_path awp
89
JOIN leaf_accounts la ON la.id = awp.id -- ✅ only leaf accounts get financial years
90
CROSS JOIN financial_years fy
91
LEFT JOIN journal_entries je
92
ON je.account_id = awp.id
93
AND je.transaction_date BETWEEN fy.start_date AND fy.end_date
94
LEFT JOIN transaction_headers th
95
ON th.id = je.transaction_id
96
AND th.company_id = p_company_id
97
WHERE (th.transaction_source_type IS NULL OR th.transaction_source_type != 18)
98
GROUP BY awp.id, awp.name, awp.parent_account_id, awp.path, awp.order_sequence, fy.fin_year_id
99
)
100
SELECT
101
a1.id AS account_id,
102
a1.name AS account_name,
103
a1.parent_account_id,
104
a1.hierarchy_path,
105
a1.order_sequence,
106
a1.fin_year_id AS financial_year,
107
a1.apr, a1.may, a1.jun, a1.jul, a1.aug, a1.sep, a1.oct, a1.nov, a1."dec",
108
a1.jan, a1.feb, a1.mar,
109
(COALESCE(a1.apr,0) + COALESCE(a1.may,0) + COALESCE(a1.jun,0) +
110
COALESCE(a1.jul,0) + COALESCE(a1.aug,0) + COALESCE(a1.sep,0) +
111
COALESCE(a1.oct,0) + COALESCE(a1.nov,0) + COALESCE(a1."dec",0) +
112
COALESCE(a1.jan,0) + COALESCE(a1.feb,0) + COALESCE(a1.mar,0)) AS total_sum,
113
((COALESCE(a1.apr,0) + COALESCE(a1.may,0) + COALESCE(a1.jun,0) +
114
COALESCE(a1.jul,0) + COALESCE(a1.aug,0) + COALESCE(a1.sep,0) +
115
COALESCE(a1.oct,0) + COALESCE(a1.nov,0) + COALESCE(a1."dec",0) +
116
COALESCE(a1.jan,0) + COALESCE(a1.feb,0) + COALESCE(a1.mar,0))
117
-
118
(COALESCE(a2.apr,0) + COALESCE(a2.may,0) + COALESCE(a2.jun,0) +
119
COALESCE(a2.jul,0) + COALESCE(a2.aug,0) + COALESCE(a2.sep,0) +
120
COALESCE(a2.oct,0) + COALESCE(a2.nov,0) + COALESCE(a2."dec",0) +
121
COALESCE(a2.jan,0) + COALESCE(a2.feb,0) + COALESCE(a2.mar,0))
122
) AS difference
123
FROM aggregated_data a1
124
LEFT JOIN aggregated_data a2
125
ON a1.id = a2.id
126
AND a1.fin_year_id = v_current_year_id
127
AND a2.fin_year_id = v_previous_year_id
128
ORDER BY a1.order_sequence, a1.fin_year_id;
129
END;
130
$function$
|
|||||
| Function | fn_check_account_id_exists | Match | ||||||
| Function | create_notification_with_message | Match | ||||||
| Function | approve_visitor_for_unit | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.approve_visitor_for_unit(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid, p_resident_id integer)
2
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, final_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
local_total_visits INT;
7
local_approved_count INT;
8
local_rejected_count INT;
9
local_new_visitor_status INT;
10
BEGIN
11
-- Step 1: Approve for this unit only
12
UPDATE public.multi_unit_visits
13
SET visitor_statuses = 2, -- Approved
14
modified_on_utc = NOW(),
15
modified_by = p_resident_user
16
WHERE visitor_log_id = p_visitor_log_id
17
AND unit_id = p_unit_id
18
AND is_deleted = FALSE;
19
20
IF NOT FOUND THEN
21
-- No update happened (invalid visitor_log_id or unit_id)
22
RETURN;
23
END IF;
24
25
-- Step 2: Recalculate parent visitor log status
26
SELECT
27
COUNT(*),
28
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2), -- Approved
29
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6) -- Rejected
30
INTO
31
local_total_visits,
32
local_approved_count,
33
local_rejected_count
34
FROM public.multi_unit_visits muv
35
WHERE muv.visitor_log_id = p_visitor_log_id
36
AND muv.is_deleted = FALSE;
37
38
-- Step 3: Decide new parent status
39
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
40
local_new_visitor_status := 2; -- All Approved
41
ELSIF local_approved_count > 0 THEN
42
local_new_visitor_status := 7; -- Partial Approved
43
ELSIF local_rejected_count > 0 THEN
44
local_new_visitor_status := 6; -- Rejected
45
ELSE
46
local_new_visitor_status := 1; -- Pending
47
END IF;
48
49
UPDATE public.visitor_logs
50
SET visitor_status_id = local_new_visitor_status,
51
modified_on_utc = NOW(),
52
modified_by = p_resident_user
53
WHERE id = p_visitor_log_id;
54
55
-- Step 4: Return minimal information for the service layer
56
RETURN QUERY
57
SELECT
58
p_visitor_log_id,
59
p_unit_id,
60
p_resident_id, -- approver resident
61
local_new_visitor_status;
62
63
END;
64
$function$
|
|||||
| Function | get_address_id | Match | ||||||
| Function | get_all_child_accounts | Match | ||||||
| Function | get_opening_balances | Match | ||||||
| Function | get_trial_balance_of_company_fy | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_trial_balance_of_company_fy(p_company_id uuid, p_finance_year_id integer)
1
CREATE OR REPLACE FUNCTION public.get_trial_balance_of_company_fy(p_company_id uuid, p_finance_year_id integer)
2
RETURNS TABLE(account_id uuid, account_type text, account_category text, account_number text, account_name text, total_debits numeric, total_credits numeric)
2
RETURNS TABLE(account_id uuid, account_type text, account_category text, account_number text, account_name text, total_debits numeric, total_credits numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
7
RETURN QUERY
6
RETURN QUERY
8
WITH RECURSIVE coa_hierarchy AS (
7
WITH RECURSIVE coa_hierarchy AS (
9
8
-- Base case: Fetch the top-level (root) accounts with no parent (e.g., Assets, Liabilities)
10
SELECT
9
SELECT
11
coa.id,
10
coa.id,
12
act.name AS account_type,
11
act.name AS account_type,
13
coa.account_number::integer,
12
coa.account_number::integer,
14
coa.name,
13
coa.name,
15
coa.parent_account_id,
14
coa.parent_account_id,
16
0 AS level,
15
0 AS level,
17
coa.account_number::text AS order_sequence
16
coa.account_number::text AS order_sequence
18
FROM public.chart_of_accounts coa
17
FROM
19
INNER JOIN public.account_types act
18
public.chart_of_accounts coa
20
ON coa.account_type_id = act.id
19
INNER JOIN
20
public.account_types act ON coa.account_type_id = act.id
21
WHERE
21
WHERE
22
coa.is_deleted = FALSE
22
coa.is_deleted = FALSE
23
AND coa.parent_account_id = '00000000-0000-0000-0000-000000000000'
23
AND coa.parent_account_id = '00000000-0000-0000-0000-000000000000' -- Root-level accounts
24
24
25
UNION ALL
25
UNION ALL
26
26
27
-- Recursive case: Fetch all child accounts for each parent
27
SELECT
28
SELECT
28
coa.id,
29
coa.id,
29
act.name AS account_type,
30
act.name AS account_type,
30
coa.account_number::integer,
31
coa.account_number::integer,
31
coa.name,
32
coa.name,
32
coa.parent_account_id,
33
coa.parent_account_id,
33
ch.level + 1,
34
ch.level + 1 AS level,
34
ch.order_sequence || '.' || coa.account_number::text
35
ch.order_sequence || '.' || coa.account_number::text AS order_sequence
35
FROM public.chart_of_accounts coa
36
FROM
36
INNER JOIN public.account_types act
37
public.chart_of_accounts coa
37
ON coa.account_type_id = act.id
38
INNER JOIN
38
INNER JOIN coa_hierarchy ch
39
public.account_types act ON coa.account_type_id = act.id
39
ON ch.id = coa.parent_account_id
40
INNER JOIN
40
WHERE coa.is_deleted = FALSE
41
coa_hierarchy ch ON ch.id = coa.parent_account_id
42
WHERE
43
coa.is_deleted = FALSE
41
),
44
),
42
43
coa_with_balances AS (
45
coa_with_balances AS (
46
-- Fetch the current financial year start and end date from the finance_year table
44
SELECT
47
SELECT
45
fy.start_date::DATE,
48
fy.start_date::DATE, -- Cast start_date to DATE
46
fy.end_date::DATE,
49
fy.end_date::DATE, -- Cast end_date to DATE
47
fy.year
50
fy.year
48
FROM public.finance_year fy
51
FROM
49
WHERE fy.id = p_finance_year_id
52
public.finance_year fy
53
WHERE
54
fy.id = p_finance_year_id
50
LIMIT 1
55
LIMIT 1
51
),
56
),
52
53
journal_entries_filtered AS (
57
journal_entries_filtered AS (
54
58
-- Fetch accounts that have non-zero balances within the financial year range
55
SELECT
59
SELECT
56
je.account_id,
60
je.account_id,
57
58
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) AS total_debits,
61
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) AS total_debits,
59
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) AS total_credits
62
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) AS total_credits
60
63
FROM
61
FROM public.journal_entries je
64
public.journal_entries je
62
65
JOIN
63
JOIN public.transaction_headers tr
66
public.transaction_headers th ON je.transaction_id = th.id
64
ON je.transaction_id = tr.id
67
JOIN
65
68
coa_with_balances fy ON th.transaction_date BETWEEN fy.start_date AND fy.end_date -- Filter by financial year
66
JOIN coa_with_balances fy
67
ON tr.transaction_date BETWEEN fy.start_date AND fy.end_date
68
69
WHERE
69
WHERE
70
tr.company_id = p_company_id
70
th.company_id = p_company_id
71
AND je.is_deleted = FALSE
71
AND je.is_deleted = FALSE
72
AND tr.is_deleted = FALSE
72
GROUP BY
73
73
je.account_id
74
-- 🔥 DOCUMENT-LEVEL REVERSAL SAFE LOGIC
75
AND tr.is_reversed = FALSE
76
AND NOT EXISTS (
77
SELECT 1
78
FROM public.transaction_headers tr_rev
79
WHERE tr_rev.company_id = tr.company_id
80
AND tr_rev.document_number = tr.document_number
81
AND tr_rev.is_reversed = TRUE
82
)
83
84
GROUP BY je.account_id
85
86
HAVING
74
HAVING
87
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) > 0 OR
75
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) > 0 OR
88
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) > 0
76
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) > 0
89
),
77
),
90
91
relevant_accounts AS (
78
relevant_accounts AS (
92
79
-- Select accounts that have balances directly
93
SELECT
80
SELECT
94
ch.id,
81
ch.id,
95
ch.account_type,
82
ch.account_type,
96
ch.account_number,
83
ch.account_number,
97
ch.name,
84
ch.name,
98
ch.level,
85
ch.level,
99
ch.order_sequence,
86
ch.order_sequence,
100
ch.parent_account_id,
87
ch.parent_account_id,
101
wb.total_debits,
88
wb.total_debits,
102
wb.total_credits
89
wb.total_credits
103
FROM coa_hierarchy ch
90
FROM
104
LEFT JOIN journal_entries_filtered wb
91
coa_hierarchy ch
105
ON ch.id = wb.account_id
92
LEFT JOIN
106
WHERE wb.account_id IS NOT NULL
93
journal_entries_filtered wb ON ch.id = wb.account_id
94
WHERE
95
wb.account_id IS NOT NULL -- Only accounts with actual balances
107
96
108
UNION ALL
97
UNION ALL
109
98
99
-- Recursively select parent accounts, without recalculating balances
110
SELECT
100
SELECT
111
ch.id,
101
ch.id,
112
ch.account_type,
102
ch.account_type,
113
ch.account_number,
103
ch.account_number,
114
ch.name,
104
ch.name,
115
ch.level,
105
ch.level,
116
ch.order_sequence,
106
ch.order_sequence,
117
ch.parent_account_id,
107
ch.parent_account_id,
118
NULL,
108
NULL AS total_debits,
119
NULL
109
NULL AS total_credits
120
FROM coa_hierarchy ch
110
FROM
121
INNER JOIN relevant_accounts ra
111
coa_hierarchy ch
122
ON ch.id = ra.parent_account_id
112
INNER JOIN
113
relevant_accounts ra ON ch.id = ra.parent_account_id -- Propagate balance up to parent accounts
123
)
114
)
124
115
-- Final output
125
SELECT DISTINCT ON (ra.order_sequence)
116
SELECT DISTINCT ON (ra.order_sequence)
126
ra.id::uuid AS account_id,
117
ra.id::uuid AS account_id, -- Chart of Accounts ID
127
ra.account_type::TEXT AS account_type,
118
ra.account_type::TEXT AS account_type, -- Account type
128
ac.name::TEXT AS account_category,
119
ac.name::TEXT AS account_category, -- Account category
129
ra.account_number::TEXT AS account_number,
120
ra.account_number::TEXT AS account_number, -- Account number
130
LPAD('', ra.level * 4, ' ') || ra.name::TEXT AS account_name,
121
LPAD('', ra.level * 4, ' ') || ra.name::TEXT AS account_name, -- Indented account name
131
COALESCE(ra.total_debits, 0) AS total_debits,
122
COALESCE(ra.total_debits, 0) AS total_debits, -- Sum of debits
132
COALESCE(ra.total_credits, 0) AS total_credits
123
COALESCE(ra.total_credits, 0) AS total_credits -- Sum of credits
133
FROM relevant_accounts ra
124
-- Cast end date of the financial year to DATE
134
LEFT JOIN public.chart_of_accounts coa
125
FROM
135
ON ra.id = coa.id
126
relevant_accounts ra
136
LEFT JOIN public.account_types at
127
LEFT JOIN
137
ON coa.account_type_id = at.id
128
public.chart_of_accounts coa ON ra.id = coa.id -- Join chart of accounts
138
LEFT JOIN public.account_categories ac
129
LEFT JOIN
139
ON at.account_category_id = ac.id
130
public.account_types at ON coa.account_type_id = at.id -- Join account types
140
CROSS JOIN coa_with_balances fy
131
LEFT JOIN
141
ORDER BY ra.order_sequence;
132
public.account_categories ac ON at.account_category_id = ac.id -- Join account categories
142
133
CROSS JOIN
134
coa_with_balances fy -- Include the financial year details
135
ORDER BY
136
ra.order_sequence; -- Maintain hierarchy in the output
143
END;
137
END;
144
$function$
138
$function$
|
|||||
| Function | pgp_pub_encrypt_bytea | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_encrypt_bytea(bytea, bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_encrypt_bytea$function$
|
|||||
| Function | pgp_pub_decrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt(bytea, bytea)
2
RETURNS text
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_text$function$
|
|||||
| Function | pgp_pub_decrypt_bytea | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt_bytea(bytea, bytea)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_bytea$function$
|
|||||
| Function | search_function_with_paramter | Match | ||||||
| Function | get_balance_sheet | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_balance_sheet(p_company_id uuid, p_finance_year_id integer, p_as_on_date date, p_is_get_all_for_organizations boolean)
1
CREATE OR REPLACE FUNCTION public.get_balance_sheet(p_company_id uuid, p_finance_year_id integer, p_as_on_date date, p_is_get_all_for_organizations boolean)
2
RETURNS TABLE(account_id uuid, account_type text, account_name text, amount numeric, category text)
2
RETURNS TABLE(account_type text, account_name text, amount numeric, category text)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
5
DECLARE
6
DECLARE
6
v_start_date DATE;
7
v_start_date DATE;
7
v_end_date DATE;
8
v_end_date DATE;
8
v_company_ids UUID[];
9
v_company_ids UUID[];
9
v_organization_id UUID;
10
v_organization_id UUID;
10
asset_account_ids integer[];
11
asset_account_ids integer[];
11
liability_account_ids integer[];
12
liability_account_ids integer[];
12
equity_account_ids integer[];
13
equity_account_ids integer[];
13
BEGIN
14
BEGIN
14
-- Fetch financial year start and end dates
15
-- Fetch financial year start and end dates
15
SELECT fy.start_date, fy.end_date
16
SELECT fy.start_date, fy.end_date
16
INTO v_start_date, v_end_date
17
INTO v_start_date, v_end_date
17
FROM public.finance_year fy
18
FROM public.finance_year fy
18
WHERE fy.id = p_finance_year_id;
19
WHERE fy.id = p_finance_year_id;
19
20
20
-- Cap the end date by the given as_on_date
21
-- Cap the end date by the given as_on_date
21
v_end_date := LEAST(v_end_date, p_as_on_date);
22
v_end_date := LEAST(v_end_date, p_as_on_date);
22
23
23
-- Get organization id of company
24
-- Get organization id of company
24
SELECT c.organization_id INTO v_organization_id
25
SELECT c.organization_id INTO v_organization_id
25
FROM public.companies c
26
FROM public.companies c
26
WHERE c.id = p_company_id;
27
WHERE c.id = p_company_id;
27
28
28
-- Get account type hierarchies
29
-- Get account type hierarchies for assets, liabilities, and equity
29
asset_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(1)); -- Assets
30
asset_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(1)); -- Assets
30
liability_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(2)); -- Liabilities
31
liability_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(2)); -- Liabilities
31
equity_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(3)); -- Equity
32
equity_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(3)); -- Equity
32
33
33
-- Company IDs based on flag
34
-- Determine company IDs based on flag
34
IF p_is_get_all_for_organizations THEN
35
IF p_is_get_all_for_organizations THEN
35
SELECT ARRAY_AGG(id)
36
SELECT ARRAY_AGG(id)
36
INTO v_company_ids
37
INTO v_company_ids
37
FROM public.companies
38
FROM public.companies
38
WHERE organization_id = v_organization_id;
39
WHERE organization_id = v_organization_id;
39
ELSE
40
ELSE
40
v_company_ids := ARRAY[p_company_id];
41
v_company_ids := ARRAY[p_company_id];
41
END IF;
42
END IF;
42
43
43
-- Assets
44
-- Return Assets
44
RETURN QUERY
45
RETURN QUERY
45
SELECT
46
SELECT
46
coa.id,
47
at.name AS account_type,
47
at.name,
48
coa.name AS account_name,
48
coa.name,
49
COALESCE(
49
COALESCE(SUM(
50
SUM(
50
CASE
51
CASE
51
WHEN je.entry_type = 'D' THEN je.amount
52
WHEN ob.entry_type = 'D' THEN ob.balance
52
WHEN je.entry_type = 'C' THEN -je.amount
53
ELSE -ob.balance
54
END
55
), 0) +
56
COALESCE(
57
SUM(CASE
58
WHEN je.entry_type = 'D' THEN je.amount -- Debit increases asset balance
59
WHEN je.entry_type = 'C' THEN -je.amount -- Credit decreases asset balance
53
ELSE 0
60
ELSE 0
54
END
61
END), 0
55
),0) AS amount,
62
) AS amount,
56
'Assets'
63
'Assets' AS category
57
FROM public.chart_of_accounts coa
64
FROM public.chart_of_accounts coa
58
JOIN public.account_types at ON coa.account_type_id = at.id
65
LEFT JOIN public.journal_entries je ON je.account_id = coa.id
59
JOIN public.journal_entries je ON je.account_id = coa.id
66
LEFT JOIN public.transaction_headers th ON je.transaction_id = th.id
60
JOIN public.transaction_headers th ON th.id = je.transaction_id
67
LEFT JOIN public.opening_balances ob ON ob.account_id = coa.id
68
AND ob.organization_id = v_organization_id
69
AND ob.finyear_id = p_finance_year_id
70
AND ob.is_deleted = FALSE
71
INNER JOIN public.account_types at ON coa.account_type_id = at.id
61
WHERE th.company_id = ANY(v_company_ids)
72
WHERE th.company_id = ANY(v_company_ids)
62
AND th.transaction_date BETWEEN v_start_date AND v_end_date
73
AND th.transaction_date BETWEEN v_start_date AND v_end_date
63
AND NOT th.is_deleted
74
AND NOT th.is_deleted
64
AND NOT je.is_deleted
75
AND NOT je.is_deleted
65
AND coa.account_type_id = ANY(asset_account_ids)
76
AND coa.account_type_id = ANY(asset_account_ids)
66
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
77
GROUP BY at.name, coa.name, coa.account_number, at.id
67
ORDER BY coa.account_number, at.id;
78
ORDER BY coa.account_number, at.id;
68
79
69
-- Liabilities
80
-- Return Liabilities
70
RETURN QUERY
81
RETURN QUERY
71
SELECT
82
SELECT
72
coa.id,
83
at.name AS account_type,
73
at.name,
84
coa.name AS account_name,
74
coa.name,
85
COALESCE(
75
COALESCE(SUM(
86
SUM(
76
CASE
87
CASE
88
WHEN ob.entry_type = 'C' THEN ob.balance
89
ELSE -ob.balance
90
END
91
), 0) +
92
COALESCE(
93
SUM(CASE
77
WHEN je.entry_type = 'C' THEN je.amount
94
WHEN je.entry_type = 'C' THEN je.amount
78
WHEN je.entry_type = 'D' THEN -je.amount
95
WHEN je.entry_type = 'D' THEN -je.amount
79
ELSE 0
96
ELSE 0
80
END
97
END), 0
81
),0) AS amount,
98
) AS amount,
82
'Liabilities'
99
'Liabilities' AS category
83
FROM public.chart_of_accounts coa
100
FROM public.chart_of_accounts coa
84
JOIN public.account_types at ON coa.account_type_id = at.id
101
LEFT JOIN public.journal_entries je ON je.account_id = coa.id
85
JOIN public.journal_entries je ON je.account_id = coa.id
102
LEFT JOIN public.transaction_headers th ON je.transaction_id = th.id
86
JOIN public.transaction_headers th ON th.id = je.transaction_id
103
LEFT JOIN public.opening_balances ob ON ob.account_id = coa.id
104
AND ob.organization_id = v_organization_id
105
AND ob.finyear_id = p_finance_year_id
106
AND ob.is_deleted = FALSE
107
INNER JOIN public.account_types at ON coa.account_type_id = at.id
87
WHERE th.company_id = ANY(v_company_ids)
108
WHERE th.company_id = ANY(v_company_ids)
88
AND th.transaction_date BETWEEN v_start_date AND v_end_date
109
AND th.transaction_date BETWEEN v_start_date AND v_end_date
89
AND NOT th.is_deleted
110
AND NOT th.is_deleted
90
AND NOT je.is_deleted
111
AND NOT je.is_deleted
91
AND coa.account_type_id = ANY(liability_account_ids)
112
AND coa.account_type_id = ANY(liability_account_ids)
92
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
113
GROUP BY at.name, coa.name, coa.account_number, at.id
93
ORDER BY coa.account_number, at.id;
114
ORDER BY coa.account_number, at.id;
94
115
95
-- Equity
116
-- Return Equity
96
RETURN QUERY
117
RETURN QUERY
97
SELECT
118
SELECT
98
coa.id,
119
at.name AS account_type,
99
at.name,
120
coa.name AS account_name,
100
coa.name,
121
COALESCE(
101
COALESCE(SUM(
122
SUM(
102
CASE
123
CASE
124
WHEN ob.entry_type = 'C' THEN ob.balance
125
ELSE -ob.balance
126
END
127
), 0) +
128
COALESCE(
129
SUM(CASE
103
WHEN je.entry_type = 'C' THEN je.amount
130
WHEN je.entry_type = 'C' THEN je.amount
104
WHEN je.entry_type = 'D' THEN -je.amount
131
WHEN je.entry_type = 'D' THEN -je.amount
105
ELSE 0
132
ELSE 0
106
END
133
END), 0
107
),0) AS amount,
134
) AS amount,
108
'Equity'
135
'Equity' AS category
109
FROM public.chart_of_accounts coa
136
FROM public.chart_of_accounts coa
110
JOIN public.account_types at ON coa.account_type_id = at.id
137
LEFT JOIN public.journal_entries je ON je.account_id = coa.id
111
JOIN public.journal_entries je ON je.account_id = coa.id
138
LEFT JOIN public.transaction_headers th ON je.transaction_id = th.id
112
JOIN public.transaction_headers th ON th.id = je.transaction_id
139
LEFT JOIN public.opening_balances ob ON ob.account_id = coa.id
140
AND ob.organization_id = v_organization_id
141
AND ob.finyear_id = p_finance_year_id
142
AND ob.is_deleted = FALSE
143
INNER JOIN public.account_types at ON coa.account_type_id = at.id
113
WHERE th.company_id = ANY(v_company_ids)
144
WHERE th.company_id = ANY(v_company_ids)
114
AND th.transaction_date BETWEEN v_start_date AND v_end_date
145
AND th.transaction_date BETWEEN v_start_date AND v_end_date
115
AND NOT th.is_deleted
146
AND NOT th.is_deleted
116
AND NOT je.is_deleted
147
AND NOT je.is_deleted
117
AND coa.account_type_id = ANY(equity_account_ids)
148
AND coa.account_type_id = ANY(equity_account_ids)
118
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
149
GROUP BY at.name, coa.name, coa.account_number, at.id
119
ORDER BY coa.account_number, at.id;
150
ORDER BY coa.account_number, at.id;
120
151
121
END;
152
END;
122
$function$
153
$function$
|
|||||
| Function | get_all_account_groups | Match | ||||||
| Function | get_user_notifications_with_message | Match | ||||||
| Function | get_city_id | Match | ||||||
| Function | grant_full_schema_access | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.grant_full_schema_access(p_schema text, p_user text)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
obj RECORD;
7
BEGIN
8
-- Grant on tables
9
FOR obj IN
10
SELECT table_name
11
FROM information_schema.tables
12
WHERE table_schema = p_schema
13
AND table_type = 'BASE TABLE'
14
LOOP
15
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.%I TO %I;', p_schema, obj.table_name, p_user);
16
RAISE NOTICE 'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %.% TO %;', p_schema, obj.table_name, p_user;
17
END LOOP;
18
19
-- Grant on sequences (USAGE + SELECT + UPDATE: full coverage)
20
FOR obj IN
21
SELECT c.relname AS sequence_name
22
FROM pg_class c
23
JOIN pg_namespace n ON n.oid = c.relnamespace
24
WHERE c.relkind = 'S'
25
AND n.nspname = p_schema
26
LOOP
27
EXECUTE format('GRANT USAGE, SELECT, UPDATE ON SEQUENCE %I.%I TO %I;', p_schema, obj.sequence_name, p_user);
28
RAISE NOTICE 'GRANT USAGE, SELECT, UPDATE ON SEQUENCE %.% TO %;', p_schema, obj.sequence_name, p_user;
29
END LOOP;
30
31
-- Grant on all functions (handles all argument types)
32
FOR obj IN
33
SELECT
34
p.proname AS function_name,
35
pg_get_function_identity_arguments(p.oid) AS args
36
FROM
37
pg_proc p
38
JOIN pg_namespace n ON p.pronamespace = n.oid
39
WHERE
40
n.nspname = p_schema
41
AND p.prokind = 'f' -- f = function
42
LOOP
43
EXECUTE format(
44
'GRANT EXECUTE ON FUNCTION %I.%I(%s) TO %I;',
45
p_schema, obj.function_name, obj.args, p_user
46
);
47
RAISE NOTICE 'GRANT EXECUTE ON FUNCTION %.%(%) TO %;', p_schema, obj.function_name, obj.args, p_user;
48
END LOOP;
49
50
-- Grant on all procedures (Postgres 11+)
51
FOR obj IN
52
SELECT
53
p.proname AS procedure_name,
54
pg_get_function_identity_arguments(p.oid) AS args
55
FROM
56
pg_proc p
57
JOIN pg_namespace n ON p.pronamespace = n.oid
58
WHERE
59
n.nspname = p_schema
60
AND p.prokind = 'p' -- p = procedure
61
LOOP
62
EXECUTE format(
63
'GRANT EXECUTE ON PROCEDURE %I.%I(%s) TO %I;',
64
p_schema, obj.procedure_name, obj.args, p_user
65
);
66
RAISE NOTICE 'GRANT EXECUTE ON PROCEDURE %.%(%) TO %;', p_schema, obj.procedure_name, obj.args, p_user;
67
END LOOP;
68
69
END;
70
$function$
|
|||||
| Function | create_budget | Match | ||||||
| Function | dummy_hello_function | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dummy_hello_function(name text)
2
RETURNS text
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN 'Hello, ' || name || '!';
7
END;
8
$function$
|
|||||
| Function | get_account_total_amount_by_date_range | Match | ||||||
| Function | get_all_users | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_users()
2
RETURNS TABLE(id uuid, first_name character varying, last_name character varying, email character varying, phone_number character varying, user_name text)
3
LANGUAGE sql
4
AS $function$
5
SELECT id, first_name, last_name, email, phone_number, user_name
6
FROM public.users;
7
$function$
|
|||||
| Function | get_balance_sheet_3 | Match | ||||||
| Function | get_customer_dues | Match | ||||||
| Function | get_total_expense | Match | ||||||
| Function | get_vendor_opening_balance | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_vendor_opening_balance(p_vendor_id uuid, p_finyear_id integer)
2
RETURNS TABLE(transaction_date timestamp without time zone, vendor_id uuid, vendor_name text, description text, credit numeric, debit numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_year_text text;
7
BEGIN
8
-- Fetch the year text for the given finance year id
9
SELECT year INTO v_year_text FROM public.finance_year WHERE id = p_finyear_id;
10
IF v_year_text IS NULL THEN
11
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
12
END IF;
13
14
RETURN QUERY
15
SELECT
16
th.transaction_date,
17
th.vendor_id,
18
v.name::text AS vendor_name, -- Explicit cast to match function signature
19
th.description,
20
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) AS credit,
21
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) AS debit
22
FROM public.transaction_headers th
23
JOIN public.vendors v ON th.vendor_id = v.id
24
JOIN public.journal_entries je ON th.id = je.transaction_id
25
JOIN public.chart_of_accounts sc ON sc.name ILIKE '%Sundry Creditors%'
26
WHERE th.vendor_id = p_vendor_id
27
AND je.account_id = sc.id
28
AND th.transaction_source_type = 13
29
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
30
ORDER BY th.transaction_date;
31
END;
32
$function$
|
|||||
| Function | test | Match | ||||||
| Function | get_all_companies | Match | ||||||
| Function | get_customer_ledger | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_customer_ledger(p_company_id uuid, p_organization_id uuid, p_customer_id uuid, p_start_date date, p_end_date date)
1
CREATE OR REPLACE FUNCTION public.get_customer_ledger(p_company_id uuid, p_organization_id uuid, p_customer_id uuid, p_start_date date, p_end_date date)
2
RETURNS TABLE(transaction_date date, account_id uuid, account_name text, debit numeric, credit numeric, balance numeric, document_number text, document_id uuid, transaction_source_type integer)
2
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, document_number text, document_id uuid, transaction_source_type integer, sort_order integer)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
v_opening_equity_id UUID;
6
v_opening_balance NUMERIC;
7
v_sundary_detors_account_id UUID;
8
v_opening_balance_equity_account_id UUID;
9
BEGIN
7
BEGIN
8
SELECT COALESCE(
9
SUM(
10
CASE
11
WHEN ob.entry_type = 'D' THEN ob.balance -- Debit increases balance
12
ELSE -ob.balance -- Credit reduces balance
13
END
14
), 0)
15
INTO v_opening_balance
16
FROM public.opening_balances ob
17
WHERE ob.organization_id = p_organization_id
18
AND ob.customer_id = p_customer_id
19
AND ob.is_deleted = FALSE;
10
20
11
SELECT accounts_receivable_account_id, opening_balance_equity_account_id
12
INTO v_sundary_detors_account_id, v_opening_balance_equity_account_id
13
FROM public.organization_accounts
14
WHERE organization_id = p_organization_id
15
LIMIT 1;
16
17
RETURN QUERY
21
RETURN QUERY
18
WITH all_transactions AS (
22
WITH customer_transactions AS (
19
SELECT
23
SELECT
20
th.transaction_date::date,
24
th.transaction_date::date AS transaction_date,
21
coa.name AS account_name,
25
coa.name AS account_name,
22
coa.id AS account_id, -- ✅ Include account_id
26
CASE
23
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
27
WHEN je.entry_type = 'D' THEN je.amount
24
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
28
ELSE 0
29
END AS debit,
30
CASE
31
WHEN je.entry_type = 'C' THEN je.amount
32
ELSE 0
33
END AS credit,
34
th.company_id,
35
th.customer_id,
36
je.account_id,
25
th.document_number,
37
th.document_number,
26
th.document_id,
38
th.document_id,
27
th.transaction_source_type,
39
th.transaction_source_type,
28
tst.sort_order,
40
2 AS sort_order
29
th.id AS transaction_id,
30
je.id AS journal_entry_id
31
FROM
41
FROM
32
public.transaction_headers th
42
public.transaction_headers th
33
JOIN public.journal_entries je ON th.id = je.transaction_id
43
INNER JOIN
34
JOIN public.transaction_source_types tst ON th.transaction_source_type = tst.id
44
public.journal_entries je ON th.id = je.transaction_id
35
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
45
INNER JOIN
46
public.chart_of_accounts coa ON je.account_id = coa.id
36
WHERE
47
WHERE
37
th.company_id = p_company_id
48
th.company_id = p_company_id
49
AND coa.organization_id = p_organization_id
38
AND th.customer_id = p_customer_id
50
AND th.customer_id = p_customer_id
39
AND je.account_id = v_sundary_detors_account_id
40
AND th.transaction_date BETWEEN p_start_date AND p_end_date
41
AND th.is_deleted = false
51
AND th.is_deleted = false
42
AND je.is_deleted = false
52
AND je.is_deleted = false
53
AND th.transaction_date >= p_start_date
54
AND th.transaction_date <= p_end_date
43
)
55
)
44
SELECT
56
SELECT
45
at.transaction_date,
57
p_start_date AS transaction_date,
46
at.account_id, -- ✅ Added here
58
'Opening Balance' AS account_name,
47
at.account_name,
59
0 AS debit,
48
at.debit,
60
0 AS credit,
49
at.credit,
61
v_opening_balance AS balance,
50
SUM(at.debit - at.credit) OVER (
62
NULL AS document_number,
63
NULL AS document_id,
64
NULL AS transaction_source_type,
65
1 AS sort_order
66
67
UNION ALL
68
69
SELECT
70
ct.transaction_date,
71
ct.account_name,
72
ct.debit,
73
ct.credit,
74
v_opening_balance
75
+ SUM(ct.debit - ct.credit)
76
OVER (ORDER BY ct.transaction_date, ct.document_number ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
77
AS balance,
78
ct.document_number,
79
ct.document_id,
80
ct.transaction_source_type,
81
ct.sort_order
82
FROM
83
customer_transactions ct
84
WHERE
85
NOT (ct.debit = 0 AND ct.credit = 0)
51
ORDER BY
86
ORDER BY
52
at.transaction_date,
87
transaction_date,
53
at.sort_order,
88
sort_order ASC; -- Then order by transaction date
54
at.transaction_id,
55
at.journal_entry_id
56
) AS balance,
57
at.document_number,
58
at.document_id,
59
at.transaction_source_type
60
FROM all_transactions AS at
61
ORDER BY
62
at.transaction_date,
63
at.sort_order,
64
at.transaction_id,
65
at.journal_entry_id;
66
67
END;
89
END;
68
$function$
90
$function$
|
|||||
| Function | get_general_ledger | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_general_ledger(p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
1
CREATE OR REPLACE FUNCTION public.get_general_ledger(p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
2
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid, customer_id uuid, customer_name text, employee_id uuid, employee_name text, vendor_id uuid, vendor_name text)
2
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
RETURN QUERY
6
RETURN QUERY
7
WITH general_transactions AS (
7
WITH general_transactions AS (
8
SELECT
8
SELECT
9
th.transaction_date::date AS transaction_date,
9
th.transaction_date::date AS transaction_date, -- Cast to date
10
coa.name AS account_name,
10
coa.name AS account_name,
11
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
11
CASE
12
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
12
WHEN je.entry_type = 'D' THEN je.amount
13
ELSE 0
14
END AS debit,
15
CASE
16
WHEN je.entry_type = 'C' THEN je.amount
17
ELSE 0
18
END AS credit,
13
th.company_id,
19
th.company_id,
14
je.account_id,
20
je.account_id,
15
th.transaction_source_type,
21
th.transaction_source_type,
16
tst.sort_order,
17
th.document_number,
22
th.document_number,
18
th.document_id,
23
th.document_id
19
th.customer_id,
20
th.employee_id,
21
th.vendor_id,
22
th.id AS transaction_id,
23
je.id AS journal_entry_id
24
FROM
24
FROM
25
public.transaction_headers th
25
public.transaction_headers th
26
INNER JOIN
26
INNER JOIN
27
public.journal_entries je ON th.id = je.transaction_id
27
public.journal_entries je ON th.id = je.transaction_id
28
INNER JOIN
28
INNER JOIN
29
public.chart_of_accounts coa ON je.account_id = coa.id
29
public.chart_of_accounts coa ON je.account_id = coa.id
30
INNER JOIN
31
public.transaction_source_types tst ON th.transaction_source_type = tst.id
32
WHERE
30
WHERE
33
th.company_id = p_company_id
31
th.company_id = p_company_id
34
AND coa.organization_id = p_organization_id
32
AND coa.organization_id = p_organization_id
35
AND th.is_deleted = false
33
AND th.is_deleted = false
36
AND je.is_deleted = false
34
AND je.is_deleted = false
37
AND th.transaction_date BETWEEN p_start_date AND p_end_date
35
AND th.transaction_date >= p_start_date
36
AND th.transaction_date <= p_end_date
38
)
37
)
39
SELECT
38
SELECT
40
gt.transaction_date,
39
gt.transaction_date,
41
gt.account_name,
40
gt.account_name,
42
gt.debit,
41
gt.debit,
43
gt.credit,
42
gt.credit,
44
SUM(gt.debit - gt.credit) OVER (
43
-- Recalculate balance where debit and credit are settled
45
ORDER BY
44
SUM(gt.debit - gt.credit) OVER (PARTITION BY gt.account_id ORDER BY gt.transaction_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance,
46
gt.transaction_date,
47
gt.sort_order,
48
gt.transaction_id,
49
gt.journal_entry_id
50
) AS balance,
51
gt.transaction_source_type,
45
gt.transaction_source_type,
52
gt.document_number,
46
gt.document_number,
53
gt.document_id,
47
gt.document_id
54
c.id AS customer_id,
55
c.name::text AS customer_name,
56
e.id AS employee_id,
57
CASE
58
WHEN e.id IS NOT NULL THEN (e.first_name || ' ' || e.last_name)::text
59
ELSE NULL
60
END AS employee_name,
61
v.id AS vendor_id,
62
v.name::text AS vendor_name
63
FROM
48
FROM
64
general_transactions gt
49
general_transactions gt
65
LEFT JOIN public.customers c ON gt.customer_id = c.id AND c.company_id = p_company_id
66
LEFT JOIN public.employees e ON gt.employee_id = e.id AND e.company_id = p_company_id
67
LEFT JOIN public.vendors v ON gt.vendor_id = v.id AND v.company_id = p_company_id
68
WHERE
50
WHERE
51
-- Exclude rows where both debit and credit are 0
69
NOT (gt.debit = 0 AND gt.credit = 0)
52
NOT (gt.debit = 0 AND gt.credit = 0)
70
ORDER BY
53
ORDER BY
71
gt.transaction_date,
54
gt.transaction_date;
72
gt.sort_order,
73
gt.transaction_id,
74
gt.journal_entry_id;
75
END;
55
END;
76
$function$
56
$function$
|
|||||
| Function | get_vendor_ledger | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_vendor_ledger(p_vendor_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
1
CREATE OR REPLACE FUNCTION public.get_vendor_ledger(p_vendor_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
2
RETURNS TABLE(transaction_date date, account_id uuid, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
2
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
6
v_sundary_creditors_account_id UUID;
7
v_vendor_advance_account_id UUID;
8
BEGIN
5
BEGIN
9
SELECT
10
accounts_payable_account_id,
11
vendor_advance_account_id
12
INTO
13
v_sundary_creditors_account_id,
14
v_vendor_advance_account_id
15
FROM public.organization_accounts
16
WHERE organization_id = p_organization_id
17
LIMIT 1;
18
19
RETURN QUERY
6
RETURN QUERY
20
WITH all_transactions AS (
7
WITH vendor_transactions AS (
21
SELECT
8
SELECT
22
th.transaction_date::date,
9
th.transaction_date::date AS transaction_date,
23
coa.id AS account_id,
24
coa.name AS account_name,
10
coa.name AS account_name,
25
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
11
CASE
26
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
12
WHEN je.entry_type = 'D' THEN je.amount
13
ELSE 0
14
END AS debit,
15
CASE
16
WHEN je.entry_type = 'C' THEN je.amount
17
ELSE 0
18
END AS credit,
19
th.company_id,
20
th.vendor_id,
21
th.transaction_source_type,
27
th.document_number,
22
th.document_number,
28
th.document_id,
23
th.document_id
29
th.transaction_source_type,
24
FROM
30
tst.sort_order,
25
public.transaction_headers th
31
th.id AS transaction_id,
26
INNER JOIN
32
je.id AS journal_entry_id
27
public.journal_entries je ON th.id = je.transaction_id
33
FROM public.transaction_headers th
28
INNER JOIN
34
JOIN public.journal_entries je ON th.id = je.transaction_id
29
public.chart_of_accounts coa ON je.account_id = coa.id
35
JOIN public.transaction_source_types tst ON th.transaction_source_type = tst.id
36
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
37
WHERE
30
WHERE
38
th.company_id = p_company_id
31
th.vendor_id = p_vendor_id
39
AND th.vendor_id = p_vendor_id
32
AND th.company_id = p_company_id
40
AND je.account_id IN (v_sundary_creditors_account_id, v_vendor_advance_account_id)
33
AND coa.organization_id = p_organization_id
41
AND th.transaction_date BETWEEN p_start_date AND p_end_date
42
AND th.is_deleted = false
34
AND th.is_deleted = false
43
AND je.is_deleted = false
35
AND je.is_deleted = false
36
AND th.transaction_date >= p_start_date
37
AND th.transaction_date <= p_end_date
44
)
38
)
45
SELECT
39
SELECT
46
at.transaction_date,
40
vt.transaction_date,
47
at.account_id,
41
vt.account_name,
48
at.account_name,
42
vt.debit,
49
at.debit,
43
vt.credit,
50
at.credit,
44
SUM(vt.debit - vt.credit) OVER (PARTITION BY vt.vendor_id ORDER BY vt.transaction_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance,
51
SUM(at.debit - at.credit) OVER (
45
vt.transaction_source_type,
46
vt.document_number,
47
vt.document_id
48
FROM
49
vendor_transactions vt
52
ORDER BY
50
ORDER BY
53
at.transaction_date,
51
vt.transaction_date;
54
at.sort_order,
55
at.transaction_id,
56
at.journal_entry_id
57
) AS balance,
58
at.transaction_source_type,
59
at.document_number,
60
at.document_id
61
FROM all_transactions AS at
62
ORDER BY
63
at.transaction_date,
64
at.sort_order,
65
at.transaction_id,
66
at.journal_entry_id;
67
END;
52
END;
68
$function$
53
$function$
|
|||||
| Function | get_party_totals | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_party_totals(p_organization_id uuid, p_company_id uuid, p_start_date date, p_end_date date, p_party_type integer)
2
RETURNS TABLE(id uuid, party_name text, company_id uuid, company_name text, total_invoiced numeric, total_received numeric, total_billed numeric, total_paid numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
CUSTOMER CONSTANT int := 1;
7
VENDOR CONSTANT int := 2;
8
BEGIN
9
-- Customers
10
11
IF p_party_type = CUSTOMER THEN
12
RETURN QUERY
13
SELECT
14
th.customer_id AS id,
15
MAX(cust.name) AS party_name,
16
th.company_id,
17
MAX(comp.name) AS company_name,
18
COALESCE(SUM(CASE WHEN th.transaction_source_type = 1 AND je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) AS total_invoiced,
19
COALESCE(SUM(CASE WHEN th.transaction_source_type = 3 AND je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) AS total_received,
20
0::numeric AS total_billed,
21
0::numeric AS total_paid
22
FROM public.transaction_headers th
23
JOIN public.journal_entries je ON je.transaction_id = th.id
24
JOIN public.companies comp ON comp.id = th.company_id
25
LEFT JOIN public.customers cust ON cust.id = th.customer_id
26
WHERE comp.organization_id = p_organization_id
27
AND (p_company_id IS NULL OR th.company_id = p_company_id)
28
AND (p_start_date IS NULL OR th.transaction_date >= p_start_date)
29
AND (p_end_date IS NULL OR th.transaction_date <= p_end_date)
30
AND th.customer_id IS NOT NULL
31
GROUP BY th.customer_id, th.company_id;
32
33
-- Vendors
34
ELSIF p_party_type = VENDOR THEN
35
RETURN QUERY
36
SELECT
37
th.vendor_id AS id,
38
MAX(vend.name) AS party_name,
39
th.company_id,
40
MAX(comp.name) AS company_name,
41
0::numeric AS total_invoiced,
42
0::numeric AS total_received,
43
COALESCE(SUM(CASE WHEN th.transaction_source_type = 2 AND je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) AS total_billed,
44
COALESCE(SUM(CASE WHEN th.transaction_source_type = 4 AND je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) AS total_paid
45
FROM public.transaction_headers th
46
JOIN public.journal_entries je ON je.transaction_id = th.id
47
JOIN public.companies comp ON comp.id = th.company_id
48
LEFT JOIN public.vendors vend ON vend.id = th.vendor_id
49
WHERE comp.organization_id = p_organization_id
50
AND (p_company_id IS NULL OR th.company_id = p_company_id)
51
AND (p_start_date IS NULL OR th.transaction_date >= p_start_date)
52
AND (p_end_date IS NULL OR th.transaction_date <= p_end_date)
53
AND th.vendor_id IS NOT NULL
54
GROUP BY th.vendor_id, th.company_id;
55
56
END IF;
57
END;
58
$function$
|
|||||
| Function | get_accounts_by_classification | Match | ||||||
| Function | get_comparative_accounts_overview | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_comparative_accounts_overview(p_company_id uuid, p_fin_year_id integer)
1
CREATE OR REPLACE FUNCTION public.get_comparative_accounts_overview(p_company_id uuid, p_fin_year_id integer, p_chart_of_account_id uuid)
2
RETURNS TABLE(account_id uuid, account_name text, parent_account_id uuid, hierarchy_path text[], order_sequence text, financial_year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_sum numeric, difference numeric)
2
RETURNS TABLE(account_id uuid, parent_account_name text, parent_account_id uuid, financial_year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_sum numeric, difference numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
v_current_year_id INTEGER := p_fin_year_id;
6
v_current_year INTEGER := p_fin_year_id;
7
v_previous_year_id INTEGER;
7
v_previous_year INTEGER := p_fin_year_id - 1;
8
v_organization_id UUID;
8
v_organization_id UUID;
9
v_is_second_last_leaf BOOLEAN;
9
BEGIN
10
BEGIN
10
-- Get organization
11
-- Get the organization_id for the given company_id
11
SELECT c.organization_id INTO v_organization_id
12
SELECT c.organization_id
13
INTO v_organization_id
12
FROM public.companies c
14
FROM public.companies c
13
WHERE c.id = p_company_id;
15
WHERE c.id = p_company_id;
14
16
15
-- Find previous financial year (based on start_date)
17
-- Search for 2nd last leaf account
16
SELECT fy.id
18
SELECT NOT EXISTS (
17
INTO v_previous_year_id
19
SELECT 1 FROM chart_of_accounts ca1
18
FROM finance_year fy
20
WHERE ca1.parent_account_id = p_chart_of_account_id
19
WHERE fy.start_date < (
20
SELECT start_date FROM finance_year WHERE id = v_current_year_id
21
)
21
)
22
ORDER BY fy.start_date DESC
22
INTO v_is_second_last_leaf;
23
LIMIT 1;
23
24
25
26
RAISE notice 'it is leaf account %', v_is_second_last_leaf;
24
27
28
-- If it is a LEAF account, fetch its transactions directly
29
IF v_is_second_last_leaf THEN
25
RETURN QUERY
30
RETURN QUERY
26
WITH RECURSIVE account_with_path AS (
31
WITH RECURSIVE child_accounts AS (
27
-- Root accounts
32
-- Get the given parent account and all its child accounts
28
SELECT
33
SELECT id, parent_account_id, name
29
coa.id,
34
FROM chart_of_accounts
30
coa.name,
35
WHERE parent_account_id = p_chart_of_account_id
31
coa.parent_account_id,
32
ARRAY[coa.name]::text[] AS path,
33
coa.account_number::text AS order_sequence
34
FROM chart_of_accounts coa
35
WHERE coa.organization_id = v_organization_id
36
AND (coa.parent_account_id IS NULL OR coa.parent_account_id = '00000000-0000-0000-0000-000000000000')
37
AND coa.is_deleted = FALSE
38
36
39
UNION ALL
37
UNION ALL
40
38
41
-- Children
39
-- Recursively get all child accounts
40
SELECT ca.id, ca.parent_account_id, ca.name
41
FROM chart_of_accounts ca
42
INNER JOIN child_accounts c ON ca.parent_account_id = c.id
43
),
44
financial_years AS (
45
-- Get previous and current financial years
46
SELECT fy.id AS fin_year_id, fy.start_date, fy.end_date
47
FROM finance_year fy
48
WHERE fy.id IN (v_previous_year, v_current_year)
49
)
42
SELECT
50
SELECT
43
c.id,
51
je.id AS transaction_id,
44
c.name,
52
je.account_id,
45
c.parent_account_id,
53
ca.name AS account_name,
46
awp.path || c.name,
54
ca.parent_account_id,
47
awp.order_sequence || '.' || c.account_number::text
55
fy.fin_year_id AS financial_year,
48
FROM chart_of_accounts c
56
je.transaction_date,
49
JOIN account_with_path awp ON c.parent_account_id = awp.id
57
EXTRACT(MONTH FROM je.transaction_date) AS calendar_month,
50
WHERE c.organization_id = v_organization_id
58
51
AND c.is_deleted = FALSE
59
-- Convert Calendar Month to Financial Month (April - March Cycle)
60
CASE
61
WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN 'Apr'
62
WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN 'May'
63
WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN 'Jun'
64
WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN 'Jul'
65
WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN 'Aug'
66
WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN 'Sep'
67
WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN 'Oct'
68
WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN 'Nov'
69
WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN 'Dec'
70
WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN 'Jan'
71
WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN 'Feb'
72
WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN 'Mar'
73
END AS financial_month,
74
75
je.amount,
76
je.description
77
FROM journal_entries je
78
INNER JOIN child_accounts ca ON je.account_id = ca.id
79
INNER JOIN financial_years fy ON je.transaction_date BETWEEN fy.start_date AND fy.end_date
80
ORDER BY fy.fin_year_id, financial_month, ca.name, je.transaction_date;
81
82
ELSE
83
-- If it is NOT a leaf account, use the ORIGINAL FUNCTIONALITY (recursive aggregation)
84
RETURN QUERY
85
WITH direct_children AS (
86
-- Step 1: Get Direct Children of Given Parent
87
SELECT ca.id AS account_id, ca.parent_account_id, ca.name
88
FROM chart_of_accounts ca
89
WHERE ca.parent_account_id = p_chart_of_account_id
90
AND ca.organization_id = v_organization_id
52
),
91
),
53
leaf_accounts AS (
92
54
SELECT a.id
93
descendant_accounts AS (
55
FROM account_with_path a
94
-- Step 2: Recursively Get All Descendants of Each Direct Child
56
WHERE NOT EXISTS (
95
WITH RECURSIVE hierarchy AS (
57
SELECT 1 FROM chart_of_accounts c
96
-- Start with direct children
58
WHERE c.parent_account_id = a.id
97
SELECT dc.account_id, dc.parent_account_id, dc.name
59
AND c.organization_id = v_organization_id
98
FROM direct_children dc
60
AND c.is_deleted = FALSE
99
100
UNION ALL
101
102
-- Then, recursively get all their descendants
103
SELECT coa.id AS account_id, coa.parent_account_id, coa.name
104
FROM chart_of_accounts coa
105
INNER JOIN hierarchy h ON coa.parent_account_id = h.account_id
61
)
106
)
107
SELECT * FROM hierarchy
62
),
108
),
109
63
financial_years AS (
110
financial_years AS (
111
-- Step 3: Get Start and End Dates for Given Financial Years
64
SELECT id AS fin_year_id, start_date, end_date
112
SELECT id AS fin_year_id, start_date, end_date
65
FROM finance_year
113
FROM finance_year
66
WHERE id IN (v_current_year_id, v_previous_year_id)
114
WHERE id IN (v_previous_year, v_current_year)
67
),
115
),
116
68
aggregated_data AS (
117
aggregated_data AS (
118
-- Step 4: Ensure Each Account Appears for Both Financial Years
69
SELECT
119
SELECT
70
awp.id,
120
dc.account_id, -- Include Account ID
71
awp.name,
121
dc.name AS parent_account_name,
72
awp.parent_account_id,
122
dc.parent_account_id, -- Ensure Parent Account ID is properly referenced
73
awp.path AS hierarchy_path,
123
fy.fin_year_id AS financial_year,
74
awp.order_sequence,
124
75
fy.fin_year_id,
125
-- Monthly Values (April to March)
76
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN je.amount END), 0) AS apr,
126
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN je.amount ELSE 0 END), 0) AS apr,
77
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN je.amount END), 0) AS may,
127
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN je.amount ELSE 0 END), 0) AS may,
78
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN je.amount END), 0) AS jun,
128
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN je.amount ELSE 0 END), 0) AS jun,
79
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN je.amount END), 0) AS jul,
129
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN je.amount ELSE 0 END), 0) AS jul,
80
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN je.amount END), 0) AS aug,
130
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN je.amount ELSE 0 END), 0) AS aug,
81
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN je.amount END), 0) AS sep,
131
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN je.amount ELSE 0 END), 0) AS sep,
82
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN je.amount END), 0) AS oct,
132
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN je.amount ELSE 0 END), 0) AS oct,
83
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN je.amount END), 0) AS nov,
133
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN je.amount ELSE 0 END), 0) AS nov,
84
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN je.amount END), 0) AS "dec",
134
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN je.amount ELSE 0 END), 0) AS "dec",
85
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN je.amount END), 0) AS jan,
135
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN je.amount ELSE 0 END), 0) AS jan,
86
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN je.amount END), 0) AS feb,
136
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN je.amount ELSE 0 END), 0) AS feb,
87
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN je.amount END), 0) AS mar
137
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN je.amount ELSE 0 END), 0) AS mar,
88
FROM account_with_path awp
138
89
JOIN leaf_accounts la ON la.id = awp.id -- ✅ only leaf accounts get financial years
139
-- Total Sum of All Months
90
CROSS JOIN financial_years fy
140
COALESCE(SUM(je.amount), 0) AS total_sum
141
142
FROM direct_children dc
143
CROSS JOIN financial_years fy -- Ensures each account appears for both years
144
LEFT JOIN descendant_accounts da ON da.parent_account_id = dc.account_id
91
LEFT JOIN journal_entries je
145
LEFT JOIN journal_entries je
92
ON je.account_id = awp.id
146
ON je.account_id = da.account_id
93
AND je.transaction_date BETWEEN fy.start_date AND fy.end_date
147
AND je.transaction_date BETWEEN fy.start_date AND fy.end_date -- Ensure correct year filtering
94
LEFT JOIN transaction_headers th
148
-- AND je.company_id = p_company_id -- Filter by Company ID
95
ON th.id = je.transaction_id
149
96
AND th.company_id = p_company_id
150
GROUP BY dc.account_id, dc.parent_account_id, dc.name, fy.fin_year_id
97
WHERE (th.transaction_source_type IS NULL OR th.transaction_source_type != 18)
98
GROUP BY awp.id, awp.name, awp.parent_account_id, awp.path, awp.order_sequence, fy.fin_year_id
99
)
151
)
152
153
-- Step 5: Compute Difference Between Two Financial Years
100
SELECT
154
SELECT
101
a1.id AS account_id,
155
a1.account_id,
102
a1.name AS account_name,
156
a1.parent_account_name,
103
a1.parent_account_id,
157
a1.parent_account_id,
104
a1.hierarchy_path,
158
a1.financial_year,
105
a1.order_sequence,
159
106
a1.fin_year_id AS financial_year,
160
-- Monthly Values
107
a1.apr, a1.may, a1.jun, a1.jul, a1.aug, a1.sep, a1.oct, a1.nov, a1."dec",
161
a1.apr, a1.may, a1.jun, a1.jul, a1.aug, a1.sep, a1.oct, a1.nov, a1."dec",
108
a1.jan, a1.feb, a1.mar,
162
a1.jan, a1.feb, a1.mar,
109
(COALESCE(a1.apr,0) + COALESCE(a1.may,0) + COALESCE(a1.jun,0) +
163
110
COALESCE(a1.jul,0) + COALESCE(a1.aug,0) + COALESCE(a1.sep,0) +
164
-- Total Sum for Financial Year
111
COALESCE(a1.oct,0) + COALESCE(a1.nov,0) + COALESCE(a1."dec",0) +
165
a1.total_sum,
112
COALESCE(a1.jan,0) + COALESCE(a1.feb,0) + COALESCE(a1.mar,0)) AS total_sum,
166
113
((COALESCE(a1.apr,0) + COALESCE(a1.may,0) + COALESCE(a1.jun,0) +
167
-- Difference (Current Year - Previous Year)
114
COALESCE(a1.jul,0) + COALESCE(a1.aug,0) + COALESCE(a1.sep,0) +
168
(COALESCE(a1.total_sum, 0) - COALESCE(a2.total_sum, 0)) AS difference
115
COALESCE(a1.oct,0) + COALESCE(a1.nov,0) + COALESCE(a1."dec",0) +
169
116
COALESCE(a1.jan,0) + COALESCE(a1.feb,0) + COALESCE(a1.mar,0))
117
-
118
(COALESCE(a2.apr,0) + COALESCE(a2.may,0) + COALESCE(a2.jun,0) +
119
COALESCE(a2.jul,0) + COALESCE(a2.aug,0) + COALESCE(a2.sep,0) +
120
COALESCE(a2.oct,0) + COALESCE(a2.nov,0) + COALESCE(a2."dec",0) +
121
COALESCE(a2.jan,0) + COALESCE(a2.feb,0) + COALESCE(a2.mar,0))
122
) AS difference
123
FROM aggregated_data a1
170
FROM aggregated_data a1
124
LEFT JOIN aggregated_data a2
171
LEFT JOIN aggregated_data a2
125
ON a1.id = a2.id
172
ON a1.account_id = a2.account_id
126
AND a1.fin_year_id = v_current_year_id
173
AND a1.financial_year = v_current_year -- Current Year
127
AND a2.fin_year_id = v_previous_year_id
174
AND a2.financial_year = v_previous_year -- Previous Year
128
ORDER BY a1.order_sequence, a1.fin_year_id;
175
176
ORDER BY a1.parent_account_name, a1.financial_year;
177
END IF;
129
END;
178
END;
130
$function$
179
$function$
|
|||||
| Function | get_collection | Match | ||||||
| Function | update_basic_company | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.update_basic_company(p_company_id uuid, p_organization_id uuid, p_name text, p_gst_in text, p_short_name text, p_currency text, p_pan text, p_tan text, p_outstanding_limit numeric, p_is_apartment boolean, p_is_non_work boolean, p_interest_percentage numeric, p_has_gst_in boolean, p_email text, p_mobile_number text)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_contact_id UUID;
7
BEGIN
8
-- 1️⃣ Get contact_id from company_contacts
9
SELECT contact_id INTO v_contact_id
10
FROM company_contacts
11
WHERE company_id = p_company_id
12
LIMIT 1;
13
14
-- 2️⃣ Update company table
15
UPDATE companies
16
SET
17
organization_id = p_organization_id,
18
name = p_name,
19
gst_in = p_gst_in,
20
short_name = p_short_name,
21
currency = p_currency,
22
pan = p_pan,
23
tan = p_tan,
24
outstanding_limit = p_outstanding_limit,
25
is_apartment = p_is_apartment,
26
is_non_work = p_is_non_work,
27
interest_percentage = p_interest_percentage,
28
has_gst_in = p_has_gst_in,
29
modified_on_utc = NOW()
30
WHERE id = p_company_id;
31
32
-- 3️⃣ Update contact table if mapping exists
33
IF v_contact_id IS NOT NULL THEN
34
UPDATE contacts
35
SET
36
email = p_email,
37
mobile_number = p_mobile_number,
38
modified_on_utc = NOW()
39
WHERE id = v_contact_id;
40
END IF;
41
42
END;
43
$function$
|
|||||
| Function | create_notification_with_userid | Match | ||||||
| Function | get_expense_account_types | Match | ||||||
| Function | pgp_sym_decrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_sym_decrypt(bytea, text)
2
RETURNS text
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_sym_decrypt_text$function$
|
|||||
| Function | pgp_sym_decrypt_bytea | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_sym_decrypt_bytea(bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_sym_decrypt_bytea$function$
|
|||||
| Function | get_module_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_module_permissions()
2
RETURNS TABLE(id integer, feature_name text, read_permission_id uuid, create_permission_id uuid, update_permission_id uuid, delete_permission_id uuid, full_access_permission_id uuid, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, deleted_on_utc timestamp without time zone, is_deleted boolean, created_by uuid, modified_by uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
mp.id,
9
mp.feature_name,
10
mp.read_permission_id,
11
mp.create_permission_id,
12
mp.update_permission_id,
13
mp.delete_permission_id,
14
mp.full_access_permission_id,
15
mp.created_on_utc,
16
mp.modified_on_utc,
17
mp.deleted_on_utc,
18
mp.is_deleted,
19
mp.created_by,
20
mp.modified_by
21
FROM module_permissions mp
22
WHERE mp.is_deleted = false
23
ORDER BY mp.feature_name;
24
END;
25
$function$
|
|||||
| Function | get_profit_loss_statement | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_profit_loss_statement(p_company_id uuid, p_fin_year_id integer, p_start_date date, p_end_date date, p_is_get_for_organization boolean)
2
RETURNS TABLE(account_id uuid, account_type text, account_name text, amount numeric, category text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date;
7
v_end_date date;
8
v_fy_start_date date;
9
v_fy_end_date date;
10
v_company_ids UUID[];
11
v_organization_id UUID;
12
income_account_ids integer[];
13
expense_account_ids integer[];
14
BEGIN
15
-- Get the financial year dates
16
SELECT start_date::date, end_date::date
17
INTO v_fy_start_date, v_fy_end_date
18
FROM public.finance_year
19
WHERE id = p_fin_year_id
20
LIMIT 1;
21
22
-- Apply filter logic for new date params
23
v_start_date := COALESCE(p_start_date, v_fy_start_date);
24
v_end_date := COALESCE(p_end_date, v_fy_end_date);
25
26
-- Get organization id
27
SELECT organization_id INTO v_organization_id
28
FROM public.companies
29
WHERE id = p_company_id;
30
31
-- Get account type hierarchies
32
income_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(4));
33
expense_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(5));
34
35
-- Populate company IDs based on org flag
36
IF p_is_get_for_organization THEN
37
SELECT ARRAY(
38
SELECT id FROM companies
39
WHERE organization_id = v_organization_id
40
)
41
INTO v_company_ids;
42
ELSE
43
v_company_ids := ARRAY[p_company_id];
44
END IF;
45
46
-- Return Income
47
RETURN QUERY
48
SELECT
49
coa.id as account_id, -- GUID column
50
at.name AS account_type,
51
coa.name AS account_name,
52
COALESCE(
53
SUM(
54
CASE
55
WHEN ob.entry_type = 'C' THEN ob.balance
56
ELSE -ob.balance
57
END
58
),0
59
) +
60
COALESCE(
61
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE -je.amount END), 0
62
) AS amount,
63
'Income' AS category
64
FROM public.chart_of_accounts coa
65
INNER JOIN public.journal_entries je ON je.account_id = coa.id
66
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
67
INNER JOIN public.account_types at ON coa.account_type_id = at.id
68
LEFT JOIN public.opening_balances ob ON ob.account_id = coa.id
69
AND ob.organization_id = v_organization_id
70
AND ob.finyear_id = p_fin_year_id
71
AND ob.is_deleted = FALSE
72
WHERE th.company_id = ANY(v_company_ids)
73
AND th.transaction_date BETWEEN v_start_date AND v_end_date
74
AND NOT th.is_deleted
75
AND NOT je.is_deleted
76
AND coa.account_type_id = ANY(income_account_ids)
77
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
78
ORDER BY coa.account_number, at.id;
79
80
-- Return Expenses
81
RETURN QUERY
82
SELECT
83
coa.id as account_id, -- GUID column
84
at.name AS account_type,
85
coa.name AS account_name,
86
COALESCE(
87
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE -je.amount END), 0
88
) AS amount,
89
'Expenses' AS category
90
FROM public.chart_of_accounts coa
91
INNER JOIN public.journal_entries je ON je.account_id = coa.id
92
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
93
INNER JOIN public.account_types at ON coa.account_type_id = at.id
94
WHERE th.company_id = ANY(v_company_ids)
95
AND th.transaction_date BETWEEN v_start_date AND v_end_date
96
AND NOT th.is_deleted
97
AND NOT je.is_deleted
98
AND coa.account_type_id = ANY(expense_account_ids)
99
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
100
ORDER BY coa.account_number, at.id;
101
END;
102
$function$
|
|||||
| Function | get_user_notifications_with_timespan | Match | ||||||
| Function | get_trial_balance_by_date_range | Match | ||||||
| Function | get_trial_balance_net | Match | ||||||
| Function | pgp_sym_encrypt_bytea | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_sym_encrypt_bytea(bytea, text, text)
2
RETURNS bytea
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_sym_encrypt_bytea$function$
|
|||||
| Function | get_accounts_by_company_and_type | Match | ||||||
| Function | get_trial_balance_of_company_fin_year | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_trial_balance_of_company_fin_year(p_company_id uuid, p_finance_year_id integer)
2
RETURNS TABLE(account_type text, account_category text, account_number text, account_name text, total_debits numeric, total_credits numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
WITH RECURSIVE coa_hierarchy AS (
8
-- Base case: Fetch the top-level (root) accounts
9
SELECT
10
coa.id,
11
act.name AS account_type,
12
act.classification AS account_classification,
13
coa.account_number::integer,
14
coa.name,
15
coa.parent_account_id,
16
0 AS level,
17
coa.account_number::text AS order_sequence
18
FROM
19
public.chart_of_accounts coa
20
INNER JOIN
21
public.account_types act ON coa.account_type_id = act.id
22
WHERE
23
coa.is_deleted = FALSE
24
AND coa.parent_account_id = '00000000-0000-0000-0000-000000000000' -- Root-level accounts
25
26
UNION ALL
27
28
-- Recursive case: Fetch child accounts
29
SELECT
30
coa.id,
31
act.name AS account_type,
32
act.classification AS account_classification,
33
coa.account_number::integer,
34
coa.name,
35
coa.parent_account_id,
36
ch.level + 1 AS level,
37
ch.order_sequence || '.' || coa.account_number::text AS order_sequence
38
FROM
39
public.chart_of_accounts coa
40
INNER JOIN
41
public.account_types act ON coa.account_type_id = act.id
42
INNER JOIN
43
coa_hierarchy ch ON ch.id = coa.parent_account_id
44
WHERE
45
coa.is_deleted = FALSE
46
),
47
coa_with_balances AS (
48
-- Fetch financial year start and end date
49
SELECT
50
fy.start_date::DATE,
51
fy.end_date::DATE
52
FROM
53
public.finance_year fy
54
WHERE
55
fy.id = p_finance_year_id
56
LIMIT 1
57
),
58
journal_entries_filtered AS (
59
-- Get total debits and credits per account
60
SELECT
61
je.account_id,
62
SUM(CASE
63
WHEN je.entry_type = 'D' THEN je.amount
64
ELSE 0
65
END) AS total_debits,
66
SUM(CASE
67
WHEN je.entry_type = 'C' THEN je.amount
68
ELSE 0
69
END) AS total_credits
70
FROM
71
public.journal_entries je
72
JOIN
73
public.transaction_headers th ON je.transaction_id = th.id
74
JOIN
75
coa_with_balances fy ON th.transaction_date BETWEEN fy.start_date AND fy.end_date
76
WHERE
77
th.company_id = p_company_id
78
AND je.is_deleted = FALSE
79
GROUP BY
80
je.account_id
81
HAVING
82
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) > 0 OR
83
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) > 0
84
),
85
relevant_accounts AS (
86
-- Select accounts that have balances
87
SELECT
88
ch.id,
89
ch.account_type,
90
ch.account_classification,
91
ch.account_number,
92
ch.name,
93
ch.level,
94
ch.order_sequence,
95
ch.parent_account_id,
96
wb.total_debits,
97
wb.total_credits
98
FROM
99
coa_hierarchy ch
100
LEFT JOIN
101
journal_entries_filtered wb ON ch.id = wb.account_id
102
WHERE
103
wb.account_id IS NOT NULL
104
105
UNION ALL
106
107
-- Recursively select parent accounts
108
SELECT
109
ch.id,
110
ch.account_type,
111
ch.account_classification,
112
ch.account_number,
113
ch.name,
114
ch.level,
115
ch.order_sequence,
116
ch.parent_account_id,
117
NULL AS total_debits,
118
NULL AS total_credits
119
FROM
120
coa_hierarchy ch
121
INNER JOIN
122
relevant_accounts ra ON ch.id = ra.parent_account_id
123
)
124
-- Final output
125
SELECT DISTINCT ON (ra.order_sequence)
126
ra.account_type::TEXT AS account_type,
127
ac.name::TEXT AS account_category,
128
ra.account_number::TEXT AS account_number,
129
LPAD('', ra.level * 4, ' ') || ra.name::TEXT AS account_name,
130
CASE
131
WHEN ra.account_classification = 'EXPENSES' THEN COALESCE(ra.total_credits, 0) -- Expenses should be on the debit side
132
ELSE COALESCE(ra.total_debits, 0)
133
END AS total_debits,
134
CASE
135
WHEN ra.account_classification = 'EXPENSES' THEN COALESCE(ra.total_debits, 0) -- Expenses should not appear on the credit side
136
ELSE COALESCE(ra.total_credits, 0)
137
END AS total_credits
138
FROM
139
relevant_accounts ra
140
LEFT JOIN
141
public.chart_of_accounts coa ON ra.id = coa.id
142
LEFT JOIN
143
public.account_types at ON coa.account_type_id = at.id
144
LEFT JOIN
145
public.account_categories ac ON at.account_category_id = ac.id
146
CROSS JOIN
147
coa_with_balances fy
148
ORDER BY
149
ra.order_sequence;
150
END;
151
$function$
|
|||||
| Function | pgp_sym_decrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_sym_decrypt(bytea, text, text)
2
RETURNS text
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_sym_decrypt_text$function$
|
|||||
| Function | get_user_by_email_or_phone | Match | ||||||
| Function | digest | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.digest(text, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_digest$function$
|
|||||
| Function | digest | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.digest(bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_digest$function$
|
|||||
| Function | hmac | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.hmac(text, text, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_hmac$function$
|
|||||
| Function | hmac | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.hmac(bytea, bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_hmac$function$
|
|||||
| Function | crypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.crypt(text, text)
2
RETURNS text
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_crypt$function$
|
|||||
| Function | gen_salt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.gen_salt(text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_gen_salt$function$
|
|||||
| Function | gen_salt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.gen_salt(text, integer)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_gen_salt_rounds$function$
|
|||||
| Function | encrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.encrypt(bytea, bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_encrypt$function$
|
|||||
| Function | decrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.decrypt(bytea, bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_decrypt$function$
|
|||||
| Function | encrypt_iv | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.encrypt_iv(bytea, bytea, bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_encrypt_iv$function$
|
|||||
| Function | decrypt_iv | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.decrypt_iv(bytea, bytea, bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
IMMUTABLE PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_decrypt_iv$function$
|
|||||
| Function | gen_random_bytes | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.gen_random_bytes(integer)
2
RETURNS bytea
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pg_random_bytes$function$
|
|||||
| Function | gen_random_uuid | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.gen_random_uuid()
2
RETURNS uuid
3
LANGUAGE c
4
PARALLEL SAFE
5
AS '$libdir/pgcrypto', $function$pg_random_uuid$function$
|
|||||
| Function | pgp_sym_encrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_sym_encrypt(text, text)
2
RETURNS bytea
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_sym_encrypt_text$function$
|
|||||
| Function | pgp_sym_encrypt_bytea | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_sym_encrypt_bytea(bytea, text)
2
RETURNS bytea
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_sym_encrypt_bytea$function$
|
|||||
| Function | pgp_sym_encrypt | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pgp_sym_encrypt(text, text, text)
2
RETURNS bytea
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/pgcrypto', $function$pgp_sym_encrypt_text$function$
|
|||||
| Function | get_bank_statements_for_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bank_statements_for_status(p_company_id uuid, p_finyear_id integer, p_date_from timestamp without time zone DEFAULT NULL::timestamp without time zone, p_date_to timestamp without time zone DEFAULT NULL::timestamp without time zone)
2
RETURNS TABLE(id integer, company_id uuid, bank_id integer, bank_name text, txn_date timestamp without time zone, cheque_number character varying, description text, value_date timestamp without time zone, branch_code character varying, debit_amount numeric, credit_amount numeric, balance numeric, has_reconciled boolean, reconciliation_status_id integer, reconciliation_status text, reconciliation_description text, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, deleted_on_utc timestamp without time zone, is_deleted boolean)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
bs.id,
9
bs.company_id,
10
bs.bank_id,
11
b.name AS bank_name,
12
bs.txn_date::timestamp,
13
bs.cheque_number,
14
bs.description,
15
bs.value_date::timestamp,
16
bs.branch_code,
17
bs.debit_amount,
18
bs.credit_amount,
19
bs.balance,
20
21
-- 🧠 Smart has_reconciled logic
22
CASE
23
WHEN br.id IS NOT NULL
24
AND rs.status IN ('partial', 'matched', 'audited') THEN TRUE
25
ELSE FALSE
26
END AS has_reconciled,
27
28
-- 🧩 New reconciliation info
29
rs.id AS reconciliation_status_id,
30
rs.status::text AS reconciliation_status,
31
rs.description::text AS reconciliation_description,
32
33
bs.created_by,
34
bs.created_on_utc,
35
bs.modified_by,
36
bs.modified_on_utc,
37
bs.deleted_on_utc,
38
bs.is_deleted
39
FROM public.bank_statements bs
40
INNER JOIN public.banks b
41
ON b.id = bs.bank_id
42
AND b.is_deleted = false
43
INNER JOIN public.finance_year fy
44
ON fy.id = p_finyear_id
45
LEFT JOIN LATERAL (
46
SELECT br.*
47
FROM public.bank_reconciliations br
48
WHERE br.bank_statement_id = bs.id
49
ORDER BY br.matched_on_utc DESC
50
LIMIT 1
51
) br ON TRUE
52
LEFT JOIN public.reconciliation_statuses rs
53
ON rs.id = br.reconciliation_status_id
54
WHERE bs.company_id = p_company_id
55
AND bs.txn_date BETWEEN fy.start_date AND fy.end_date
56
AND (p_date_from IS NULL OR bs.txn_date >= p_date_from)
57
AND (p_date_to IS NULL OR bs.txn_date <= p_date_to)
58
AND bs.is_deleted = false
59
ORDER BY bs.txn_date, bs.id;
60
END;
61
$function$
|
|||||
| Function | get_sundry_creditors_ledger | Match | ||||||
| Function | get_account_expense | Match | ||||||
| Function | verify_user_password | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.verify_user_password(p_user_name text, p_password text)
2
RETURNS uuid
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_user_id UUID;
7
BEGIN
8
SELECT id
9
INTO v_user_id
10
FROM users
11
WHERE user_name = p_user_name
12
AND is_deleted = false
13
AND password_hash = crypt(p_password, password_hash);
14
15
RETURN v_user_id;
16
END;
17
$function$
|
|||||
| Function | get_account_ledger | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_account_ledger(p_account_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
1
CREATE OR REPLACE FUNCTION public.get_account_ledger(p_account_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
2
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid, customer_id uuid, customer_name text, employee_id uuid, employee_name text, vendor_id uuid, vendor_name text)
2
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
RETURN QUERY
6
RETURN QUERY
7
WITH RECURSIVE account_tree AS (
7
WITH account_transactions AS (
8
SELECT id
9
FROM public.chart_of_accounts
10
WHERE id = p_account_id
11
AND organization_id = p_organization_id
12
AND is_deleted = false
13
UNION ALL
14
SELECT coa.id
15
FROM public.chart_of_accounts coa
16
INNER JOIN account_tree at ON coa.parent_account_id = at.id
17
WHERE coa.organization_id = p_organization_id
18
AND coa.is_deleted = false
19
),
20
account_transactions AS (
21
SELECT
8
SELECT
22
th.transaction_date::date AS transaction_date,
9
th.transaction_date::date AS transaction_date, -- Cast to date
23
coa.name AS account_name,
10
coa.name AS account_name,
24
coa.id AS account_id,
11
CASE
25
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
12
WHEN je.entry_type = 'D' THEN je.amount
26
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
13
ELSE 0
14
END AS debit,
15
CASE
16
WHEN je.entry_type = 'C' THEN je.amount
17
ELSE 0
18
END AS credit,
19
th.company_id,
20
je.account_id,
21
th.transaction_source_type,
27
th.document_number,
22
th.document_number,
28
th.document_id,
23
th.document_id
29
th.transaction_source_type,
30
tst.sort_order,
31
th.id AS transaction_id,
32
je.id AS journal_entry_id,
33
th.customer_id,
34
th.employee_id,
35
th.vendor_id
36
FROM
24
FROM
37
public.transaction_headers th
25
public.transaction_headers th
38
INNER JOIN
26
INNER JOIN
39
public.journal_entries je ON th.id = je.transaction_id
27
public.journal_entries je ON th.id = je.transaction_id
40
INNER JOIN
28
INNER JOIN
41
public.chart_of_accounts coa ON je.account_id = coa.id
29
public.chart_of_accounts coa ON je.account_id = coa.id
42
INNER JOIN
43
account_tree at ON je.account_id = at.id
44
INNER JOIN
45
public.transaction_source_types tst ON th.transaction_source_type = tst.id
46
WHERE
30
WHERE
47
th.company_id = p_company_id
31
je.account_id = p_account_id
32
AND th.company_id = p_company_id
48
AND coa.organization_id = p_organization_id
33
AND coa.organization_id = p_organization_id
49
AND th.is_deleted = false
34
AND th.is_deleted = false
50
AND je.is_deleted = false
35
AND je.is_deleted = false
51
AND th.transaction_date BETWEEN p_start_date AND p_end_date
36
AND th.transaction_date >= p_start_date
37
AND th.transaction_date <= p_end_date
52
)
38
)
53
SELECT
39
SELECT
54
at.transaction_date,
40
at.transaction_date,
55
at.account_name,
41
at.account_name,
56
at.debit,
42
at.debit,
57
at.credit,
43
at.credit,
58
SUM(at.debit - at.credit) OVER (
44
SUM(at.debit - at.credit) OVER (PARTITION BY at.account_id ORDER BY at.transaction_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance,
59
ORDER BY
60
at.transaction_date,
61
at.sort_order,
62
at.transaction_id,
63
at.journal_entry_id
64
) AS balance,
65
at.transaction_source_type,
45
at.transaction_source_type,
66
at.document_number,
46
at.document_number,
67
at.document_id,
47
at.document_id
68
c.id AS customer_id,
69
c.name::text AS customer_name,
70
e.id AS employee_id,
71
CASE
72
WHEN e.id IS NOT NULL THEN (e.first_name || ' ' || e.last_name)::text
73
ELSE NULL
74
END AS employee_name,
75
v.id AS vendor_id,
76
v.name::text AS vendor_name
77
FROM
48
FROM
78
account_transactions at
49
account_transactions at
79
LEFT JOIN public.customers c ON at.customer_id = c.id AND c.company_id = p_company_id
80
LEFT JOIN public.employees e ON at.employee_id = e.id AND e.company_id = p_company_id
81
LEFT JOIN public.vendors v ON at.vendor_id = v.id AND v.company_id = p_company_id
82
WHERE
50
WHERE
83
NOT (at.debit = 0 AND at.credit = 0)
51
NOT (at.debit = 0 AND at.credit = 0)
84
ORDER BY
52
ORDER BY
85
at.transaction_date,
53
at.transaction_date;
86
at.sort_order,
87
at.transaction_id,
88
at.journal_entry_id;
89
END;
54
END;
90
$function$
55
$function$
|
|||||
| Function | get_account_total_amount | Match | ||||||
| Function | get_account_transactions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_account_transactions(p_company_id uuid, p_fin_year_id integer, p_chart_of_account_id uuid)
2
RETURNS TABLE(account_id uuid, account_name text, parent_account_id uuid, account_type text, year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_current_year numeric, total_last_year numeric, difference numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date TIMESTAMP DEFAULT NULL;
7
v_end_date TIMESTAMP DEFAULT NULL;
8
v_prev_start_date TIMESTAMP DEFAULT NULL;
9
v_prev_end_date TIMESTAMP DEFAULT NULL;
10
BEGIN
11
-- Get start and end date for the given financial year(fn)
12
SELECT fn.start_date, fn.end_date
13
INTO v_start_date, v_end_date
14
FROM finance_year fn
15
WHERE fn.id = p_fin_year_id
16
LIMIT 1;
17
18
-- If no result, raise an error
19
IF v_start_date IS NULL OR v_end_date IS NULL THEN
20
RAISE EXCEPTION 'Financial year % not found in finance_year table', p_fin_year_id;
21
END IF;
22
23
-- Get the previous financial year's(pfn) start and end date
24
SELECT pfn.start_date, pfn.end_date
25
INTO v_prev_start_date, v_prev_end_date
26
FROM finance_year pfn
27
WHERE pfn.id = (p_fin_year_id - 1)
28
LIMIT 1;
29
30
-- Return the final query
31
RETURN QUERY
32
WITH direct_children AS (
33
SELECT coa.id AS child_id, coa.name AS child_name, coa.parent_account_id AS direct_parent_id, coa.account_type_id
34
FROM chart_of_accounts coa
35
WHERE coa.parent_account_id = p_chart_of_account_id
36
),
37
all_descendants AS (
38
WITH RECURSIVE hierarchy AS (
39
SELECT ca.id AS descendant_id, ca.parent_account_id AS ancestor_parent_id
40
FROM chart_of_accounts ca
41
WHERE ca.parent_account_id IN (SELECT child_id FROM direct_children)
42
43
UNION ALL
44
45
SELECT coa.id AS descendant_id, coa.parent_account_id AS ancestor_parent_id
46
FROM chart_of_accounts coa
47
INNER JOIN hierarchy h ON coa.parent_account_id = h.descendant_id
48
)
49
SELECT descendant_id, ancestor_parent_id FROM hierarchy
50
),
51
years AS (
52
SELECT p_fin_year_id AS year
53
UNION ALL
54
SELECT (p_fin_year_id - 1) AS year
55
),
56
all_accounts AS (
57
SELECT
58
dc.child_id AS account_id,
59
dc.child_name AS account_name,
60
dc.direct_parent_id AS parent_account_id,
61
dc.account_type_id,
62
y.year,
63
dc.child_id AS summation_base_id
64
FROM direct_children dc
65
CROSS JOIN years y
66
67
UNION ALL
68
69
SELECT
70
ad.descendant_id AS account_id,
71
dc.child_name AS account_name,
72
dc.direct_parent_id AS parent_account_id,
73
dc.account_type_id,
74
y.year,
75
dc.child_id AS summation_base_id
76
FROM direct_children dc
77
JOIN all_descendants ad ON dc.child_id = ad.ancestor_parent_id
78
CROSS JOIN years y
79
)
80
SELECT
81
aa.summation_base_id AS account_id,
82
aa.account_name,
83
aa.parent_account_id,
84
at.name AS account_type,
85
aa.year,
86
87
-- Monthly values, ensuring correct year filtering
88
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 4 THEN je.amount ELSE 0 END), 0) AS apr,
89
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 5 THEN je.amount ELSE 0 END), 0) AS may,
90
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 6 THEN je.amount ELSE 0 END), 0) AS jun,
91
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 7 THEN je.amount ELSE 0 END), 0) AS jul,
92
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 8 THEN je.amount ELSE 0 END), 0) AS aug,
93
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 9 THEN je.amount ELSE 0 END), 0) AS sep,
94
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 10 THEN je.amount ELSE 0 END), 0) AS oct,
95
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 11 THEN je.amount ELSE 0 END), 0) AS nov,
96
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 12 THEN je.amount ELSE 0 END), 0) AS "dec",
97
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 1 THEN je.amount ELSE 0 END), 0) AS jan,
98
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 2 THEN je.amount ELSE 0 END), 0) AS feb,
99
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 3 THEN je.amount ELSE 0 END), 0) AS mar,
100
101
-- Totals for each year
102
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date THEN je.amount ELSE 0 END), 0) AS total_current_year,
103
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date THEN je.amount ELSE 0 END), 0) AS total_last_year,
104
105
-- Difference calculation
106
(COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date THEN je.amount ELSE 0 END), 0) -
107
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date THEN je.amount ELSE 0 END), 0))
108
AS difference
109
110
FROM all_accounts aa
111
LEFT JOIN journal_entries je
112
ON je.account_id = aa.account_id
113
AND (je.transaction_date BETWEEN v_start_date AND v_end_date OR
114
je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date)
115
LEFT JOIN account_types at ON at.id = aa.account_type_id
116
GROUP BY aa.year, aa.summation_base_id, aa.account_name, aa.parent_account_id, at.name
117
ORDER BY aa.summation_base_id, aa.year;
118
119
END;
120
$function$
|
|||||
| Function | get_account_type_hierarchy | Match | ||||||
| Function | get_coa_by_account_type | Match | ||||||
| Function | get_all_bank_transfers_by_company_and_datespan | Match | ||||||
| Function | get_all_expense_categories | Match | ||||||
| Function | get_all_coa | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_coa(p_organization_id uuid, p_finyear_id integer)
2
RETURNS TABLE(id uuid, account_type text, account_number integer, name text, opening_balance numeric, level integer, order_sequence character varying, is_default_account boolean, schedule text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
WITH RECURSIVE coa_view AS (
8
SELECT
9
coa.id,
10
act.name as account_type,
11
coa.account_number::integer,
12
coa.name,
13
coa.parent_account_id,
14
0 AS level,
15
coa.account_number::text AS order_sequence,
16
coa.is_default_account,
17
NULL::text AS schedule
18
FROM public.chart_of_accounts coa
19
INNER JOIN public.account_types act ON coa.account_type_id = act.id
20
WHERE coa.organization_id = p_organization_id
21
AND (coa.parent_account_id = '00000000-0000-0000-0000-000000000000' OR coa.parent_account_id IS NULL)
22
AND coa.is_deleted = FALSE
23
24
UNION ALL
25
26
SELECT
27
coa.id,
28
act.name as account_type,
29
coa.account_number::integer,
30
coa.name,
31
coa.parent_account_id,
32
view.level + 1 AS level,
33
view.order_sequence || '.' || coa.account_number::text AS order_sequence,
34
coa.is_default_account,
35
NULL::text AS schedule
36
FROM public.chart_of_accounts coa
37
INNER JOIN coa_view view ON view.id = coa.parent_account_id
38
INNER JOIN public.account_types act ON coa.account_type_id = act.id
39
WHERE coa.organization_id = p_organization_id
40
AND coa.is_deleted = FALSE
41
),
42
balances AS (
43
SELECT
44
je.account_id,
45
SUM(
46
CASE WHEN je.entry_type = 'D' THEN je.amount
47
ELSE -je.amount END
48
) AS balance
49
FROM public.journal_entries je
50
INNER JOIN public.transaction_headers th
51
ON th.id = je.transaction_id
52
INNER JOIN public.companies c
53
ON th.company_id = c.id
54
AND c.organization_id = p_organization_id
55
INNER JOIN public.finance_year fy
56
ON th.transaction_date BETWEEN fy.start_date AND fy.end_date
57
AND fy.id = p_finyear_id
58
WHERE th.is_deleted = FALSE
59
GROUP BY je.account_id
60
)
61
SELECT
62
view.id,
63
view.account_type,
64
view.account_number,
65
LPAD('', view.level * 4, ' ') || view.name AS name,
66
COALESCE(balances.balance, 0) AS opening_balance,
67
view.level,
68
view.order_sequence::character varying AS order_sequence,
69
view.is_default_account,
70
view.schedule
71
FROM coa_view view
72
LEFT JOIN balances ON balances.account_id = view.id
73
ORDER BY view.order_sequence;
74
END;
75
$function$
|
|||||
| Function | get_all_coa_by_account_type_id | Match | ||||||
| Function | get_all_coa_with_pi | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_coa_with_pi(p_organization_id uuid)
2
RETURNS TABLE(id uuid, account_type text, account_number integer, name text, opening_balance numeric, level integer, order_sequence character varying, schedule text, parent_account_id uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN query
7
WITH RECURSIVE coa_view AS (
8
SELECT
9
coa.id,
10
act.name as account_type,
11
coa.account_number::integer,
12
coa.name,
13
coa.parent_account_id,
14
0 AS level,
15
coa.account_number::text AS order_sequence,
16
NULL::text AS schedule
17
FROM
18
public.chart_of_accounts coa
19
INNER JOIN
20
public.account_types act ON coa.account_type_id = act.id
21
WHERE
22
coa.organization_id = p_organization_id
23
AND coa.parent_account_id = '00000000-0000-0000-0000-000000000000'
24
AND coa.is_deleted = FALSE
25
UNION ALL
26
SELECT
27
coa.id,
28
act.name as account_type,
29
coa.account_number::integer,
30
coa.name,
31
coa.parent_account_id,
32
view.level + 1 AS level,
33
view.order_sequence || '.' || coa.account_number::text AS order_sequence,
34
NULL::text AS schedule
35
FROM
36
public.chart_of_accounts coa
37
INNER JOIN
38
coa_view view ON view.id = coa.parent_account_id
39
INNER JOIN
40
public.account_types act ON coa.account_type_id = act.id
41
WHERE
42
coa.organization_id = p_organization_id
43
AND coa.is_deleted = FALSE
44
)
45
SELECT
46
view.id,
47
view.account_type,
48
view.account_number,
49
LPAD('', view.level * 4, ' ') || view.name AS name,
50
0::numeric AS opening_balance,
51
view.level,
52
view.order_sequence::character varying AS order_sequence,
53
view.schedule,
54
view.parent_account_id
55
FROM
56
coa_view view
57
ORDER BY
58
view.order_sequence;
59
END;
60
$function$
|
|||||
| Function | change_user_password | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.change_user_password(p_user_name text, p_current_password text, p_new_password text, p_modified_by uuid)
2
RETURNS boolean
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_user_id UUID;
7
BEGIN
8
-- Step 1: verify current password
9
SELECT id
10
INTO v_user_id
11
FROM users
12
WHERE user_name = p_user_name
13
AND is_deleted = false
14
AND password_hash = crypt(p_current_password, password_hash);
15
16
IF v_user_id IS NULL THEN
17
RETURN FALSE;
18
END IF;
19
20
-- Step 2: update password
21
UPDATE users
22
SET password_hash = crypt(p_new_password, gen_salt('bf', 12)),
23
modified_on_utc = NOW(),
24
modified_by = p_modified_by
25
WHERE id = v_user_id;
26
27
RETURN TRUE;
28
END;
29
$function$
|
|||||
| Function | get_coa_opening_balance | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_coa_opening_balance(p_coa_id uuid, p_finyear_id integer)
2
RETURNS TABLE(transaction_date timestamp without time zone, coa_id uuid, coa_name text, description text, debit numeric, credit numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_year_text text;
7
BEGIN
8
-- Fetch the year text for the given finance year id
9
SELECT year INTO v_year_text FROM public.finance_year WHERE id = p_finyear_id;
10
IF v_year_text IS NULL THEN
11
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
12
END IF;
13
14
RETURN QUERY
15
SELECT
16
th.transaction_date,
17
th.document_id AS coa_id,
18
coa.name AS coa_name,
19
th.description,
20
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) AS debit,
21
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) AS credit
22
FROM public.transaction_headers th
23
JOIN public.journal_entries je ON th.id = je.transaction_id
24
JOIN public.chart_of_accounts coa ON th.document_id = coa.id
25
WHERE th.document_id = p_coa_id
26
AND je.account_id = th.document_id
27
AND th.transaction_source_type = 13
28
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
29
ORDER BY th.transaction_date;
30
END;
31
$function$
|
|||||
| Function | get_budget_planning_data | Match | ||||||
| Function | get_cash_flow | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_cash_flow()
2
RETURNS TABLE(transaction_date date, account_number text, name text, net_cash_flow numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
je.transaction_date,
9
coa.account_number,
10
coa.name,
11
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE -je.amount END) AS net_cash_flow
12
FROM
13
public.journal_entries je
14
JOIN
15
public.chart_of_accounts coa ON je.account_id = coa.id
16
WHERE
17
coa.account_type_id = 1 -- Current Assets, focusing on cash accounts
18
AND je.is_deleted = false
19
GROUP BY
20
je.transaction_date, coa.account_number, coa.name
21
ORDER BY
22
coa.account_number, je.transaction_date ;
23
END;
24
$function$
|
|||||
| Function | get_cash_flow | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_cash_flow(p_company_id uuid)
2
RETURNS TABLE(transaction_date date, account_number text, name text, net_cash_flow numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
6
BEGIN
7
RETURN QUERY
8
SELECT
9
je.transaction_date::date,
10
coa.account_number,
11
coa.name,
12
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE -je.amount END) AS net_cash_flow
13
FROM
14
public.journal_entries je
15
JOIN
16
public.chart_of_accounts coa ON je.account_id = coa.id
17
JOIN
18
public.transaction_headers th ON je.transaction_id = th.id
19
WHERE
20
coa.account_type_id = 1 -- Current Assets, focusing on cash accounts
21
AND je.is_deleted = false
22
AND th.company_id = p_company_id
23
GROUP BY
24
je.transaction_date, coa.account_number, coa.name
25
ORDER BY
26
coa.account_number, je.transaction_date ;
27
END;
28
29
$function$
|
|||||
| Function | get_chart_of_accounts_hierarchy | Match | ||||||
| Function | get_company_details | Match | ||||||
| Function | get_customer_dues | Match | ||||||
| Function | get_customer_opening_balance | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_customer_opening_balance(p_customer_id uuid, p_finyear_id integer)
2
RETURNS TABLE(transaction_date timestamp without time zone, customer_id uuid, customer_name text, description text, debit numeric, credit numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_year_text text;
7
BEGIN
8
-- Fetch the year text for the given finance year id
9
SELECT year INTO v_year_text FROM public.finance_year WHERE id = p_finyear_id;
10
IF v_year_text IS NULL THEN
11
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
12
END IF;
13
14
RETURN QUERY
15
SELECT
16
th.transaction_date,
17
th.customer_id,
18
c.name::text AS customer_name, -- explicit cast here!
19
th.description,
20
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) AS debit,
21
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) AS credit
22
FROM public.transaction_headers th
23
JOIN public.customers c ON th.customer_id = c.id
24
JOIN public.journal_entries je ON th.id = je.transaction_id
25
JOIN public.chart_of_accounts sd ON sd.name ILIKE '%Sundry Debtors%'
26
WHERE th.customer_id = p_customer_id
27
AND je.account_id = sd.id
28
AND th.transaction_source_type = 13
29
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
30
ORDER BY th.transaction_date;
31
END;
32
$function$
|
|||||
| Function | get_customer_outstanding | Match | ||||||
| Function | get_trial_balance_test | Match | ||||||
| Function | get_dashboard_totals_by_company_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_dashboard_totals_by_company_id(p_company_id uuid, p_finance_year_id integer)
2
RETURNS TABLE(income_total numeric, expense_total numeric, pending_dues_total numeric, pending_payments_total numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_organization_id UUID;
7
v_financial_year_start DATE;
8
v_financial_year_end DATE;
9
v_finance_year TEXT;
10
BEGIN
11
-- Fetch organization ID
12
SELECT organization_id INTO v_organization_id
13
FROM public.companies
14
WHERE id = p_company_id;
15
16
IF NOT FOUND THEN
17
RAISE EXCEPTION 'Invalid company ID: %', p_company_id;
18
END IF;
19
20
-- Fetch financial year details
21
SELECT start_date, end_date, year INTO v_financial_year_start, v_financial_year_end, v_finance_year
22
FROM public.finance_year
23
WHERE id = p_finance_year_id;
24
25
IF NOT FOUND THEN
26
RAISE EXCEPTION 'Invalid finance year ID: %', p_finance_year_id;
27
END IF;
28
29
-- Calculate totals
30
RETURN QUERY
31
SELECT
32
-- Income Total
33
ROUND(
34
(
35
SELECT COALESCE(SUM(je.amount), 0)
36
FROM public.journal_entries je
37
INNER JOIN public.chart_of_accounts ca ON je.account_id = ca.id
38
INNER JOIN public.account_types at ON ca.account_type_id = at.id
39
INNER JOIN public.account_categories ac ON at.account_category_id = ac.id
40
INNER JOIN public.transaction_headers tr ON je.transaction_id = tr.id
41
WHERE ac.id = 4
42
AND tr.company_id = p_company_id
43
AND ca.organization_id = v_organization_id
44
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
45
AND je.is_deleted = FALSE
46
), 2
47
) AS income_total,
48
49
-- Expense Total
50
ROUND(
51
(
52
SELECT COALESCE(SUM(je.amount), 0)
53
FROM public.journal_entries je
54
INNER JOIN public.chart_of_accounts ca ON je.account_id = ca.id
55
INNER JOIN public.account_types at ON ca.account_type_id = at.id
56
INNER JOIN public.account_categories ac ON at.account_category_id = ac.id
57
INNER JOIN public.transaction_headers tr ON je.transaction_id = tr.id
58
WHERE ac.id = 5
59
AND tr.company_id = p_company_id
60
AND ca.organization_id = v_organization_id
61
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
62
AND je.is_deleted = FALSE
63
), 2
64
) AS expense_total,
65
66
ROUND(
67
(
68
SELECT
69
COALESCE(SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) -
70
COALESCE(SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END), 0)
71
FROM public.journal_entries je
72
INNER JOIN public.chart_of_accounts ca ON je.account_id = ca.id
73
INNER JOIN public.account_types at ON ca.account_type_id = at.id
74
INNER JOIN public.account_categories ac ON at.account_category_id = ac.id
75
INNER JOIN public.transaction_headers tr ON je.transaction_id = tr.id
76
WHERE at.name IN ('Accounts Receivable', 'Current Assets') -- Filter specifically for receivables
77
AND tr.company_id = p_company_id
78
AND ca.organization_id = v_organization_id
79
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
80
AND je.is_deleted = FALSE
81
), 2
82
) AS pending_dues_total,
83
84
-- Pending Payments Total
85
ROUND(
86
(
87
SELECT COALESCE(SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END), 0) -
88
COALESCE(SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END), 0)
89
FROM public.journal_entries je
90
INNER JOIN public.chart_of_accounts ca ON je.account_id = ca.id
91
INNER JOIN public.account_types at ON ca.account_type_id = at.id
92
INNER JOIN public.transaction_headers tr ON je.transaction_id = tr.id
93
WHERE ca.account_type_id IN (
94
SELECT id
95
FROM public.get_account_type_hierarchy(2)
96
)
97
AND tr.company_id = p_company_id
98
AND ca.organization_id = v_organization_id
99
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
100
AND je.is_deleted = FALSE
101
AND je.entry_type IN ('C', 'D') -- Ensure only valid entry types are included
102
), 2
103
) AS pending_payments_total
104
105
106
;
107
108
END;
109
$function$
|
|||||
| Function | get_dashboard_totals_test | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_dashboard_totals_test(p_company_id uuid, p_finance_year_id integer)
2
RETURNS TABLE(income_total numeric, expense_total numeric, pending_dues_total numeric, pending_payments_total numeric, prev_income_total numeric, prev_expense_total numeric, prev_pending_dues_total numeric, prev_pending_payments_total numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_organization_id UUID;
7
v_financial_year_start DATE;
8
v_financial_year_end DATE;
9
v_prev_financial_year_start DATE;
10
v_prev_financial_year_end DATE;
11
v_finance_year INTEGER;
12
v_today DATE := CURRENT_DATE;
13
BEGIN
14
-- Fetch organization ID
15
SELECT organization_id INTO v_organization_id
16
FROM public.companies
17
WHERE id = p_company_id;
18
19
IF NOT FOUND THEN
20
RAISE EXCEPTION 'Invalid company ID: %', p_company_id;
21
END IF;
22
23
-- Get financial year integer from 'YYYY-YY'
24
SELECT LEFT(year, 4)::INTEGER INTO v_finance_year
25
FROM public.finance_year
26
WHERE id = p_finance_year_id
27
LIMIT 1;
28
29
IF NOT FOUND THEN
30
RAISE EXCEPTION 'Invalid finance year ID: %', p_finance_year_id;
31
END IF;
32
33
-- Define financial year range
34
v_financial_year_start := MAKE_DATE(v_finance_year, 4, 1);
35
v_financial_year_end := MAKE_DATE(v_finance_year + 1, 3, 31);
36
37
-- Define previous financial year range
38
v_prev_financial_year_start := MAKE_DATE(v_finance_year - 1, 4, 1);
39
v_prev_financial_year_end := MAKE_DATE(v_finance_year, 3, 31);
40
41
-- Adjust financial year ends if the year is ongoing
42
IF v_today < v_financial_year_end THEN
43
v_financial_year_end := v_today;
44
END IF;
45
46
IF v_today < v_prev_financial_year_end THEN
47
v_prev_financial_year_end := v_today - INTERVAL '1 year';
48
END IF;
49
50
-- Align both end dates to same day/month by limiting to shorter period
51
v_financial_year_end := LEAST(v_financial_year_end, v_prev_financial_year_end + INTERVAL '1 year');
52
v_prev_financial_year_end := v_financial_year_end - INTERVAL '1 year';
53
54
-- Return calculated data
55
RETURN QUERY
56
SELECT
57
-- Current Year Income
58
ROUND(COALESCE((
59
SELECT SUM(je.amount)
60
FROM journal_entries je
61
JOIN chart_of_accounts ca ON je.account_id = ca.id
62
JOIN account_types at ON ca.account_type_id = at.id
63
JOIN account_categories ac ON at.account_category_id = ac.id
64
JOIN transaction_headers tr ON je.transaction_id = tr.id
65
WHERE ac.id = 4
66
AND tr.company_id = p_company_id
67
AND ca.organization_id = v_organization_id
68
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
69
AND je.is_deleted = FALSE
70
), 0), 2),
71
72
-- Current Year Expense
73
ROUND(COALESCE((
74
SELECT SUM(je.amount)
75
FROM journal_entries je
76
JOIN chart_of_accounts ca ON je.account_id = ca.id
77
JOIN account_types at ON ca.account_type_id = at.id
78
JOIN account_categories ac ON at.account_category_id = ac.id
79
JOIN transaction_headers tr ON je.transaction_id = tr.id
80
WHERE ac.id = 5
81
and tr.transaction_source_type = 2
82
AND tr.company_id = p_company_id
83
AND ca.organization_id = v_organization_id
84
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
85
AND je.is_deleted = FALSE
86
), 0), 2),
87
88
-- Pending Dues
89
ROUND(COALESCE((
90
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
91
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
92
FROM journal_entries je
93
JOIN chart_of_accounts ca ON je.account_id = ca.id
94
JOIN account_types at ON ca.account_type_id = at.id
95
JOIN transaction_headers tr ON je.transaction_id = tr.id
96
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
97
AND tr.company_id = p_company_id
98
AND ca.organization_id = v_organization_id
99
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
100
AND je.is_deleted = FALSE
101
), 0), 2),
102
103
-- Pending Payments
104
ROUND(COALESCE((
105
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
106
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
107
FROM journal_entries je
108
JOIN chart_of_accounts ca ON je.account_id = ca.id
109
JOIN account_types at ON ca.account_type_id = at.id
110
JOIN transaction_headers tr ON je.transaction_id = tr.id
111
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
112
AND tr.company_id = p_company_id
113
AND ca.organization_id = v_organization_id
114
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
115
AND je.is_deleted = FALSE
116
), 0), 2),
117
118
-- Previous Year Income
119
ROUND(COALESCE((
120
SELECT SUM(je.amount)
121
FROM journal_entries je
122
JOIN chart_of_accounts ca ON je.account_id = ca.id
123
JOIN account_types at ON ca.account_type_id = at.id
124
JOIN account_categories ac ON at.account_category_id = ac.id
125
JOIN transaction_headers tr ON je.transaction_id = tr.id
126
WHERE ac.id = 4
127
AND tr.company_id = p_company_id
128
AND ca.organization_id = v_organization_id
129
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
130
AND je.is_deleted = FALSE
131
), 0), 2),
132
133
-- Previous Year Expense
134
ROUND(COALESCE((
135
SELECT SUM(je.amount)
136
FROM journal_entries je
137
JOIN chart_of_accounts ca ON je.account_id = ca.id
138
JOIN account_types at ON ca.account_type_id = at.id
139
JOIN account_categories ac ON at.account_category_id = ac.id
140
JOIN transaction_headers tr ON je.transaction_id = tr.id
141
WHERE ac.id = 5
142
AND tr.company_id = p_company_id
143
AND ca.organization_id = v_organization_id
144
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
145
AND je.is_deleted = FALSE
146
), 0), 2),
147
148
-- Previous Year Pending Dues
149
ROUND(COALESCE((
150
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
151
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
152
FROM journal_entries je
153
JOIN chart_of_accounts ca ON je.account_id = ca.id
154
JOIN account_types at ON ca.account_type_id = at.id
155
JOIN transaction_headers tr ON je.transaction_id = tr.id
156
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
157
AND tr.company_id = p_company_id
158
AND ca.organization_id = v_organization_id
159
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
160
AND je.is_deleted = FALSE
161
), 0), 2),
162
163
-- Previous Year Pending Payments
164
ROUND(COALESCE((
165
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
166
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
167
FROM journal_entries je
168
JOIN chart_of_accounts ca ON je.account_id = ca.id
169
JOIN account_types at ON ca.account_type_id = at.id
170
JOIN transaction_headers tr ON je.transaction_id = tr.id
171
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
172
AND tr.company_id = p_company_id
173
AND ca.organization_id = v_organization_id
174
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
175
AND je.is_deleted = FALSE
176
), 0), 2);
177
END;
178
$function$
|
|||||
| Function | get_user_deletion_request_by_email | Match | ||||||
| Function | get_journal_voucher_by_header_id | Match | ||||||
| Function | get_sundry_debtors_ledger | Match | ||||||
| Function | get_employee_opening_balance | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_employee_opening_balance(p_employee_id uuid, p_finyear_id integer)
2
RETURNS TABLE(transaction_date timestamp without time zone, employee_id uuid, employee_name text, description text, debit numeric, credit numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_year_text text;
7
BEGIN
8
-- Fetch the year text for the given finance year id
9
SELECT year INTO v_year_text FROM public.finance_year WHERE id = p_finyear_id;
10
IF v_year_text IS NULL THEN
11
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
12
END IF;
13
14
RETURN QUERY
15
SELECT
16
th.transaction_date,
17
th.employee_id,
18
(e.first_name || ' ' || e.last_name) AS employee_name,
19
th.description,
20
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) AS debit,
21
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) AS credit
22
FROM public.transaction_headers th
23
JOIN public.employees e ON th.employee_id = e.id
24
JOIN public.journal_entries je ON th.id = je.transaction_id
25
JOIN public.chart_of_accounts sp ON sp.name ILIKE '%Salary Payable%'
26
WHERE th.employee_id = p_employee_id
27
AND je.account_id = sp.id
28
AND th.transaction_source_type = 13
29
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
30
ORDER BY th.transaction_date;
31
END;
32
$function$
|
|||||
| Function | get_expense_monthly_data | Match | ||||||
| Function | get_expense_overview | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_expense_overview(p_company_id uuid, p_period_type integer, p_finance_id integer)
2
RETURNS TABLE(period text, year numeric, account_name text, total_expense numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_financial_year_start date;
7
v_financial_year_end date;
8
v_finance_year text;
9
v_organization_id uuid;
10
11
-- Define constants for period types
12
CONST_MONTHLY CONSTANT INTEGER := 1;
13
CONST_QUARTERLY CONSTANT INTEGER := 2;
14
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
15
CONST_YEARLY CONSTANT INTEGER := 4;
16
BEGIN
17
-- Step 1: Fetch the organization ID based on the company ID
18
SELECT organization_id INTO v_organization_id
19
FROM public.companies
20
WHERE id = p_company_id;
21
22
-- Step 2: Get financial year boundaries
23
SELECT fy.start_date, fy.end_date, fy.year INTO v_financial_year_start, v_financial_year_end, v_finance_year
24
FROM public.finance_year fy
25
WHERE id = p_finance_id;
26
27
-- Check the period type and execute the appropriate logic
28
IF p_period_type = CONST_MONTHLY THEN
29
RETURN QUERY
30
WITH RECURSIVE AccountHierarchy AS (
31
-- Base case: Select root accounts (expense accounts)
32
SELECT
33
coa.id AS account_id,
34
coa.name AS account_name,
35
coa.account_number,
36
coa.parent_account_id
37
FROM
38
public.chart_of_accounts coa
39
WHERE
40
coa.account_type_id = 5 -- Expense accounts
41
AND coa.organization_id = v_organization_id
42
43
UNION ALL
44
45
-- Recursive case: Select child accounts under the root accounts
46
SELECT
47
coa.id AS account_id,
48
coa.name AS account_name,
49
coa.account_number,
50
coa.parent_account_id
51
FROM
52
public.chart_of_accounts coa
53
INNER JOIN
54
AccountHierarchy ah ON coa.parent_account_id = ah.account_id
55
),
56
months AS (
57
SELECT
58
TO_CHAR(months.m, 'Mon') AS period,
59
EXTRACT(YEAR FROM months.m) AS extracted_year,
60
EXTRACT(MONTH FROM months.m) AS month_num
61
FROM
62
generate_series(v_financial_year_start, v_financial_year_end, '1 month'::interval) AS months(m)
63
)
64
SELECT
65
months.period,
66
months.extracted_year AS year,
67
ah.account_name,
68
COALESCE(SUM(je.amount), 0) AS total_expense
69
FROM
70
months
71
INNER JOIN journal_entries je
72
ON EXTRACT(MONTH FROM je.transaction_date) = months.month_num
73
AND EXTRACT(YEAR FROM je.transaction_date) = months.extracted_year
74
INNER JOIN transaction_headers tr
75
ON je.transaction_id = tr.id
76
AND tr.company_id = p_company_id -- Ensure filtering by company_id
77
INNER JOIN AccountHierarchy ah
78
ON je.account_id = ah.account_id
79
WHERE
80
je.entry_type = 'D' -- Only debits are considered expenses
81
AND je.is_deleted = FALSE -- Filter non-deleted entries
82
GROUP BY
83
months.period, months.extracted_year, months.month_num, ah.account_name
84
ORDER BY
85
months.extracted_year, months.month_num, ah.account_name;
86
87
ELSIF p_period_type = CONST_QUARTERLY THEN
88
RETURN QUERY
89
WITH RECURSIVE AccountHierarchy AS (
90
-- Base case: Select root accounts (expense accounts)
91
SELECT
92
coa.id AS account_id,
93
coa.name AS account_name,
94
coa.account_number,
95
coa.parent_account_id
96
FROM
97
public.chart_of_accounts coa
98
WHERE
99
coa.account_type_id = 5 -- Expense accounts
100
AND coa.organization_id = v_organization_id
101
102
UNION ALL
103
104
-- Recursive case: Select child accounts under the root accounts
105
SELECT
106
coa.id AS account_id,
107
coa.name AS account_name,
108
coa.account_number,
109
coa.parent_account_id
110
FROM
111
public.chart_of_accounts coa
112
INNER JOIN
113
AccountHierarchy ah ON coa.parent_account_id = ah.account_id
114
),
115
quarters AS (
116
SELECT
117
'Q1' AS period, 1 AS quarter_num, EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
118
v_financial_year_start AS start_date, v_financial_year_start + interval '2 month' AS end_date
119
UNION ALL
120
SELECT
121
'Q2' AS period, 2 AS quarter_num, EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
122
v_financial_year_start + interval '3 month' AS start_date, v_financial_year_start + interval '5 month' AS end_date
123
UNION ALL
124
SELECT
125
'Q3' AS period, 3 AS quarter_num, EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
126
v_financial_year_start + interval '6 month' AS start_date, v_financial_year_start + interval '8 month' AS end_date
127
UNION ALL
128
SELECT
129
'Q4' AS period, 4 AS quarter_num, EXTRACT(YEAR FROM v_financial_year_start) + 1 AS extracted_year,
130
v_financial_year_start + interval '9 month' AS start_date, v_financial_year_end AS end_date
131
)
132
SELECT
133
quarters.period,
134
quarters.extracted_year AS year,
135
ah.account_name,
136
COALESCE(SUM(je.amount), 0) AS total_expense
137
FROM
138
quarters
139
LEFT JOIN journal_entries je
140
ON je.transaction_date BETWEEN quarters.start_date AND quarters.end_date
141
LEFT JOIN transaction_headers tr
142
ON je.transaction_id = tr.id
143
AND tr.company_id = p_company_id -- Ensure filtering by company_id
144
INNER JOIN AccountHierarchy ah
145
ON je.account_id = ah.account_id
146
WHERE
147
je.entry_type = 'D' -- Only debits are considered expenses
148
AND je.is_deleted = FALSE -- Filter non-deleted entries
149
GROUP BY
150
quarters.period, quarters.extracted_year, quarters.quarter_num, ah.account_name
151
ORDER BY
152
quarters.extracted_year, quarters.quarter_num, ah.account_name;
153
154
-- Repeat similar adjustments for Half-Yearly and Yearly periods.
155
ELSIF p_period_type = CONST_HALF_YEARLY THEN
156
RETURN QUERY
157
WITH RECURSIVE AccountHierarchy AS (
158
-- Base case: Select root accounts (expense accounts)
159
SELECT
160
coa.id AS account_id,
161
coa.name AS account_name,
162
coa.account_number,
163
coa.parent_account_id
164
FROM
165
public.chart_of_accounts coa
166
WHERE
167
coa.account_type_id = 5 -- Expense accounts
168
AND coa.organization_id = v_organization_id
169
170
UNION ALL
171
172
-- Recursive case: Select child accounts under the root accounts
173
SELECT
174
coa.id AS account_id,
175
coa.name AS account_name,
176
coa.account_number,
177
coa.parent_account_id
178
FROM
179
public.chart_of_accounts coa
180
INNER JOIN
181
AccountHierarchy ah ON coa.parent_account_id = ah.account_id
182
),
183
half_years AS (
184
SELECT
185
'H1' AS period,
186
1 AS half_year_num,
187
EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
188
v_financial_year_start AS start_date,
189
v_financial_year_start + interval '5 month' AS end_date
190
UNION ALL
191
SELECT
192
'H2' AS period,
193
2 AS half_year_num,
194
EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
195
v_financial_year_start + interval '6 month' AS start_date,
196
v_financial_year_end AS end_date
197
)
198
SELECT
199
half_years.period,
200
half_years.extracted_year AS year,
201
ah.account_name,
202
COALESCE(SUM(je.amount), 0) AS total_expense
203
FROM
204
half_years
205
LEFT JOIN journal_entries je
206
ON je.transaction_date BETWEEN half_years.start_date AND half_years.end_date
207
LEFT JOIN transaction_headers tr
208
ON je.transaction_id = tr.id
209
AND tr.company_id = p_company_id -- Ensure filtering by company_id
210
INNER JOIN AccountHierarchy ah
211
ON je.account_id = ah.account_id
212
WHERE
213
je.entry_type = 'D' -- Only debits are considered expenses
214
AND je.is_deleted = FALSE -- Filter non-deleted entries
215
GROUP BY
216
half_years.period, half_years.extracted_year, half_years.half_year_num, ah.account_name
217
ORDER BY
218
half_years.extracted_year, half_years.half_year_num, ah.account_name;
219
ELSIF p_period_type = CONST_YEARLY THEN
220
RETURN QUERY
221
WITH RECURSIVE AccountHierarchy AS (
222
-- Base case: Select root accounts (expense accounts)
223
SELECT
224
coa.id AS account_id,
225
coa.name AS account_name,
226
coa.account_number,
227
coa.parent_account_id
228
FROM
229
public.chart_of_accounts coa
230
WHERE
231
coa.account_type_id = 5 -- Expense accounts
232
AND coa.organization_id = v_organization_id
233
234
UNION ALL
235
236
-- Recursive case: Select child accounts under the root accounts
237
SELECT
238
coa.id AS account_id,
239
coa.name AS account_name,
240
coa.account_number,
241
coa.parent_account_id
242
FROM
243
public.chart_of_accounts coa
244
INNER JOIN
245
AccountHierarchy ah ON coa.parent_account_id = ah.account_id
246
)
247
SELECT
248
'Year' AS period,
249
EXTRACT(YEAR FROM v_financial_year_start) AS year,
250
ah.account_name,
251
COALESCE(SUM(je.amount), 0) AS total_expense
252
FROM
253
journal_entries je
254
LEFT JOIN transaction_headers tr
255
ON je.transaction_id = tr.id
256
AND tr.company_id = p_company_id -- Ensure filtering by company_id
257
INNER JOIN AccountHierarchy ah
258
ON je.account_id = ah.account_id
259
WHERE
260
je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
261
AND je.entry_type = 'D' -- Only debits are considered expenses
262
AND je.is_deleted = FALSE -- Filter non-deleted entries
263
GROUP BY
264
year, ah.account_name
265
ORDER BY
266
year, ah.account_name;
267
268
END IF;
269
END;
270
$function$
|
|||||
| Function | get_financial_year_dates | Match | ||||||
| Function | get_new_voucher_number | Match | ||||||
| Function | get_full_account_hierarchy_overview | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_full_account_hierarchy_overview(p_company_id uuid, p_fin_year_id integer)
2
RETURNS TABLE(account_id uuid, account_name text, parent_account_id uuid, hierarchy_path text[], financial_year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_sum numeric, difference numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
#variable_conflict use_column
6
DECLARE
7
v_current_year INTEGER := p_fin_year_id;
8
v_previous_year INTEGER := p_fin_year_id - 1;
9
v_organization_id UUID;
10
BEGIN
11
SELECT c.organization_id INTO v_organization_id
12
FROM public.companies c
13
WHERE c.id = p_company_id;
14
15
RETURN QUERY
16
WITH RECURSIVE account_with_path AS (
17
-- This is a much safer way to build the path
18
SELECT
19
ca.id,
20
ca.name,
21
ca.parent_account_id,
22
-- If parent is missing or is self, start a new path. Otherwise, build the path.
23
COALESCE(p.name, '') || '/' || ca.name AS path_str
24
FROM chart_of_accounts ca
25
LEFT JOIN chart_of_accounts p ON ca.parent_account_id = p.id AND ca.parent_account_id != ca.id
26
WHERE ca.organization_id = v_organization_id
27
),
28
financial_years AS (
29
SELECT id AS fin_year_id, start_date, end_date
30
FROM finance_year
31
WHERE id IN (v_previous_year, v_current_year)
32
),
33
aggregated_data AS (
34
SELECT
35
awp.id, awp.name, awp.parent_account_id,
36
-- Convert the path string to an array for AG Grid
37
string_to_array(ltrim(awp.path_str, '/'), '/') AS hierarchy_path,
38
fy.fin_year_id,
39
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN 1 ELSE 0 END), 0) AS apr,
40
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN 1 ELSE 0 END), 0) AS may,
41
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN 1 ELSE 0 END), 0) AS jun,
42
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN 1 ELSE 0 END), 0) AS jul,
43
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN 1 ELSE 0 END), 0) AS aug,
44
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN 1 ELSE 0 END), 0) AS sep,
45
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN 1 ELSE 0 END), 0) AS oct,
46
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN 1 ELSE 0 END), 0) AS nov,
47
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN 1 ELSE 0 END), 0) AS "dec",
48
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN 1 ELSE 0 END), 0) AS jan,
49
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN 1 ELSE 0 END), 0) AS feb,
50
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN 1 ELSE 0 END), 0) AS mar,
51
COALESCE(SUM(je.amount), 0) AS total_sum
52
FROM account_with_path awp
53
CROSS JOIN financial_years fy
54
LEFT JOIN journal_entries je ON je.account_id = awp.id AND je.transaction_date BETWEEN fy.start_date AND fy.end_date
55
LEFT JOIN transaction_headers th ON th.id = je.transaction_id AND th.company_id = p_company_id
56
WHERE (th.transaction_source_type IS NULL OR th.transaction_source_type != 13)
57
GROUP BY awp.id, awp.name, awp.parent_account_id, awp.path_str, fy.fin_year_id
58
)
59
SELECT
60
a1.id, a1.name, a1.parent_account_id, a1.hierarchy_path, a1.fin_year_id,
61
a1.apr, a1.may, a1.jun, a1.jul, a1.aug, a1.sep, a1.oct, a1.nov, a1."dec",
62
a1.jan, a1.feb, a1.mar,
63
a1.total_sum,
64
(COALESCE(a1.total_sum, 0) - COALESCE(a2.total_sum, 0)) AS difference
65
FROM aggregated_data a1
66
LEFT JOIN aggregated_data a2 ON a1.id = a2.id AND a1.fin_year_id = v_current_year AND a2.fin_year_id = v_previous_year
67
ORDER BY a1.hierarchy_path, a1.fin_year_id;
68
END;
69
$function$
|
|||||
| Function | get_income_expense_monthly_hierarchy | Match | ||||||
| Function | get_income_expense_ytd_all_years | Match | ||||||
| Function | get_total_income | Match | ||||||
| Function | get_ledger_report | Match | ||||||
| Function | get_maintenance_collection_status | Match | ||||||
| Function | get_n_years_expenses | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_n_years_expenses(p_company_id uuid, p_fin_year_ids integer[])
2
RETURNS TABLE(account_id uuid, account_name text, finance_year_id integer, finance_year_label text, month_number integer, month_label text, amount numeric)
3
LANGUAGE plpgsql
4
STABLE PARALLEL SAFE
5
AS $function$
6
DECLARE
7
months_arr TEXT[] := ARRAY['Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar'];
8
yrec RECORD;
9
m INT;
10
month_start DATE;
11
month_end DATE;
12
BEGIN
13
FOR yrec IN
14
SELECT
15
fy.id,
16
fy.start_date,
17
fy.end_date,
18
CONCAT(EXTRACT(YEAR FROM fy.start_date), '-', RIGHT(TO_CHAR(fy.end_date, 'YYYY'), 2)) AS label
19
FROM public.finance_year fy
20
WHERE fy.id = ANY(p_fin_year_ids)
21
ORDER BY fy.start_date
22
LOOP
23
FOR m IN 1..12 LOOP
24
month_start := yrec.start_date + ((m - 1) * INTERVAL '1 month');
25
month_end := (month_start + INTERVAL '1 month' - INTERVAL '1 day')::DATE;
26
27
RETURN QUERY
28
WITH RECURSIVE AccountHierarchy AS (
29
SELECT coa.id, coa.name, coa.parent_account_id
30
FROM public.chart_of_accounts coa
31
WHERE coa.account_type_id = 5
32
UNION ALL
33
SELECT coa.id, coa.name, coa.parent_account_id
34
FROM public.chart_of_accounts coa
35
INNER JOIN AccountHierarchy ah ON coa.parent_account_id = ah.id
36
)
37
SELECT
38
ah.id AS account_id,
39
ah.name AS account_name,
40
yrec.id AS finance_year_id,
41
yrec.label AS finance_year_label,
42
m AS month_number,
43
months_arr[m] AS month_label,
44
COALESCE(SUM(je.amount), 0) AS amount
45
FROM AccountHierarchy ah
46
LEFT JOIN public.journal_entries je
47
ON je.account_id = ah.id
48
AND je.transaction_date BETWEEN month_start AND month_end
49
AND je.is_deleted = FALSE
50
LEFT JOIN public.transaction_headers th
51
ON je.transaction_id = th.id
52
AND th.company_id = p_company_id
53
GROUP BY ah.id, ah.name;
54
END LOOP;
55
END LOOP;
56
END;
57
$function$
|
|||||
| Function | get_user_notifications | Match | ||||||
| Function | get_new_transfer_number | Match | ||||||
| Function | get_user_by_email | Match | ||||||
| Function | get_user_by_username | Match | ||||||
| Function | get_vendor_dues | Match | ||||||
| Function | get_vendor_ledger | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_vendor_ledger(p_vendor_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
1
CREATE OR REPLACE FUNCTION public.get_vendor_ledger(p_vendor_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
2
RETURNS TABLE(transaction_date date, account_id uuid, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
2
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
6
v_sundary_creditors_account_id UUID;
7
v_vendor_advance_account_id UUID;
8
BEGIN
5
BEGIN
9
SELECT
10
accounts_payable_account_id,
11
vendor_advance_account_id
12
INTO
13
v_sundary_creditors_account_id,
14
v_vendor_advance_account_id
15
FROM public.organization_accounts
16
WHERE organization_id = p_organization_id
17
LIMIT 1;
18
19
RETURN QUERY
6
RETURN QUERY
20
WITH all_transactions AS (
7
WITH vendor_transactions AS (
21
SELECT
8
SELECT
22
th.transaction_date::date,
9
th.transaction_date::date AS transaction_date,
23
coa.id AS account_id,
24
coa.name AS account_name,
10
coa.name AS account_name,
25
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
11
CASE
26
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
12
WHEN je.entry_type = 'D' THEN je.amount
13
ELSE 0
14
END AS debit,
15
CASE
16
WHEN je.entry_type = 'C' THEN je.amount
17
ELSE 0
18
END AS credit,
19
th.company_id,
20
th.vendor_id,
21
th.transaction_source_type,
27
th.document_number,
22
th.document_number,
28
th.document_id,
23
th.document_id
29
th.transaction_source_type,
24
FROM
30
tst.sort_order,
25
public.transaction_headers th
31
th.id AS transaction_id,
26
INNER JOIN
32
je.id AS journal_entry_id
27
public.journal_entries je ON th.id = je.transaction_id
33
FROM public.transaction_headers th
28
INNER JOIN
34
JOIN public.journal_entries je ON th.id = je.transaction_id
29
public.chart_of_accounts coa ON je.account_id = coa.id
35
JOIN public.transaction_source_types tst ON th.transaction_source_type = tst.id
36
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
37
WHERE
30
WHERE
38
th.company_id = p_company_id
31
th.vendor_id = p_vendor_id
39
AND th.vendor_id = p_vendor_id
32
AND th.company_id = p_company_id
40
AND je.account_id IN (v_sundary_creditors_account_id, v_vendor_advance_account_id)
33
AND coa.organization_id = p_organization_id
41
AND th.transaction_date BETWEEN p_start_date AND p_end_date
42
AND th.is_deleted = false
34
AND th.is_deleted = false
43
AND je.is_deleted = false
35
AND je.is_deleted = false
36
AND th.transaction_date >= p_start_date
37
AND th.transaction_date <= p_end_date
44
)
38
)
45
SELECT
39
SELECT
46
at.transaction_date,
40
vt.transaction_date,
47
at.account_id,
41
vt.account_name,
48
at.account_name,
42
vt.debit,
49
at.debit,
43
vt.credit,
50
at.credit,
44
SUM(vt.debit - vt.credit) OVER (PARTITION BY vt.vendor_id ORDER BY vt.transaction_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance,
51
SUM(at.debit - at.credit) OVER (
45
vt.transaction_source_type,
46
vt.document_number,
47
vt.document_id
48
FROM
49
vendor_transactions vt
52
ORDER BY
50
ORDER BY
53
at.transaction_date,
51
vt.transaction_date;
54
at.sort_order,
55
at.transaction_id,
56
at.journal_entry_id
57
) AS balance,
58
at.transaction_source_type,
59
at.document_number,
60
at.document_id
61
FROM all_transactions AS at
62
ORDER BY
63
at.transaction_date,
64
at.sort_order,
65
at.transaction_id,
66
at.journal_entry_id;
67
END;
52
END;
68
$function$
53
$function$
|
|||||
| Function | get_vendor_outstanding | Match | ||||||
| Function | get_year_wise_opening_balance | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_year_wise_opening_balance(p_finyear_id integer)
2
RETURNS TABLE(id bigint, transaction_date timestamp without time zone, entity_id uuid, entity_name text, description text, debit numeric, credit numeric, entity_type text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_year_text text;
7
BEGIN
8
-- Fetch the year text for the given finance year id
9
SELECT fy.year INTO v_year_text FROM public.finance_year fy WHERE fy.id = p_finyear_id;
10
IF v_year_text IS NULL THEN
11
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
12
END IF;
13
14
RETURN QUERY
15
16
-- VENDORS
17
SELECT
18
th.id,
19
th.transaction_date,
20
th.vendor_id as entity_id,
21
v.name::text as entity_name,
22
th.description,
23
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) as debit,
24
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) as credit,
25
'Vendor' as entity_type
26
FROM public.transaction_headers th
27
JOIN public.vendors v ON th.vendor_id = v.id
28
JOIN public.journal_entries je ON th.id = je.transaction_id
29
JOIN public.chart_of_accounts sc ON je.account_id = sc.id AND sc.name ILIKE '%Sundry Creditors%'
30
WHERE th.transaction_source_type = 13
31
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
32
33
UNION ALL
34
35
-- CUSTOMERS
36
SELECT
37
th.id,
38
th.transaction_date,
39
th.customer_id as entity_id,
40
c.name::text as entity_name,
41
th.description,
42
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) as debit,
43
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) as credit,
44
'Customer' as entity_type
45
FROM public.transaction_headers th
46
JOIN public.customers c ON th.customer_id = c.id
47
JOIN public.journal_entries je ON th.id = je.transaction_id
48
JOIN public.chart_of_accounts sd ON je.account_id = sd.id AND sd.name ILIKE '%Sundry Debtors%'
49
WHERE th.transaction_source_type = 13
50
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
51
52
UNION ALL
53
54
-- EMPLOYEES
55
SELECT
56
th.id,
57
th.transaction_date,
58
th.employee_id as entity_id,
59
e.first_name::text as entity_name,
60
th.description,
61
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) as debit,
62
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) as credit,
63
'Employee' as entity_type
64
FROM public.transaction_headers th
65
JOIN public.employees e ON th.employee_id = e.id
66
JOIN public.journal_entries je ON th.id = je.transaction_id
67
JOIN public.chart_of_accounts sp ON je.account_id = sp.id AND sp.name ILIKE '%Salary Payable%'
68
WHERE th.transaction_source_type = 13
69
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
70
71
UNION ALL
72
73
-- COA ACCOUNTS (other than above)
74
SELECT
75
th.id,
76
th.transaction_date,
77
th.document_id as entity_id,
78
coa.name::text as entity_name,
79
th.description,
80
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) as debit,
81
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) as credit,
82
'COA' as entity_type
83
FROM public.transaction_headers th
84
JOIN public.journal_entries je ON th.id = je.transaction_id
85
JOIN public.chart_of_accounts coa ON th.document_id = coa.id AND je.account_id = th.document_id
86
WHERE th.transaction_source_type = 13
87
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
88
89
ORDER BY entity_name;
90
91
END;
92
$function$
|
|||||
| Function | validate_organization_accounts | Match | ||||||
| Function | assign_user_default_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.assign_user_default_permissions(p_user_id uuid, p_role_id integer, p_organization_id uuid, p_created_by uuid DEFAULT NULL::uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_permission_count INT;
7
v_existing_organization_user INT;
8
v_existing_user_role INT;
9
BEGIN
10
RAISE NOTICE 'Assigning default permissions for User=% to Role=%', p_user_id, p_role_id;
11
12
-- 1️⃣ Ensure user exists in organization_users
13
SELECT COUNT(*) INTO v_existing_organization_user
14
FROM organization_users ou
15
WHERE ou.user_id = p_user_id
16
AND ou.organization_id = p_organization_id
17
AND ou.is_deleted = FALSE;
18
19
IF v_existing_organization_user = 0 THEN
20
INSERT INTO organization_users (
21
id, user_id, effective_start_date, created_on_utc,
22
created_by, organization_id, is_deleted
23
)
24
VALUES (
25
uuid_generate_v4(),
26
p_user_id,
27
NOW(),
28
NOW(),
29
COALESCE(p_created_by, p_user_id),
30
p_organization_id,
31
FALSE
32
);
33
RAISE NOTICE '✅ Added user to organization_users';
34
END IF;
35
36
-- 2️⃣ Ensure user has matching role in user_roles
37
SELECT COUNT(*) INTO v_existing_user_role
38
FROM user_roles ur
39
WHERE ur.user_id = p_user_id
40
AND ur.role_id = p_role_id
41
AND ur.organization_id = p_organization_id
42
AND ur.is_deleted = FALSE;
43
44
IF v_existing_user_role = 0 THEN
45
INSERT INTO user_roles (
46
user_id, role_id, created_on_utc, created_by,
47
is_deleted, start_date, organization_id
48
)
49
VALUES (
50
p_user_id,
51
p_role_id,
52
NOW(),
53
COALESCE(p_created_by, p_user_id),
54
FALSE,
55
NOW(),
56
p_organization_id
57
);
58
RAISE NOTICE '✅ Added role assignment for user';
59
END IF;
60
61
-- 3️⃣ Assign permissions from role_permissions (NOT template mappings)
62
INSERT INTO user_permissions (
63
user_id,
64
permission_id,
65
organization_id,
66
created_on_utc,
67
created_by,
68
is_deleted
69
)
70
SELECT
71
p_user_id,
72
rp.permission_id,
73
p_organization_id,
74
NOW(),
75
COALESCE(p_created_by, p_user_id),
76
FALSE
77
FROM role_permissions rp
78
WHERE rp.role_id = p_role_id
79
AND NOT EXISTS (
80
SELECT 1
81
FROM user_permissions up
82
WHERE up.user_id = p_user_id
83
AND up.permission_id = rp.permission_id
84
AND up.organization_id = p_organization_id
85
AND up.is_deleted = FALSE
86
);
87
88
GET DIAGNOSTICS v_permission_count = ROW_COUNT;
89
90
RAISE NOTICE '✅ Assigned % permissions to User=% for Role=%',
91
v_permission_count, p_user_id, p_role_id;
92
93
EXCEPTION
94
WHEN OTHERS THEN
95
RAISE WARNING '⚠️ Error assigning permissions for user %: %', p_user_id, SQLERRM;
96
END;
97
$function$
|
|||||
| Function | upsert_role_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.upsert_role_permissions(p_role_id integer, p_permission_ids uuid[], p_user_id uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
-- 1️⃣ Reactivate existing soft-deleted permissions that are now in the list
7
UPDATE public.role_permissions
8
SET is_deleted = FALSE,
9
deleted_on_utc = NULL,
10
modified_on_utc = NOW(),
11
modified_by = p_user_id
12
WHERE role_id = p_role_id
13
AND permission_id = ANY(p_permission_ids)
14
AND is_deleted = TRUE;
15
16
-- 2️⃣ Insert missing (new) permissions
17
INSERT INTO public.role_permissions (
18
role_id,
19
permission_id,
20
created_on_utc,
21
created_by,
22
is_deleted
23
)
24
SELECT
25
p_role_id,
26
pid,
27
NOW(),
28
p_user_id,
29
FALSE
30
FROM UNNEST(p_permission_ids) AS pid
31
WHERE NOT EXISTS (
32
SELECT 1
33
FROM public.role_permissions rp
34
WHERE rp.role_id = p_role_id
35
AND rp.permission_id = pid
36
);
37
38
-- 3️⃣ Soft delete permissions not in the provided list
39
UPDATE public.role_permissions
40
SET is_deleted = TRUE,
41
deleted_on_utc = NOW(),
42
modified_on_utc = NOW(),
43
modified_by = p_user_id
44
WHERE role_id = p_role_id
45
AND permission_id NOT IN (SELECT unnest(p_permission_ids))
46
AND is_deleted = FALSE;
47
END;
48
$function$
|
|||||
| Function | get_outstanding_invoices_by_customer_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_outstanding_invoices_by_customer_id(p_company_id uuid, p_customer_id uuid)
2
RETURNS TABLE(customer_name text, coa_name text, amount numeric, due_date date, aging_bucket text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
c.name::text AS customer_name,
9
coa.name AS coa_name,
10
je.amount,
11
(th.transaction_date + INTERVAL '30 days')::date AS due_date,
12
CASE
13
WHEN (th.transaction_date + INTERVAL '30 days') <= CURRENT_DATE
14
THEN 'Current'
15
WHEN (th.transaction_date + INTERVAL '30 days') BETWEEN CURRENT_DATE - INTERVAL '30 days' AND CURRENT_DATE
16
THEN '1-30 Days Past Due'
17
WHEN (th.transaction_date + INTERVAL '60 days') BETWEEN CURRENT_DATE - INTERVAL '60 days' AND CURRENT_DATE - INTERVAL '31 days'
18
THEN '31-60 Days Past Due'
19
ELSE 'Over 60 Days Past Due'
20
END AS aging_bucket
21
FROM
22
public.journal_entries je
23
JOIN
24
public.transaction_headers th ON je.transaction_id = th.id
25
JOIN
26
public.chart_of_accounts coa ON je.account_id = coa.id
27
JOIN
28
public.customers c ON th.customer_id = c.id
29
WHERE
30
coa.account_type_id = (SELECT id FROM public.account_types WHERE name = 'Accounts Receivable')
31
AND je.is_deleted = false
32
AND th.company_id = p_company_id
33
AND th.customer_id = p_customer_id
34
AND (th.transaction_date + INTERVAL '30 days') <= CURRENT_DATE
35
ORDER BY
36
(th.transaction_date + INTERVAL '30 days')::date;
37
END;
38
$function$
|
|||||
| Function | get_outstanding_invoices_by_customer | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_outstanding_invoices_by_customer(p_company_id uuid, p_customer_id uuid, p_start_date date DEFAULT NULL::date, p_end_date date DEFAULT NULL::date)
2
RETURNS TABLE(customer_name text, coa_name text, amount numeric, due_date date, aging_bucket text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date := COALESCE(p_start_date, CURRENT_DATE - INTERVAL '365 days');
7
v_end_date date := COALESCE(p_end_date, CURRENT_DATE);
8
BEGIN
9
RETURN QUERY
10
SELECT
11
c.name::text AS customer_name,
12
coa.name AS coa_name,
13
je.amount,
14
(th.transaction_date + INTERVAL '30 days')::date AS due_date,
15
16
/* Aging bucket calculation */
17
CASE
18
WHEN (th.transaction_date + INTERVAL '30 days') <= CURRENT_DATE
19
THEN 'Current'
20
WHEN (th.transaction_date + INTERVAL '30 days') BETWEEN CURRENT_DATE - INTERVAL '30 days' AND CURRENT_DATE
21
THEN '1-30 Days Past Due'
22
WHEN (th.transaction_date + INTERVAL '60 days') BETWEEN CURRENT_DATE - INTERVAL '60 days' AND CURRENT_DATE - INTERVAL '31 days'
23
THEN '31-60 Days Past Due'
24
ELSE 'Over 60 Days Past Due'
25
END AS aging_bucket
26
27
FROM public.journal_entries je
28
JOIN public.transaction_headers th ON je.transaction_id = th.id
29
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
30
JOIN public.customers c ON th.customer_id = c.id
31
32
WHERE
33
th.company_id = p_company_id
34
AND th.customer_id = p_customer_id
35
AND je.is_deleted = false
36
AND coa.account_type_id = (
37
SELECT id FROM public.account_types WHERE name = 'Accounts Receivable'
38
)
39
40
/* Overdue invoices only (due_date <= today) */
41
AND (th.transaction_date + INTERVAL '30 days')::date <= CURRENT_DATE
42
43
/* Date range filtering */
44
AND th.transaction_date::date BETWEEN v_start_date AND v_end_date
45
46
ORDER BY (th.transaction_date + INTERVAL '30 days')::date;
47
END;
48
$function$
|
|||||
| Function | postgres_fdw_handler | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_handler()
2
RETURNS fdw_handler
3
LANGUAGE c
4
STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_handler$function$
|
|||||
| Function | postgres_fdw_validator | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_validator(text[], oid)
2
RETURNS void
3
LANGUAGE c
4
STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_validator$function$
|
|||||
| Function | postgres_fdw_disconnect | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_disconnect(text)
2
RETURNS boolean
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_disconnect$function$
|
|||||
| Function | postgres_fdw_disconnect_all | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_disconnect_all()
2
RETURNS boolean
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_disconnect_all$function$
|
|||||
| Function | postgres_fdw_get_connections | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_get_connections(check_conn boolean DEFAULT false, OUT server_name text, OUT user_name text, OUT valid boolean, OUT used_in_xact boolean, OUT closed boolean, OUT remote_backend_pid integer)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_get_connections_1_2$function$
|
|||||
| Function | notify_user_permission_change | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.notify_user_permission_change()
2
RETURNS trigger
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_user_id uuid;
7
v_org_id uuid;
8
payload json;
9
BEGIN
10
-- Handle DELETE separately (NEW is null)
11
IF TG_OP = 'DELETE' THEN
12
v_user_id := OLD.user_id;
13
v_org_id := OLD.organization_id;
14
ELSE
15
v_user_id := NEW.user_id;
16
v_org_id := NEW.organization_id;
17
END IF;
18
19
payload := json_build_object(
20
'entity', 'user_permissions',
21
'operation', TG_OP,
22
'userId', v_user_id,
23
'organizationId', v_org_id,
24
'occurredAt', now()
25
);
26
27
PERFORM pg_notify('permission_user_changed', payload::text);
28
29
RETURN NULL;
30
END;
31
$function$
|
|||||
| Function | get_system_user_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_system_user_id()
2
RETURNS uuid
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_user_id uuid;
7
BEGIN
8
SELECT id
9
INTO v_user_id
10
FROM public.users
11
WHERE first_name = 'System'
12
AND is_deleted = false
13
LIMIT 1;
14
15
16
RETURN COALESCE(
17
v_user_id,
18
'dd4f94f2-0f79-4748-b94b-bf935e3944c7'::uuid
19
);
20
END;
21
$function$
|
|||||
| Function | upsert_user_roles | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.upsert_user_roles(p_user_id uuid, p_role_ids integer[], p_organization_id uuid, p_created_by uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
INSERT INTO public.user_roles
7
(
8
user_id,
9
role_id,
10
organization_id,
11
created_on_utc,
12
created_by,
13
is_deleted,
14
start_date,
15
end_date
16
)
17
SELECT
18
p_user_id,
19
role_id,
20
p_organization_id,
21
NOW(),
22
p_created_by,
23
false,
24
NOW(),
25
NOW() + INTERVAL '1 year'
26
FROM UNNEST(p_role_ids) AS role_id
27
ON CONFLICT (user_id, role_id, organization_id)
28
DO UPDATE
29
SET
30
is_deleted = false,
31
start_date = NOW(),
32
end_date = NOW() + INTERVAL '1 year',
33
modified_on_utc = NOW(),
34
modified_by = p_created_by;
35
END;
36
$function$
|
|||||
| Function | get_bank_statements | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bank_statements(p_organization_id uuid, p_finyear_id integer, p_date_from timestamp without time zone DEFAULT NULL::timestamp without time zone, p_date_to timestamp without time zone DEFAULT NULL::timestamp without time zone)
2
RETURNS TABLE(id integer, organization_id uuid, bank_id uuid, bank_name text, txn_date timestamp without time zone, cheque_number character varying, description text, value_date timestamp without time zone, branch_code character varying, debit_amount numeric, credit_amount numeric, balance numeric, has_reconciled boolean, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, deleted_on_utc timestamp without time zone, is_deleted boolean, rec_status_id integer, rec_status text, rec_confidence_score numeric, rec_strategy_name text, rec_matched_party_name text, rec_party_id uuid, rec_party_type text, rec_reviewed_by uuid, rec_reviewed_on_utc timestamp without time zone, rec_matched_amount numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
bs.id,
9
bs.organization_id,
10
bs.bank_id,
11
coa.name AS bank_name,
12
bs.txn_date,
13
bs.cheque_number,
14
bs.description,
15
bs.value_date,
16
bs.branch_code,
17
bs.debit_amount,
18
bs.credit_amount,
19
bs.balance,
20
bs.has_reconciled,
21
bs.created_by,
22
bs.created_on_utc,
23
bs.modified_by,
24
bs.modified_on_utc,
25
bs.deleted_on_utc,
26
bs.is_deleted,
27
28
/* -----------------------------------------
29
Final reconciliation status
30
----------------------------------------- */
31
COALESCE(br.reconciliation_status_id, 0) AS rec_status_id,
32
33
CASE
34
WHEN br.reconciliation_status_id IS NOT NULL THEN
35
COALESCE(rs.status::text, 'unmatched')
36
WHEN brs.confidence_score >= 70.99 THEN
37
'matched'
38
WHEN brs.status = 'pending' THEN
39
'pending'
40
ELSE
41
'unmatched'
42
END AS rec_status,
43
44
/* -----------------------------------------
45
Latest reconciliation suggestion
46
----------------------------------------- */
47
COALESCE(brs.confidence_score, 0) AS rec_confidence_score,
48
COALESCE(brs.strategy_name::text, 'No Match Found') AS rec_strategy_name,
49
brs.matched_party_name::text AS rec_matched_party_name,
50
brs.party_id AS rec_party_id,
51
brs.party_type::text AS rec_party_type,
52
brs.reviewed_by AS rec_reviewed_by,
53
brs.reviewed_on_utc AS rec_reviewed_on_utc,
54
COALESCE(brs.matched_amount, 0) AS rec_matched_amount
55
56
FROM public.bank_statements bs
57
58
/* Bank account (COA) */
59
INNER JOIN public.chart_of_accounts coa
60
ON coa.id = bs.bank_id
61
AND coa.account_type_id = 9
62
AND coa.is_deleted = FALSE
63
64
/* Financial year scope */
65
INNER JOIN public.finance_year fy
66
ON fy.id = p_finyear_id
67
68
/* Latest AI / manual staging record */
69
LEFT JOIN LATERAL (
70
SELECT brs_inner.*
71
FROM public.bank_reconciliation_stagings brs_inner
72
WHERE brs_inner.organization_id = bs.organization_id
73
AND brs_inner.bank_statement_id = bs.id
74
AND brs_inner.is_deleted = FALSE
75
ORDER BY brs_inner.created_on_utc DESC
76
LIMIT 1
77
) brs ON TRUE
78
79
/* Finalized reconciliation */
80
LEFT JOIN public.bank_reconciliations br
81
ON br.bank_statement_id = bs.id
82
AND br.organization_id = bs.organization_id
83
84
/* Reconciliation status lookup */
85
LEFT JOIN public.reconciliation_statuses rs
86
ON rs.id = br.reconciliation_status_id
87
88
WHERE bs.organization_id = p_organization_id
89
AND bs.txn_date BETWEEN fy.start_date AND fy.end_date
90
AND (p_date_from IS NULL OR bs.txn_date >= p_date_from)
91
AND (p_date_to IS NULL OR bs.txn_date <= p_date_to)
92
AND bs.is_deleted = FALSE
93
94
ORDER BY bs.txn_date, bs.id;
95
END;
96
$function$
|
|||||
| Function | upsert_user_permissions_from_user_roles | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.upsert_user_permissions_from_user_roles(p_user_id uuid, p_organization_id uuid, p_role_ids integer[], p_created_by uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
/*
7
* STEP 1: Insert or revive permissions coming from roles
8
*/
9
INSERT INTO public.user_permissions (
10
user_id,
11
organization_id,
12
permission_id,
13
created_on_utc,
14
created_by,
15
is_deleted,
16
is_explicit
17
)
18
SELECT DISTINCT
19
p_user_id,
20
p_organization_id,
21
rp.permission_id,
22
now(),
23
p_created_by,
24
false,
25
false -- role-derived
26
FROM public.role_permissions rp
27
WHERE rp.role_id = ANY (p_role_ids)
28
AND rp.is_deleted = false
29
30
ON CONFLICT (user_id, organization_id, permission_id)
31
DO UPDATE
32
SET
33
is_deleted = false,
34
deleted_on_utc = NULL,
35
modified_on_utc = now(),
36
modified_by = p_created_by,
37
is_explicit = false;
38
39
/*
40
* STEP 2: Soft-delete role-derived permissions
41
* that are no longer present in current roles
42
*/
43
UPDATE public.user_permissions up
44
SET
45
is_deleted = true,
46
deleted_on_utc = now(),
47
modified_on_utc = now(),
48
modified_by = p_created_by
49
WHERE up.user_id = p_user_id
50
AND up.organization_id = p_organization_id
51
AND up.is_explicit = false
52
AND up.is_deleted = false
53
AND up.permission_id NOT IN (
54
SELECT DISTINCT rp.permission_id
55
FROM public.role_permissions rp
56
WHERE rp.role_id = ANY (p_role_ids)
57
AND rp.is_deleted = false
58
);
59
END;
60
$function$
|
|||||
| Function | sync_user_permissions_from_roles | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.sync_user_permissions_from_roles(p_user_id uuid DEFAULT NULL::uuid, p_organization_id uuid DEFAULT NULL::uuid, p_triggered_by text DEFAULT 'manual'::text, p_notes text DEFAULT NULL::text)
2
RETURNS uuid
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
-- Sync run tracking
7
v_sync_run_id uuid := gen_random_uuid();
8
9
-- Loop variables
10
v_user_id uuid;
11
v_org_id uuid;
12
v_role_id int;
13
v_permission_id uuid;
14
15
-- For UPSERT result
16
v_user_permission_id int;
17
BEGIN
18
/*
19
============================================================
20
START SYNC RUN
21
============================================================
22
*/
23
INSERT INTO public.user_permission_sync_runs (
24
id,
25
started_on_utc,
26
triggered_by,
27
notes
28
)
29
VALUES (
30
v_sync_run_id,
31
NOW(),
32
p_triggered_by,
33
p_notes
34
);
35
36
/*
37
============================================================
38
MAIN SYNC LOOP
39
- Filtered by optional user / organization
40
============================================================
41
*/
42
FOR v_user_id, v_org_id IN
43
SELECT DISTINCT
44
ur.user_id,
45
ur.organization_id
46
FROM public.user_roles ur
47
WHERE ur.is_deleted = false
48
AND (p_user_id IS NULL OR ur.user_id = p_user_id)
49
AND (p_organization_id IS NULL OR ur.organization_id = p_organization_id)
50
LOOP
51
52
/*
53
--------------------------------------------------------
54
LOOP ROLES FOR USER + ORGANIZATION
55
--------------------------------------------------------
56
*/
57
FOR v_role_id IN
58
SELECT ur.role_id
59
FROM public.user_roles ur
60
WHERE ur.user_id = v_user_id
61
AND ur.organization_id = v_org_id
62
AND ur.is_deleted = false
63
LOOP
64
65
/*
66
----------------------------------------------------
67
LOOP PERMISSIONS FOR ROLE
68
----------------------------------------------------
69
*/
70
FOR v_permission_id IN
71
SELECT rp.permission_id
72
FROM public.role_permissions rp
73
WHERE rp.role_id = v_role_id
74
AND rp.is_deleted = false
75
LOOP
76
77
/*
78
------------------------------------------------
79
UPSERT USER PERMISSION (SAFE)
80
------------------------------------------------
81
*/
82
INSERT INTO public.user_permissions (
83
user_id,
84
permission_id,
85
organization_id,
86
created_on_utc,
87
created_by,
88
is_deleted,
89
modified_on_utc,
90
modified_by,
91
deleted_on_utc
92
)
93
VALUES (
94
v_user_id,
95
v_permission_id,
96
v_org_id,
97
NOW(),
98
v_user_id,
99
false,
100
NULL,
101
NULL,
102
NULL
103
)
104
ON CONFLICT (user_id, organization_id, permission_id)
105
DO UPDATE
106
SET
107
is_deleted = false,
108
deleted_on_utc = NULL,
109
modified_on_utc = NOW(),
110
modified_by = v_user_id
111
WHERE public.user_permissions.is_deleted = true
112
RETURNING id
113
INTO v_user_permission_id;
114
115
/*
116
------------------------------------------------
117
BACKUP / DELTA SNAPSHOT
118
- Only when INSERT or REVIVE happened
119
------------------------------------------------
120
*/
121
IF FOUND THEN
122
INSERT INTO public.user_permission_sync_deltas (
123
id,
124
sync_run_id,
125
user_permission_id,
126
user_id,
127
organization_id,
128
permission_id,
129
action,
130
created_on_utc
131
)
132
VALUES (
133
gen_random_uuid(),
134
v_sync_run_id,
135
v_user_permission_id,
136
v_user_id,
137
v_org_id,
138
v_permission_id,
139
'UPSERT',
140
NOW()
141
);
142
END IF;
143
144
END LOOP; -- permissions
145
END LOOP; -- roles
146
END LOOP; -- users + organizations
147
148
/*
149
============================================================
150
COMPLETE SYNC RUN
151
============================================================
152
*/
153
UPDATE public.user_permission_sync_runs
154
SET completed_on_utc = NOW()
155
WHERE id = v_sync_run_id;
156
157
RETURN v_sync_run_id;
158
END;
159
$function$
|
|||||
| Function | insert_multiple_transactions_and_journal_entries | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.insert_multiple_transactions_and_journal_entries(p_transactions_data jsonb)
1
CREATE OR REPLACE FUNCTION public.insert_multiple_transactions_and_journal_entries(p_transactions_data jsonb)
2
RETURNS TABLE(transaction_id bigint, document_id uuid, reference text)
2
RETURNS TABLE(transaction_id bigint, document_id uuid)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
6
DECLARE
5
DECLARE
7
v_transaction_id BIGINT;
6
v_transaction_id BIGINT;
8
v_transaction_record JSONB;
7
v_transaction_record JSONB;
9
v_journal_entry JSONB;
8
v_journal_entry JSONB;
10
v_narration_id BIGINT;
11
v_reference TEXT;
12
v_is_reversed BOOLEAN;
13
BEGIN
9
BEGIN
14
-- Loop through each transaction in the input array
10
-- Loop through each transaction in the input array
15
FOR v_transaction_record IN SELECT * FROM jsonb_array_elements(p_transactions_data)
11
FOR v_transaction_record IN SELECT * FROM jsonb_array_elements(p_transactions_data)
16
LOOP
12
LOOP
17
-- ✅ Extract is_reversed (default false if null)
18
v_is_reversed := COALESCE((v_transaction_record->>'is_reversal')::boolean, false);
19
20
-- Insert into transaction_headers
13
-- Insert into transaction_headers
21
INSERT INTO public.transaction_headers (
14
INSERT INTO public.transaction_headers (
22
company_id,
15
company_id,
23
customer_id,
16
customer_id,
24
vendor_id,
17
vendor_id,
25
employee_id,
18
employee_id,
26
transaction_date,
19
transaction_date,
27
transaction_source_type,
20
transaction_source_type,
28
status_id,
21
status_id,
29
document_id,
22
document_id,
30
document_number,
23
document_number,
31
description,
24
description,
32
created_by,
25
created_by,
33
created_on_utc,
26
created_on_utc
34
is_reversed -- ✅ NEW COLUMN
35
)
27
)
36
VALUES(
28
VALUES(
37
(v_transaction_record->>'company_id')::uuid,
29
(v_transaction_record->>'company_id')::uuid,
38
NULLIF((v_transaction_record->>'customer_id')::uuid, '00000000-0000-0000-0000-000000000000'),
30
NULLIF((v_transaction_record->>'customer_id')::uuid, '00000000-0000-0000-0000-000000000000'),
39
NULLIF((v_transaction_record->>'vendor_id')::uuid, '00000000-0000-0000-0000-000000000000'),
31
NULLIF((v_transaction_record->>'vendor_id')::uuid, '00000000-0000-0000-0000-000000000000'),
40
NULLIF((v_transaction_record->>'employee_id')::uuid, '00000000-0000-0000-0000-000000000000'),
32
NULLIF((v_transaction_record->>'employee_id')::uuid, '00000000-0000-0000-0000-000000000000'),
41
(v_transaction_record->>'transaction_date')::date,
33
(v_transaction_record->>'transaction_date')::date,
42
(v_transaction_record->>'transaction_source_type')::integer,
34
(v_transaction_record->>'transaction_source_type')::integer,
43
(v_transaction_record->>'status_id')::integer,
35
(v_transaction_record->>'status_id')::integer,
44
(v_transaction_record->>'document_id')::uuid,
36
(v_transaction_record->>'document_id')::uuid,
45
v_transaction_record->>'document_number',
37
v_transaction_record->>'document_number',
46
v_transaction_record->>'description',
38
v_transaction_record->>'description',
47
(v_transaction_record->>'user_id')::uuid,
39
(v_transaction_record->>'user_id')::uuid,
48
CURRENT_TIMESTAMP,
40
CURRENT_TIMESTAMP
49
v_is_reversed -- ✅ INSERT VALUE
50
)
41
)
51
RETURNING id INTO v_transaction_id;
42
RETURNING id INTO v_transaction_id;
52
53
-------------------------------------------------------------------
54
-- 2️⃣ If reversal → mark original transaction as reversed
55
-------------------------------------------------------------------
56
IF v_is_reversed = true THEN
57
43
58
UPDATE public.transaction_headers th
59
SET
60
is_reversed = true,
61
modified_on_utc = CURRENT_TIMESTAMP
62
WHERE th.company_id = (v_transaction_record->>'company_id')::uuid
63
AND th.document_id = (v_transaction_record->>'document_id')::uuid
64
AND th.transaction_source_type = (v_transaction_record->>'transaction_source_type')::integer
65
AND th.id <> v_transaction_id
66
AND th.is_reversed = false;
67
44
68
END IF;
69
45
70
-- ✅ Insert into payment_references if reference exists and not empty
71
v_reference := NULLIF(TRIM(v_transaction_record->>'reference'), '');
72
IF v_reference IS NOT NULL THEN
73
INSERT INTO public.payment_references (
74
transaction_id,
75
reference
76
)
77
VALUES (
78
v_transaction_id,
79
v_reference
80
);
81
END IF;
82
83
-- Loop through each journal entry associated with the transaction
46
-- Loop through each journal entry associated with the transaction
84
FOR v_journal_entry IN SELECT * FROM jsonb_array_elements(v_transaction_record->'journal_entries')
47
FOR v_journal_entry IN SELECT * FROM jsonb_array_elements(v_transaction_record->'journal_entries')
85
LOOP
48
LOOP
86
-- Insert narration first
87
INSERT INTO public.journal_narrations (
88
transaction_date,
89
description_template_id,
90
dynamic_data,
91
created_by,
92
created_on_utc
93
)
94
VALUES(
95
(v_journal_entry->>'transaction_date')::timestamp,
96
(v_journal_entry->>'description_template_id')::integer,
97
COALESCE((v_journal_entry->>'dynamic_data'), ''),
98
(v_transaction_record->>'user_id')::uuid,
99
CURRENT_TIMESTAMP
100
)
101
RETURNING id INTO v_narration_id;
102
103
-- Insert journal entry
104
INSERT INTO public.journal_entries (
49
INSERT INTO public.journal_entries (
105
transaction_id,
50
transaction_id,
106
account_id,
51
account_id,
107
transaction_date,
52
transaction_date,
108
amount,
53
amount,
109
entry_type,
54
entry_type,
110
entry_source_id,
55
description_template_id,
111
journal_narration_id
56
dynamic_data,
57
entry_source_id
112
)
58
)
113
VALUES(
59
VALUES(
114
v_transaction_id,
60
v_transaction_id,
115
(v_journal_entry->>'account_id')::uuid,
61
(v_journal_entry->>'account_id')::uuid,
116
(v_journal_entry->>'transaction_date')::date,
62
(v_journal_entry->>'transaction_date')::date,
117
(v_journal_entry->>'amount')::numeric,
63
(v_journal_entry->>'amount')::numeric,
118
(v_journal_entry->>'entry_type')::char,
64
(v_journal_entry->>'entry_type')::char,
119
(v_journal_entry->>'entry_source_id')::integer,
65
(v_journal_entry->>'description_template_id')::integer,
120
v_narration_id
66
(v_journal_entry->>'dynamic_data')::jsonb,
67
(v_journal_entry->>'entry_source_id')::integer
121
);
68
);
122
END LOOP;
69
END LOOP;
123
70
124
-- Return transaction ID, document ID, and reference for this transaction
71
-- Return transaction ID and document ID for this transaction
125
RETURN QUERY SELECT v_transaction_id, (v_transaction_record->>'document_id')::uuid, v_reference;
72
RETURN QUERY SELECT v_transaction_id, (v_transaction_record->>'document_id')::uuid;
126
END LOOP;
73
END LOOP;
127
128
EXCEPTION
74
EXCEPTION
75
-- Catch any error and raise an exception
129
WHEN OTHERS THEN
76
WHEN OTHERS THEN
130
RAISE EXCEPTION 'Error occurred: %', SQLERRM;
77
RAISE EXCEPTION 'Error occurred: %', SQLERRM;
131
END;
78
END;
132
$function$
79
$function$
|
|||||
| Function | get_dashboard_totals_with_previous_year_comparison | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_dashboard_totals_with_previous_year_comparison(p_company_id uuid, p_finance_year_id integer)
1
CREATE OR REPLACE FUNCTION public.get_dashboard_totals_with_previous_year_comparison(p_company_id uuid, p_finance_year_id integer)
2
RETURNS TABLE(income_total numeric, expense_total numeric, pending_dues_total numeric, pending_payments_total numeric, prev_income_total numeric, prev_expense_total numeric, prev_pending_dues_total numeric, prev_pending_payments_total numeric)
2
RETURNS TABLE(income_total numeric, expense_total numeric, pending_dues_total numeric, pending_payments_total numeric, prev_income_total numeric, prev_expense_total numeric, prev_pending_dues_total numeric, prev_pending_payments_total numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
v_organization_id UUID;
6
v_organization_id UUID;
7
7
v_financial_year_start DATE;
8
v_start_date DATE;
8
v_financial_year_end DATE;
9
v_end_date DATE;
9
v_prev_financial_year_start DATE;
10
10
v_prev_financial_year_end DATE;
11
v_prev_start DATE;
11
v_finance_year INTEGER;
12
v_prev_end DATE;
12
v_today DATE := CURRENT_DATE;
13
BEGIN
13
BEGIN
14
-- Get org
14
-- Fetch organization ID
15
SELECT organization_id INTO v_organization_id
15
SELECT organization_id INTO v_organization_id
16
FROM public.companies
16
FROM public.companies
17
WHERE id = p_company_id;
17
WHERE id = p_company_id;
18
18
19
IF NOT FOUND THEN
19
IF NOT FOUND THEN
20
RAISE EXCEPTION 'Invalid company ID: %', p_company_id;
20
RAISE EXCEPTION 'Invalid company ID: %', p_company_id;
21
END IF;
22
23
-- Get financial year integer from 'YYYY-YY'
24
SELECT LEFT(year, 4)::INTEGER INTO v_finance_year
25
FROM public.finance_year
26
WHERE id = p_finance_year_id
27
LIMIT 1;
28
29
IF NOT FOUND THEN
30
RAISE EXCEPTION 'Invalid finance year ID: %', p_finance_year_id;
31
END IF;
32
33
-- Define financial year range
34
v_financial_year_start := MAKE_DATE(v_finance_year, 4, 1);
35
v_financial_year_end := MAKE_DATE(v_finance_year + 1, 3, 31);
36
37
-- Define previous financial year range
38
v_prev_financial_year_start := MAKE_DATE(v_finance_year - 1, 4, 1);
39
v_prev_financial_year_end := MAKE_DATE(v_finance_year, 3, 31);
40
41
-- Adjust financial year ends if the year is ongoing
42
IF v_today < v_financial_year_end THEN
43
v_financial_year_end := v_today;
21
END IF;
44
END IF;
22
45
23
/* ---------------------------------------------------------
46
IF v_today < v_prev_financial_year_end THEN
24
Rolling 12 Months
47
v_prev_financial_year_end := v_today - INTERVAL '1 year';
25
--------------------------------------------------------- */
48
END IF;
26
v_start_date := (date_trunc('month', CURRENT_DATE) - interval '11 months')::date;
27
v_end_date := (date_trunc('month', CURRENT_DATE) + interval '1 month')::date;
28
49
29
/* ---------------------------------------------------------
50
-- Align both end dates to same day/month by limiting to shorter period
30
Previous Rolling 12 Months
51
v_financial_year_end := LEAST(v_financial_year_end, v_prev_financial_year_end + INTERVAL '1 year');
31
--------------------------------------------------------- */
52
v_prev_financial_year_end := v_financial_year_end - INTERVAL '1 year';
32
v_prev_start := v_start_date - interval '1 year';
33
v_prev_end := v_end_date - interval '1 year';
34
53
54
-- Return calculated data
35
RETURN QUERY
55
RETURN QUERY
36
SELECT
56
SELECT
37
57
-- Current Year Income
38
/* ================= CURRENT ================= */
39
40
-- Income
41
ROUND(COALESCE((
58
ROUND(COALESCE((
42
SELECT SUM(je.amount)
59
SELECT SUM(je.amount)
43
FROM journal_entries je
60
FROM journal_entries je
44
JOIN chart_of_accounts ca ON je.account_id = ca.id
61
JOIN chart_of_accounts ca ON je.account_id = ca.id
45
JOIN account_types at ON ca.account_type_id = at.id
62
JOIN account_types at ON ca.account_type_id = at.id
46
JOIN account_categories ac ON at.account_category_id = ac.id
63
JOIN account_categories ac ON at.account_category_id = ac.id
47
JOIN transaction_headers tr ON je.transaction_id = tr.id
64
JOIN transaction_headers tr ON je.transaction_id = tr.id
48
WHERE ac.id = 4
65
WHERE ac.id = 4
49
AND tr.company_id = p_company_id
66
AND tr.company_id = p_company_id
50
AND ca.organization_id = v_organization_id
67
AND ca.organization_id = v_organization_id
51
AND je.transaction_date >= v_start_date
68
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
52
AND je.transaction_date < v_end_date
69
AND je.is_deleted = FALSE
53
AND COALESCE(je.is_deleted, FALSE) = FALSE
54
AND COALESCE(tr.is_reversed, FALSE) = FALSE
55
), 0), 2),
70
), 0), 2),
56
71
57
-- Expense
72
-- Current Year Expense
58
ROUND(COALESCE((
73
ROUND(COALESCE((
59
SELECT SUM(je.amount)
74
SELECT SUM(je.amount)
60
FROM journal_entries je
75
FROM journal_entries je
61
JOIN chart_of_accounts ca ON je.account_id = ca.id
76
JOIN chart_of_accounts ca ON je.account_id = ca.id
62
JOIN account_types at ON ca.account_type_id = at.id
77
JOIN account_types at ON ca.account_type_id = at.id
63
JOIN account_categories ac ON at.account_category_id = ac.id
78
JOIN account_categories ac ON at.account_category_id = ac.id
64
JOIN transaction_headers tr ON je.transaction_id = tr.id
79
JOIN transaction_headers tr ON je.transaction_id = tr.id
65
WHERE ac.id = 5
80
WHERE ac.id = 5
81
AND tr.transaction_source_type = 2
66
AND tr.company_id = p_company_id
82
AND tr.company_id = p_company_id
67
AND ca.organization_id = v_organization_id
83
AND ca.organization_id = v_organization_id
68
AND je.transaction_date >= v_start_date
84
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
69
AND je.transaction_date < v_end_date
85
AND je.is_deleted = FALSE
70
AND COALESCE(je.is_deleted, FALSE) = FALSE
71
AND COALESCE(tr.is_reversed, FALSE) = FALSE
72
), 0), 2),
86
), 0), 2),
73
87
74
-- Pending Dues
88
-- Pending Dues
75
ROUND(COALESCE((
89
ROUND(COALESCE((
76
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
90
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
77
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
91
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
78
FROM journal_entries je
92
FROM journal_entries je
79
JOIN chart_of_accounts ca ON je.account_id = ca.id
93
JOIN chart_of_accounts ca ON je.account_id = ca.id
80
JOIN account_types at ON ca.account_type_id = at.id
94
JOIN account_types at ON ca.account_type_id = at.id
81
JOIN transaction_headers tr ON je.transaction_id = tr.id
95
JOIN transaction_headers tr ON je.transaction_id = tr.id
82
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
96
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
83
AND tr.company_id = p_company_id
97
AND tr.company_id = p_company_id
84
AND ca.organization_id = v_organization_id
98
AND ca.organization_id = v_organization_id
85
AND je.transaction_date >= v_start_date
99
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
86
AND je.transaction_date < v_end_date
100
AND je.is_deleted = FALSE
87
AND COALESCE(je.is_deleted, FALSE) = FALSE
88
AND COALESCE(tr.is_reversed, FALSE) = FALSE
89
), 0), 2),
101
), 0), 2),
90
102
91
-- Pending Payments
103
-- Pending Payments
92
ROUND(COALESCE((
104
ROUND(COALESCE((
93
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
105
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
94
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
106
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
95
FROM journal_entries je
107
FROM journal_entries je
96
JOIN chart_of_accounts ca ON je.account_id = ca.id
108
JOIN chart_of_accounts ca ON je.account_id = ca.id
97
JOIN account_types at ON ca.account_type_id = at.id
109
JOIN account_types at ON ca.account_type_id = at.id
98
JOIN transaction_headers tr ON je.transaction_id = tr.id
110
JOIN transaction_headers tr ON je.transaction_id = tr.id
99
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
111
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
100
AND tr.company_id = p_company_id
112
AND tr.company_id = p_company_id
101
AND ca.organization_id = v_organization_id
113
AND ca.organization_id = v_organization_id
102
AND je.transaction_date >= v_start_date
114
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
103
AND je.transaction_date < v_end_date
115
AND je.is_deleted = FALSE
104
AND COALESCE(je.is_deleted, FALSE) = FALSE
105
AND COALESCE(tr.is_reversed, FALSE) = FALSE
106
), 0), 2),
116
), 0), 2),
107
117
108
/* ================= PREVIOUS ROLLING ================= */
118
-- Previous Year Income
109
110
-- Previous Income
111
ROUND(COALESCE((
119
ROUND(COALESCE((
112
SELECT SUM(je.amount)
120
SELECT SUM(je.amount)
113
FROM journal_entries je
121
FROM journal_entries je
114
JOIN chart_of_accounts ca ON je.account_id = ca.id
122
JOIN chart_of_accounts ca ON je.account_id = ca.id
115
JOIN account_types at ON ca.account_type_id = at.id
123
JOIN account_types at ON ca.account_type_id = at.id
116
JOIN account_categories ac ON at.account_category_id = ac.id
124
JOIN account_categories ac ON at.account_category_id = ac.id
117
JOIN transaction_headers tr ON je.transaction_id = tr.id
125
JOIN transaction_headers tr ON je.transaction_id = tr.id
118
WHERE ac.id = 4
126
WHERE ac.id = 4
119
AND tr.company_id = p_company_id
127
AND tr.company_id = p_company_id
120
AND ca.organization_id = v_organization_id
128
AND ca.organization_id = v_organization_id
121
AND je.transaction_date >= v_prev_start
129
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
122
AND je.transaction_date < v_prev_end
130
AND je.is_deleted = FALSE
123
AND COALESCE(je.is_deleted, FALSE) = FALSE
124
AND COALESCE(tr.is_reversed, FALSE) = FALSE
125
), 0), 2),
131
), 0), 2),
126
132
127
-- Previous Expense
133
-- Previous Year Expense
128
ROUND(COALESCE((
134
ROUND(COALESCE((
129
SELECT SUM(je.amount)
135
SELECT SUM(je.amount)
130
FROM journal_entries je
136
FROM journal_entries je
131
JOIN chart_of_accounts ca ON je.account_id = ca.id
137
JOIN chart_of_accounts ca ON je.account_id = ca.id
132
JOIN account_types at ON ca.account_type_id = at.id
138
JOIN account_types at ON ca.account_type_id = at.id
133
JOIN account_categories ac ON at.account_category_id = ac.id
139
JOIN account_categories ac ON at.account_category_id = ac.id
134
JOIN transaction_headers tr ON je.transaction_id = tr.id
140
JOIN transaction_headers tr ON je.transaction_id = tr.id
135
WHERE ac.id = 5
141
WHERE ac.id = 5
136
AND tr.company_id = p_company_id
142
AND tr.company_id = p_company_id
137
AND ca.organization_id = v_organization_id
143
AND ca.organization_id = v_organization_id
138
AND je.transaction_date >= v_prev_start
144
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
139
AND je.transaction_date < v_prev_end
145
AND je.is_deleted = FALSE
140
AND COALESCE(je.is_deleted, FALSE) = FALSE
141
AND COALESCE(tr.is_reversed, FALSE) = FALSE
142
), 0), 2),
146
), 0), 2),
143
147
144
-- Previous Pending Dues
148
-- Previous Year Pending Dues
145
ROUND(COALESCE((
149
ROUND(COALESCE((
146
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
150
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
147
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
151
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
148
FROM journal_entries je
152
FROM journal_entries je
149
JOIN chart_of_accounts ca ON je.account_id = ca.id
153
JOIN chart_of_accounts ca ON je.account_id = ca.id
150
JOIN account_types at ON ca.account_type_id = at.id
154
JOIN account_types at ON ca.account_type_id = at.id
151
JOIN transaction_headers tr ON je.transaction_id = tr.id
155
JOIN transaction_headers tr ON je.transaction_id = tr.id
152
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
156
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
153
AND tr.company_id = p_company_id
157
AND tr.company_id = p_company_id
154
AND ca.organization_id = v_organization_id
158
AND ca.organization_id = v_organization_id
155
AND je.transaction_date >= v_prev_start
159
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
156
AND je.transaction_date < v_prev_end
160
AND je.is_deleted = FALSE
157
AND COALESCE(je.is_deleted, FALSE) = FALSE
158
AND COALESCE(tr.is_reversed, FALSE) = FALSE
159
), 0), 2),
161
), 0), 2),
160
162
161
-- Previous Pending Payments
163
-- Previous Year Pending Payments
162
ROUND(COALESCE((
164
ROUND(COALESCE((
163
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
165
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
164
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
166
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
165
FROM journal_entries je
167
FROM journal_entries je
166
JOIN chart_of_accounts ca ON je.account_id = ca.id
168
JOIN chart_of_accounts ca ON je.account_id = ca.id
167
JOIN account_types at ON ca.account_type_id = at.id
169
JOIN account_types at ON ca.account_type_id = at.id
168
JOIN transaction_headers tr ON je.transaction_id = tr.id
170
JOIN transaction_headers tr ON je.transaction_id = tr.id
169
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
171
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
170
AND tr.company_id = p_company_id
172
AND tr.company_id = p_company_id
171
AND ca.organization_id = v_organization_id
173
AND ca.organization_id = v_organization_id
172
AND je.transaction_date >= v_prev_start
174
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
173
AND je.transaction_date < v_prev_end
175
AND je.is_deleted = FALSE
174
AND COALESCE(je.is_deleted, FALSE) = FALSE
175
AND COALESCE(tr.is_reversed, FALSE) = FALSE
176
), 0), 2);
176
), 0), 2);
177
178
END;
177
END;
179
$function$
178
$function$
|
|||||
| Function | get_all_gate_passes | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_gate_passes(p_company_id uuid, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
2
RETURNS TABLE(id uuid, gate_pass_no character varying, gate_pass_type character varying, warehouse_id uuid, warehouse_name character varying, party_type character varying, party_id uuid, vehicle_no character varying, driver_name character varying, purpose character varying, gate_out_time timestamp without time zone, gate_in_time timestamp without time zone, status character varying, remarks text, created_on_utc timestamp without time zone, created_by character varying, modified_by character varying)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
7
-- 🔥 DEBUG LOGS
8
RAISE NOTICE 'CompanyId: %', p_company_id;
9
RAISE NOTICE 'StartDate: %', p_start_date;
10
RAISE NOTICE 'EndDate: %', p_end_date;
11
12
RETURN QUERY
13
SELECT
14
gh.id,
15
gh.gate_pass_no,
16
gh.gate_pass_type,
17
gh.warehouse_id,
18
w.name AS warehouse_name,
19
gh.party_type,
20
gh.party_id,
21
gh.vehicle_no,
22
gh.driver_name,
23
gh.purpose,
24
gh.gate_out_time,
25
gh.gate_in_time,
26
gh.status,
27
gh.remarks,
28
gh.created_on_utc,
29
30
CAST(TRIM(CONCAT(cu.first_name, ' ', cu.last_name)) AS varchar),
31
CAST(TRIM(CONCAT(mu.first_name, ' ', mu.last_name)) AS varchar)
32
33
FROM public.gate_pass_headers gh
34
35
LEFT JOIN public.warehouses w
36
ON gh.warehouse_id = w.id
37
AND w.is_deleted = false
38
39
LEFT JOIN public.users cu
40
ON cu.id = gh.created_by
41
42
LEFT JOIN public.users mu
43
ON mu.id = gh.modified_by
44
45
WHERE gh.company_id = p_company_id
46
AND gh.is_deleted = false
47
AND (p_start_date IS NULL OR gh.created_on_utc >= p_start_date)
48
AND (p_end_date IS NULL OR gh.created_on_utc <= p_end_date)
49
50
ORDER BY gh.created_on_utc DESC;
51
52
END;
53
$function$
|
|||||
| Procedure | delete_journal_entries_by_accounts | Match | ||||||
| Procedure | copy_user_permissions | Match | ||||||
| Procedure | create_organization | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_organization(IN p_id uuid, IN p_name text, IN p_description text, IN p_phone_number text, IN p_email text, IN p_address_line text, IN p_gstin text, IN p_tag_line text, IN p_city_id uuid, IN p_short_name text, IN p_pan text, IN p_tan text, IN p_created_by uuid, IN p_org_type_id integer)
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
-- Step 1: Insert into organizations table
6
INSERT INTO public.organizations (
7
id,
8
name,
9
description,
10
phone_number,
11
email,
12
address_line,
13
gstin,
14
tag_line,
15
city_id,
16
short_name,
17
pan,
18
tan,
19
outstanding_limit,
20
is_non_work,
21
interest_percentage,
22
created_on_utc,
23
created_by,
24
organization_type_id
25
) VALUES (
26
p_id, -- Organization ID
27
p_name, -- Organization Name
28
p_description, -- Description
29
p_phone_number, -- Phone Number
30
p_email, -- Email
31
p_address_line, -- Address Line
32
p_gstin, -- GSTIN
33
p_tag_line, -- Tag Line
34
p_city_id, -- City ID
35
p_short_name, -- Short Name
36
p_pan, -- PAN
37
p_tan, -- TAN
38
0, -- Outstanding Limit
39
false, -- Is Non-Work
40
0, -- Interest Percentage
41
NOW(), -- Created On Timestamp
42
p_created_by, -- Created By
43
p_org_type_id
44
);
45
46
-- Notify the successful creation of the organization
47
RAISE NOTICE 'Organization created successfully with ID: %', p_id;
48
END;
49
$procedure$
|
|||||
| Procedure | create_company1 | Match | ||||||
| Procedure | delete_journal_entries_by_account | Match | ||||||
| Procedure | initilize_organization_test | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initilize_organization_test(IN p_id uuid, IN p_name text, IN p_company_guids text, IN p_company_names text, IN p_user_id uuid, IN p_user_first_name text, IN p_user_last_name text, IN p_phone_number character varying, IN p_email character varying, IN p_gstin text, IN p_description text, IN p_tag_line text, IN p_short_name text, IN p_pan text, IN p_tan text, IN p_address_line1 text, IN p_address_line2 text, IN p_country_id uuid, IN p_state_id uuid, IN p_city_name text, IN p_zip_code text, IN p_created_by uuid, IN p_default_company_id uuid, IN p_org_type_id integer)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_city_id uuid;
6
v_address_id uuid; -- Generate a unique address ID
7
v_user_id uuid; -- Generate a unique user ID
8
v_permission_ids uuid[];
9
v_company_names text[]; -- Array to hold parsed company names
10
v_company_guids uuid[]; -- Array to hold parsed company GUIDs
11
v_company_name text; -- Variable for iterating through company names
12
v_company_guid uuid; -- Variable for iterating through company GUIDs
13
v_user_company_id uuid;
14
15
-- Constants for default organization and company IDs
16
DEFAULT_ORGANIZATION_ID CONSTANT uuid := '68929d37-b647-4d7b-8c91-7dfe2396c93b';
17
--DEFAULT_COMPANY_ID CONSTANT uuid := '048000ba-e62c-474b-b5eb-fe629a4dc863';
18
ADMIN_PERMISSION CONSTANT text := 'Dhanman.Admin';
19
BEGIN
20
21
22
-- Parse company names and GUIDs into arrays
23
v_company_names := string_to_array(p_company_names, ',');
24
v_company_guids := string_to_array(p_company_guids, ',');
25
26
-- Print arrays
27
RAISE NOTICE 'Company Names: %', v_company_names;
28
RAISE NOTICE 'Company GUIDs: %', v_company_guids;
29
30
-- Get city_id
31
v_city_id := get_city_id(p_city_name, p_zip_code, p_state_id, p_created_by);
32
33
-- Get address_id
34
v_address_id := get_address_id(p_country_id, p_state_id , v_city_id , p_address_line1, p_zip_code, p_created_by, p_address_line2);
35
36
v_user_company_id := v_company_guids[1]; -- Pick the first company ID
37
38
-- Print the generated address_id and fetched state_id and country_id
39
RAISE NOTICE 'Generated address_id: %, City ID: %, Country ID: %', v_address_id, v_city_id, p_country_id;
40
41
-- Check if organization exists by any unique identifier
42
IF NOT EXISTS (SELECT 1 FROM organizations WHERE id = p_id) THEN
43
-- Only create organization if no matching record exists
44
CALL public.create_organization(
45
p_id,
46
p_name,
47
p_description,
48
p_phone_number,
49
p_email,
50
p_address_line1,
51
p_gstin,
52
p_tag_line,
53
v_city_id,
54
p_short_name,
55
p_pan,
56
p_tan,
57
p_created_by,
58
p_org_type_id
59
);
60
61
RAISE NOTICE 'Organization ID % successfully inserted.', p_id;
62
ELSE
63
RAISE NOTICE 'Organization with ID % already exists. Skipping organization creation.', p_id;
64
END IF;
65
66
-- Fetch the permission_id for 'Dhanman.Admin'
67
SELECT array_agg(id) INTO v_permission_ids
68
FROM permissions
69
WHERE name = ADMIN_PERMISSION;
70
71
-- Assign all default users and default permissions as admin
72
CALL public.assign_default_users_to_organization(p_id, v_permission_ids, p_created_by);
73
74
-- Iterate through the company names and GUIDs
75
FOR i IN 1..array_length(v_company_names, 1) LOOP
76
v_company_name := v_company_names[i];
77
v_company_guid := v_company_guids[i];
78
79
-- Call initialize_company for each company
80
CALL public.initialize_company(
81
v_company_guid,
82
p_id,
83
v_company_name,
84
p_description,
85
p_gstin,
86
p_pan,
87
p_tan,
88
'INR',
89
p_short_name,
90
p_tag_line,
91
p_name,
92
p_phone_number,
93
p_email,
94
p_created_by,
95
p_default_company_id
96
);
97
98
RAISE NOTICE 'Company % initialized with GUID %.', v_company_name, v_company_guid;
99
END LOOP;
100
101
-- Create and get user_id
102
v_user_id := create_user(
103
p_user_id,
104
v_company_guids[1],
105
p_email,
106
p_phone_number,
107
p_user_first_name,
108
p_user_last_name,
109
v_address_id,
110
p_created_by
111
);
112
113
-- Insert into organization_users table to link the generated user to the organization
114
CALL public.insert_organization_user(v_user_id, p_created_by, p_id);
115
116
-- 7. Map User → All Companies
117
FOR i IN 1..array_length(v_company_guids, 1) LOOP
118
INSERT INTO company_users (
119
id,
120
company_id,
121
user_id,
122
effective_start_date,
123
effective_end_date,
124
created_on_utc,
125
created_by
126
)
127
VALUES (
128
gen_random_uuid(),
129
v_company_guids[i],
130
v_user_id,
131
NOW(),
132
DATE '9999-12-31',
133
NOW(),
134
p_created_by
135
)
136
ON CONFLICT DO NOTHING;
137
END LOOP;
138
139
-- assign permission to default user
140
CALL public.insert_user_permission(
141
v_user_id ,
142
p_id ,
143
v_permission_ids,
144
p_created_by
145
);
146
147
148
149
-- Call initialize_org_coa to copy chart_of_accounts from the old organization to the new organization
150
CALL public.initialize_org_coa(
151
DEFAULT_ORGANIZATION_ID, -- Old organization ID
152
p_id, -- New organization ID
153
p_company_guids, -- New Company Id
154
p_created_by
155
);
156
157
-- Print a confirmation after the chart_of_accounts is copied
158
RAISE NOTICE 'Chart of accounts successfully copied from organization % to %.', DEFAULT_ORGANIZATION_ID, p_id;
159
160
END;
161
$procedure$
|
|||||
| Procedure | initialize_company | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initialize_company(IN p_id uuid, IN p_organization_id uuid, IN p_name text, IN p_description text, IN p_gstin text, IN p_pan text, IN p_tan text, IN p_currency text, IN p_short_name text, IN p_tag_line text, IN p_proprietor_name text, IN p_phone_number character varying, IN p_email character varying, IN p_created_by uuid, IN p_default_company_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_contact_id uuid := gen_random_uuid();
6
v_company_contact_id uuid := gen_random_uuid();
7
v_company_exists boolean;
8
v_contact_exists boolean;
9
v_company_contact_exists boolean;
10
DEFAULT_USER_ID CONSTANT uuid := 'e3a9d60f-fec9-4c4f-9b0b-bd85ef454a44';
11
BEGIN
12
13
-- ============================
14
-- 1. Create Company
15
-- ============================
16
17
SELECT EXISTS (
18
SELECT 1 FROM public.companies
19
WHERE id = p_id
20
OR (organization_id = p_organization_id AND name = p_name)
21
) INTO v_company_exists;
22
23
IF NOT v_company_exists THEN
24
INSERT INTO public.companies (
25
id, organization_id, name, description,
26
gstin, pan, tan, currency,
27
short_name, tag_line, proprietor_name,
28
outstanding_limit, is_non_work, is_apartment,
29
interest_percentage, created_on_utc, created_by
30
) VALUES (
31
p_id, p_organization_id, p_name, p_description,
32
p_gstin, p_pan, p_tan, p_currency,
33
p_short_name, p_tag_line, p_proprietor_name,
34
0, false, true,
35
0, NOW(), p_created_by
36
);
37
38
RAISE NOTICE 'Company % created.', p_name;
39
END IF;
40
41
-- ============================
42
-- 2. Create / Fetch Contact
43
-- ============================
44
45
SELECT EXISTS (
46
SELECT 1 FROM contacts
47
WHERE email = p_email OR phone_number = p_phone_number
48
) INTO v_contact_exists;
49
50
IF NOT v_contact_exists THEN
51
INSERT INTO contacts (
52
id, salutation, first_name, last_name,
53
email, phone_number, mobile_number,
54
is_primary, created_on_utc, created_by
55
) VALUES (
56
v_contact_id, 'Mr.', p_proprietor_name, NULL,
57
p_email, p_phone_number, p_phone_number,
58
true, NOW(), p_created_by
59
);
60
ELSE
61
SELECT id INTO v_contact_id
62
FROM contacts
63
WHERE email = p_email OR phone_number = p_phone_number
64
LIMIT 1;
65
END IF;
66
67
-- ============================
68
-- 3. Link Company & Contact
69
-- ============================
70
71
SELECT EXISTS (
72
SELECT 1 FROM company_contacts
73
WHERE company_id = p_id AND contact_id = v_contact_id
74
) INTO v_company_contact_exists;
75
76
IF NOT v_company_contact_exists THEN
77
INSERT INTO company_contacts (
78
id, company_id, contact_id,
79
created_on_utc, created_by
80
) VALUES (
81
v_company_contact_id, p_id, v_contact_id,
82
NOW(), p_created_by
83
);
84
END IF;
85
86
-- ============================
87
-- 4. Attach Default System User
88
-- ============================
89
90
INSERT INTO company_users (
91
id, company_id, user_id,
92
effective_start_date, effective_end_date,
93
created_on_utc, created_by
94
)
95
VALUES (
96
gen_random_uuid(), p_id, DEFAULT_USER_ID,
97
NOW(), DATE '9999-12-31',
98
NOW(), p_created_by
99
)
100
ON CONFLICT DO NOTHING;
101
102
-- ============================
103
-- 5. Initialize Finance & JV
104
-- ============================
105
106
IF NOT v_company_exists THEN
107
CALL public.insert_company_finance_years(p_id);
108
CALL public.initialize_journal_voucher_header_ids(
109
p_default_company_id,
110
p_id
111
);
112
END IF;
113
114
RAISE NOTICE 'Company initialization completed for %', p_name;
115
116
END;
117
$procedure$
|
|||||
| Procedure | get_all_chart_of_accounts | Match | ||||||
| Procedure | assign_default_users_to_organization | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.assign_default_users_to_organization(IN p_organization_id uuid, IN p_permission_ids uuid[], IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
loop_user_id UUID;
6
BEGIN
7
-- Insert default users into organization_users (only if they don't exist)
8
INSERT INTO public.organization_users (
9
id,
10
user_id,
11
effective_start_date,
12
effective_end_date,
13
created_on_utc,
14
created_by,
15
organization_id
16
)
17
SELECT
18
gen_random_uuid(),
19
d.user_id,
20
NOW(),
21
DATE '9999-12-31',
22
NOW(),
23
p_created_by,
24
p_organization_id
25
FROM
26
public.default_organization_users d
27
WHERE NOT EXISTS (
28
SELECT 1
29
FROM public.organization_users ou
30
WHERE ou.user_id = d.user_id
31
AND ou.organization_id = p_organization_id
32
);
33
34
RAISE NOTICE 'Added % default users to organization %',
35
(SELECT COUNT(*) FROM public.default_organization_users), p_organization_id;
36
37
-- Assign permissions for these users by calling insert_user_permission
38
FOR loop_user_id IN (SELECT d.user_id
39
FROM public.default_organization_users d)
40
LOOP
41
CALL public.insert_user_permission(
42
loop_user_id,
43
p_organization_id,
44
p_permission_ids,
45
p_created_by
46
);
47
END LOOP;
48
RAISE NOTICE 'Attempted to assign permission % to default users in organization %',
49
p_permission_ids,
50
p_organization_id;
51
END;
52
$procedure$
|
|||||
| Procedure | delete_journal_vouchers_by_company | Match | ||||||
| Procedure | delete_transactions_by_company | Match | ||||||
| Procedure | dummy_log_procedure | Match | ||||||
| Procedure | hard_delete_org_common | Match | ||||||
| Procedure | hard_delete_organization | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.hard_delete_organization(IN p_organization_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
-- Delete from user_permissions (cascading through organization and users)
6
DELETE FROM user_permissions
7
WHERE organization_id = p_organization_id;
8
9
-- Delete from organization_users (cascading through organization)
10
DELETE FROM organization_users
11
WHERE organization_id = p_organization_id;
12
13
-- Delete from company_finance_year (cascading through companies)
14
DELETE FROM company_finance_year
15
WHERE company_id IN (
16
SELECT id FROM companies WHERE organization_id = p_organization_id
17
);
18
19
-- Delete from company_users (cascading through companies)
20
DELETE FROM company_users
21
WHERE company_id IN (
22
SELECT id FROM companies WHERE organization_id = p_organization_id
23
);
24
25
-- Delete from company_contacts (cascading through companies)
26
DELETE FROM company_contacts
27
WHERE company_id IN (
28
SELECT id FROM companies WHERE organization_id = p_organization_id
29
);
30
31
-- Delete from contacts (cascading through companies)
32
DELETE FROM contacts
33
WHERE id IN (
34
SELECT contact_id FROM company_contacts
35
WHERE company_id IN (
36
SELECT id FROM companies WHERE organization_id = p_organization_id
37
)
38
);
39
40
--delete from company_preference
41
DELETE FROM public.company_preferences
42
WHERE company_id in (SELECT id from public.companies where organization_id = p_organization_id);
43
44
-- Delete from companies (cascading through organization)
45
DELETE FROM companies
46
WHERE organization_id = p_organization_id;
47
48
DELETE FROM organization_accounts
49
WHERE organization_id = p_organization_id;
50
51
-- Delete from chart_of_accounts (cascading through organization)
52
DELETE FROM chart_of_accounts
53
WHERE organization_id = p_organization_id;
54
55
-- Finally, delete the organization itself
56
DELETE FROM organizations
57
WHERE id = p_organization_id;
58
59
-- Optional: Log the deletion action
60
RAISE NOTICE 'Organization with ID % and all related data have been hard deleted.',
61
p_organization_id;
62
63
END;
64
$procedure$
|
|||||
| Procedure | initialize_journal_voucher_header_ids | Match | ||||||
| Procedure | initialize_org_coa | Match | ||||||
| Procedure | initilize_organization | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initilize_organization(IN p_id uuid, IN p_name text, IN p_company_guids text, IN p_company_names text, IN p_user_id uuid, IN p_user_first_name text, IN p_user_last_name text, IN p_phone_number character varying, IN p_email character varying, IN p_gstin text, IN p_description text, IN p_tag_line text, IN p_short_name text, IN p_pan text, IN p_tan text, IN p_address_line1 text, IN p_address_line2 text, IN p_country_id uuid, IN p_state_id uuid, IN p_city_name text, IN p_zip_code text, IN p_created_by uuid, IN p_default_company_id uuid)
1
CREATE OR REPLACE PROCEDURE public.initilize_organization(IN p_id uuid, IN p_name text, IN p_company_guids text, IN p_company_names text, IN p_user_id uuid, IN p_user_first_name text, IN p_user_last_name text, IN p_phone_number character varying, IN p_email character varying, IN p_gstin text, IN p_description text, IN p_tag_line text, IN p_short_name text, IN p_pan text, IN p_tan text, IN p_address_line1 text, IN p_address_line2 text, IN p_country_id uuid, IN p_state_id uuid, IN p_city_name text, IN p_zip_code text, IN p_created_by uuid, IN p_default_company_id uuid)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
DECLARE
4
DECLARE
5
v_city_id uuid;
5
v_city_id uuid;
6
v_address_id uuid; -- Generate a unique address ID
6
v_address_id uuid; -- Generate a unique address ID
7
v_user_id uuid; -- Generate a unique user ID
7
v_user_id uuid; -- Generate a unique user ID
8
v_permission_ids uuid[];
8
v_permission_id uuid;
9
v_company_names text[]; -- Array to hold parsed company names
9
v_company_names text[]; -- Array to hold parsed company names
10
v_company_guids uuid[]; -- Array to hold parsed company GUIDs
10
v_company_guids uuid[]; -- Array to hold parsed company GUIDs
11
v_company_name text; -- Variable for iterating through company names
11
v_company_name text; -- Variable for iterating through company names
12
v_company_guid uuid; -- Variable for iterating through company GUIDs
12
v_company_guid uuid; -- Variable for iterating through company GUIDs
13
v_user_company_id uuid;
13
v_user_company_id uuid;
14
14
15
-- Constants for default organization and company IDs
15
-- Constants for default organization and company IDs
16
DEFAULT_ORGANIZATION_ID CONSTANT uuid := '68929d37-b647-4d7b-8c91-7dfe2396c93b';
16
DEFAULT_ORGANIZATION_ID CONSTANT uuid := '68929d37-b647-4d7b-8c91-7dfe2396c93b';
17
--DEFAULT_COMPANY_ID CONSTANT uuid := '048000ba-e62c-474b-b5eb-fe629a4dc863';
17
--DEFAULT_COMPANY_ID CONSTANT uuid := '048000ba-e62c-474b-b5eb-fe629a4dc863';
18
ADMIN_PERMISSION CONSTANT text := 'Dhanman.Admin';
18
ADMIN_PERMISSION CONSTANT text := 'Dhanman.Admin';
19
BEGIN
19
BEGIN
20
20
21
21
22
-- Parse company names and GUIDs into arrays
22
-- Parse company names and GUIDs into arrays
23
v_company_names := string_to_array(p_company_names, ',');
23
v_company_names := string_to_array(p_company_names, ',');
24
v_company_guids := string_to_array(p_company_guids, ',');
24
v_company_guids := string_to_array(p_company_guids, ',');
25
26
-- Print arrays
27
RAISE NOTICE 'Company Names: %', v_company_names;
28
RAISE NOTICE 'Company GUIDs: %', v_company_guids;
29
25
30
-- Get city_id
26
-- Get city_id
31
v_city_id := get_city_id(p_city_name, p_zip_code, p_state_id, p_created_by);
27
v_city_id := get_city_id(p_city_name, p_zip_code, p_state_id, p_created_by);
32
28
33
-- Get address_id
29
-- Get address_id
34
v_address_id := get_address_id(p_country_id, p_state_id , v_city_id , p_address_line1, p_zip_code, p_created_by, p_address_line2);
30
v_address_id := get_address_id(p_country_id, p_state_id , v_city_id , p_address_line1, p_zip_code, p_created_by, p_address_line2);
35
31
36
v_user_company_id := v_company_guids[1]; -- Pick the first company ID
32
v_user_company_id := v_company_guids[1]; -- Pick the first company ID
37
33
38
-- Create and get user_id
34
-- Create and get user_id
39
v_user_id := create_user(p_user_id, v_user_company_id, p_email, p_phone_number, p_user_first_name, p_user_last_name, v_address_id, p_created_by);
35
v_user_id := create_user(p_user_id, v_user_company_id, p_email, p_phone_number, p_user_first_name, p_user_last_name, v_address_id, p_created_by);
40
RAISE NOTICE 'Generated User ID: %',v_user_company_id;
36
RAISE NOTICE 'Generated User ID: %',v_user_company_id;
41
37
42
-- Print the generated address_id and fetched state_id and country_id
38
-- Print the generated address_id and fetched state_id and country_id
43
RAISE NOTICE 'Generated address_id: %, City ID: %, Country ID: %, User ID: %', v_address_id, v_city_id, p_country_id, v_user_id;
39
RAISE NOTICE 'Generated address_id: %, City ID: %, Country ID: %, User ID: %', v_address_id, v_city_id, p_country_id, v_user_id;
44
40
45
/* -- Check if organization exists by any unique identifier
41
-- Check if organization exists by any unique identifier
46
IF NOT EXISTS (SELECT 1 FROM organizations WHERE id = p_id) THEN
42
IF NOT EXISTS (SELECT 1 FROM organizations WHERE id = p_id) THEN
47
-- Only create organization if no matching record exists
43
-- Only create organization if no matching record exists
48
CALL public.create_organization(
44
CALL public.create_organization(
49
p_id,
45
p_id,
50
p_name,
46
p_name,
51
p_description,
47
p_description,
52
p_phone_number,
48
p_phone_number,
53
p_email,
49
p_email,
54
p_address_line1,
50
p_address_line1,
55
p_gstin,
51
p_gstin,
56
p_tag_line,
52
p_tag_line,
57
v_city_id,
53
v_city_id,
58
p_short_name,
54
p_short_name,
59
p_pan,
55
p_pan,
60
p_tan,
56
p_tan,
61
p_created_by
57
p_created_by
62
);
58
);
63
59
64
RAISE NOTICE 'Organization ID % successfully inserted.', p_id;
60
RAISE NOTICE 'Organization ID % successfully inserted.', p_id;
65
ELSE
61
ELSE
66
RAISE NOTICE 'Organization with ID % already exists. Skipping organization creation.', p_id;
62
RAISE NOTICE 'Organization with ID % already exists. Skipping organization creation.', p_id;
67
END IF;
63
END IF;
68
64
69
-- Insert into organization_users table to link the generated user to the organization
65
-- Insert into organization_users table to link the generated user to the organization
70
CALL public.insert_organization_user(v_user_id, p_created_by, p_id);
66
CALL public.insert_organization_user(v_user_id, p_created_by, p_id);
71
67
72
-- Fetch the permission_id for 'Dhanman.Admin'
68
-- Fetch the permission_id for 'Dhanman.Admin'
73
SELECT array_agg(id) INTO v_permission_ids
69
SELECT id INTO v_permission_id
74
FROM permissions
70
FROM permissions
75
WHERE name = ADMIN_PERMISSION;
71
WHERE name = ADMIN_PERMISSION;
76
72
77
CALL public.insert_user_permission(
73
CALL public.insert_user_permission(
78
v_user_id ,
74
v_user_id ,
79
p_id ,
75
p_id ,
80
v_permission_ids,
76
v_permission_id,
81
p_created_by
77
p_created_by
82
);
78
);
83
79
84
-- Assign all default users and default permissions as admin
80
-- Assign all default users and default permissions as admin
85
CALL public.assign_default_users_to_organization(p_id, v_permission_ids, p_created_by);
81
CALL public.assign_default_users_to_organization(p_id, v_permission_id, p_created_by);
86
82
87
-- Iterate through the company names and GUIDs
83
-- Iterate through the company names and GUIDs
88
FOR i IN 1..array_length(v_company_names, 1) LOOP
84
FOR i IN 1..array_length(v_company_names, 1) LOOP
89
v_company_name := v_company_names[i];
85
v_company_name := v_company_names[i];
90
v_company_guid := v_company_guids[i];
86
v_company_guid := v_company_guids[i];
91
87
92
-- Call initialize_company for each company
88
-- Call initialize_company for each company
93
CALL public.initialize_company(
89
CALL public.initialize_company(
94
v_company_guid,
90
v_company_guid,
95
p_id,
91
p_id,
96
v_company_name,
92
v_company_name,
97
p_description,
93
p_description,
98
p_gstin,
94
p_gstin,
99
p_pan,
95
p_pan,
100
p_tan,
96
p_tan,
101
'INR',
97
'INR',
102
p_short_name,
98
p_short_name,
103
p_tag_line,
99
p_tag_line,
104
p_name,
100
p_name,
105
p_phone_number,
101
p_phone_number,
106
p_email,
102
p_email,
107
p_created_by,
103
p_created_by,
108
v_user_id,
104
v_user_id,
109
p_default_company_id
105
p_default_company_id
110
);
106
);
111
107
112
RAISE NOTICE 'Company % initialized with GUID %.', v_company_name, v_company_guid;
108
RAISE NOTICE 'Company % initialized with GUID %.', v_company_name, v_company_guid;
113
END LOOP;
109
END LOOP;
114
110
115
-- Call initialize_org_coa to copy chart_of_accounts from the old organization to the new organization
111
-- Call initialize_org_coa to copy chart_of_accounts from the old organization to the new organization
116
CALL public.initialize_org_coa(
112
CALL public.initialize_org_coa(
117
DEFAULT_ORGANIZATION_ID, -- Old organization ID
113
DEFAULT_ORGANIZATION_ID, -- Old organization ID
118
p_id, -- New organization ID
114
p_id, -- New organization ID
119
p_company_guids, -- New Company Id
115
p_company_guids, -- New Company Id
120
p_created_by
116
p_created_by
121
);
117
);
122
118
123
-- Print a confirmation after the chart_of_accounts is copied
119
-- Print a confirmation after the chart_of_accounts is copied
124
RAISE NOTICE 'Chart of accounts successfully copied from organization % to %.', DEFAULT_ORGANIZATION_ID, p_id;*/
120
RAISE NOTICE 'Chart of accounts successfully copied from organization % to %.', DEFAULT_ORGANIZATION_ID, p_id;
125
121
126
END;
122
END;
127
$procedure$
123
$procedure$
|
|||||
| Procedure | initialize_company | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initialize_company(IN p_id uuid, IN p_organization_id uuid, IN p_name text, IN p_description text, IN p_gstin text, IN p_pan text, IN p_tan text, IN p_currency text, IN p_short_name text, IN p_tag_line text, IN p_proprietor_name text, IN p_phone_number character varying, IN p_email character varying, IN p_created_by uuid, IN p_default_company_id uuid)
1
CREATE OR REPLACE PROCEDURE public.initialize_company(IN p_id uuid, IN p_organization_id uuid, IN p_name text, IN p_description text, IN p_gstin text, IN p_pan text, IN p_tan text, IN p_currency text, IN p_short_name text, IN p_tag_line text, IN p_proprietor_name text, IN p_phone_number character varying, IN p_email character varying, IN p_created_by uuid, IN p_user_id uuid, IN p_default_company_id uuid)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
DECLARE
4
DECLARE
5
v_contact_id uuid := gen_random_uuid();
5
v_contact_id uuid := gen_random_uuid();
6
v_company_contact_id uuid := gen_random_uuid();
6
v_company_contact_id uuid := gen_random_uuid();
7
v_finance_year_id int;
8
v_prev_finance_year_id INTEGER;
9
v_next_finance_year_id INTEGER;
10
DEFAULT_USER_ID CONSTANT uuid := '50fd5012-940b-4fec-ad0a-2ac900239c8b';
7
v_company_exists boolean;
11
v_company_exists boolean;
8
v_contact_exists boolean;
12
v_contact_exists boolean;
9
v_company_contact_exists boolean;
13
v_company_contact_exists boolean;
10
DEFAULT_USER_ID CONSTANT uuid := 'e3a9d60f-fec9-4c4f-9b0b-bd85ef454a44';
14
v_user_company_exists boolean;
15
v_default_user_company_exists boolean;
11
BEGIN
16
BEGIN
12
17
-- Check if company already exists
13
-- ============================
14
-- 1. Create Company
15
-- ============================
16
17
SELECT EXISTS (
18
SELECT EXISTS (
18
SELECT 1 FROM public.companies
19
SELECT 1 FROM public.companies
19
WHERE id = p_id
20
WHERE id = p_id
20
OR (organization_id = p_organization_id AND name = p_name)
21
OR (organization_id = p_organization_id AND name = p_name)
21
) INTO v_company_exists;
22
) INTO v_company_exists;
22
23
23
IF NOT v_company_exists THEN
24
IF NOT v_company_exists THEN
25
-- Insert into companies table
24
INSERT INTO public.companies (
26
INSERT INTO public.companies (
25
id, organization_id, name, description,
27
id, organization_id, name, description, gstin, pan, tan,
26
gstin, pan, tan, currency,
28
currency, short_name, tag_line, proprietor_name,
27
short_name, tag_line, proprietor_name,
29
outstanding_limit, is_non_work, is_apartment, interest_percentage,
28
outstanding_limit, is_non_work, is_apartment,
30
created_on_utc, created_by
29
interest_percentage, created_on_utc, created_by
30
) VALUES (
31
) VALUES (
31
p_id, p_organization_id, p_name, p_description,
32
p_id, p_organization_id, p_name, p_description, p_gstin, p_pan, p_tan,
32
p_gstin, p_pan, p_tan, p_currency,
33
p_currency, p_short_name, p_tag_line, p_proprietor_name,
33
p_short_name, p_tag_line, p_proprietor_name,
34
0, false, true, 0, NOW(), p_created_by
34
0, false, true,
35
0, NOW(), p_created_by
36
);
35
);
37
36
RAISE NOTICE 'Company % created successfully.', p_name;
38
RAISE NOTICE 'Company % created.', p_name;
37
ELSE
38
RAISE NOTICE 'Company with ID %, name %, or GSTIN/PAN % already exists. Skipping company creation.',
39
p_id, p_name, p_gstin;
39
END IF;
40
END IF;
40
41
41
-- ============================
42
-- Check if contact already exists for this email/phone
42
-- 2. Create / Fetch Contact
43
-- ============================
44
45
SELECT EXISTS (
43
SELECT EXISTS (
46
SELECT 1 FROM contacts
44
SELECT 1 FROM contacts
47
WHERE email = p_email OR phone_number = p_phone_number
45
WHERE email = p_email OR phone_number = p_phone_number
48
) INTO v_contact_exists;
46
) INTO v_contact_exists;
49
47
50
IF NOT v_contact_exists THEN
48
IF NOT v_contact_exists THEN
49
-- Insert contact
51
INSERT INTO contacts (
50
INSERT INTO contacts (
52
id, salutation, first_name, last_name,
51
id, salutation, first_name, last_name, email,
53
email, phone_number, mobile_number,
52
phone_number, mobile_number, is_primary,
54
is_primary, created_on_utc, created_by
53
created_on_utc, created_by
55
) VALUES (
54
) VALUES (
56
v_contact_id, 'Mr.', p_proprietor_name, NULL,
55
v_contact_id, 'Mr.', p_proprietor_name, NULL, p_email,
57
p_email, p_phone_number, p_phone_number,
56
p_phone_number, p_phone_number, TRUE,
58
true, NOW(), p_created_by
57
NOW(), p_created_by
59
);
58
);
59
RAISE NOTICE 'Contact created for company % with email %.', p_name, p_email;
60
ELSE
60
ELSE
61
-- Get existing contact ID
61
SELECT id INTO v_contact_id
62
SELECT id INTO v_contact_id
62
FROM contacts
63
FROM contacts
63
WHERE email = p_email OR phone_number = p_phone_number
64
WHERE email = p_email OR phone_number = p_phone_number
64
LIMIT 1;
65
LIMIT 1;
66
RAISE NOTICE 'Contact with email % or phone % already exists. Using existing contact.', p_email, p_phone_number;
65
END IF;
67
END IF;
66
68
67
-- ============================
69
-- Check if company-contact relationship exists
68
-- 3. Link Company & Contact
69
-- ============================
70
71
SELECT EXISTS (
70
SELECT EXISTS (
72
SELECT 1 FROM company_contacts
71
SELECT 1 FROM company_contacts
73
WHERE company_id = p_id AND contact_id = v_contact_id
72
WHERE company_id = p_id AND contact_id = v_contact_id
74
) INTO v_company_contact_exists;
73
) INTO v_company_contact_exists;
75
74
76
IF NOT v_company_contact_exists THEN
75
IF NOT v_company_contact_exists THEN
76
-- Link company and contact
77
INSERT INTO company_contacts (
77
INSERT INTO company_contacts (
78
id, company_id, contact_id,
78
id, company_id, contact_id, created_on_utc, created_by
79
created_on_utc, created_by
80
) VALUES (
79
) VALUES (
81
v_company_contact_id, p_id, v_contact_id,
80
v_company_contact_id, p_id, v_contact_id, NOW(), p_created_by
82
NOW(), p_created_by
83
);
81
);
84
END IF;
82
END IF;
85
83
86
-- ============================
84
-- Check if user-company relationship exists for main user
87
-- 4. Attach Default System User
85
SELECT EXISTS (
88
-- ============================
86
SELECT 1 FROM company_users
87
WHERE company_id = p_id AND user_id = p_user_id
88
) INTO v_user_company_exists;
89
89
90
IF NOT v_user_company_exists THEN
91
-- Link main user to company
90
INSERT INTO company_users (
92
INSERT INTO company_users (
91
id, company_id, user_id,
93
id, company_id, user_id, effective_start_date,
92
effective_start_date, effective_end_date,
94
effective_end_date, created_on_utc, created_by
93
created_on_utc, created_by
95
) VALUES (
94
)
96
gen_random_uuid(), p_id, p_user_id, NOW(),
95
VALUES (
97
DATE '9999-12-31', NOW(), p_created_by
96
gen_random_uuid(), p_id, DEFAULT_USER_ID,
98
);
97
NOW(), DATE '9999-12-31',
99
END IF;
98
NOW(), p_created_by
100
99
)
101
-- Check if user-company relationship exists for default user
100
ON CONFLICT DO NOTHING;
102
SELECT EXISTS (
103
SELECT 1 FROM company_users
104
WHERE company_id = p_id AND user_id = DEFAULT_USER_ID
105
) INTO v_default_user_company_exists;
101
106
102
-- ============================
107
IF NOT v_default_user_company_exists THEN
103
-- 5. Initialize Finance & JV
108
-- Link default user to company
104
-- ============================
109
INSERT INTO company_users (
110
id, company_id, user_id, effective_start_date,
111
effective_end_date, created_on_utc, created_by
112
) VALUES (
113
gen_random_uuid(), p_id, DEFAULT_USER_ID, NOW(),
114
DATE '9999-12-31', NOW(), p_created_by
115
);
116
END IF;
105
117
118
-- Initialize finance years if company was newly created
106
IF NOT v_company_exists THEN
119
IF NOT v_company_exists THEN
107
CALL public.insert_company_finance_years(p_id);
120
CALL public.insert_company_finance_years(p_id);
108
CALL public.initialize_journal_voucher_header_ids(
121
CALL public.initialize_journal_voucher_header_ids(p_default_company_id, p_id);
109
p_default_company_id,
110
p_id
111
);
112
END IF;
122
END IF;
113
123
114
RAISE NOTICE 'Company initialization completed for %', p_name;
124
RAISE NOTICE 'Company initialization completed for % (ID: %)', p_name, p_id;
115
116
END;
125
END;
117
$procedure$
126
$procedure$
|
|||||
| Procedure | insert_bank_statement_by_excel | Match | ||||||
| Procedure | insert_company_finance_years | Match | ||||||
| Procedure | insert_company_user | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_company_user(IN p_user_id uuid, IN p_created_by uuid, IN p_company_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_existing_id uuid;
6
v_is_deleted boolean;
7
BEGIN
8
-- Check if user-company relationship exists (including soft-deleted)
9
SELECT id, is_deleted
10
INTO v_existing_id, v_is_deleted
11
FROM public.company_users
12
WHERE user_id = p_user_id
13
AND company_id = p_company_id
14
LIMIT 1;
15
16
-- If exists and active (not deleted), do nothing
17
IF FOUND AND v_is_deleted = false THEN
18
RAISE NOTICE 'User % already exists in company % (active), skipping insert.', p_user_id, p_company_id;
19
20
-- If exists but soft-deleted, reactivate the record
21
ELSIF FOUND AND v_is_deleted = true THEN
22
UPDATE public.company_users
23
SET is_deleted = false,
24
modified_on_utc = NOW(),
25
modified_by = p_created_by,
26
deleted_on_utc = NULL
27
WHERE id = v_existing_id;
28
29
RAISE NOTICE 'User % re-activated in company % (id=%)', p_user_id, p_company_id, v_existing_id;
30
31
-- If no record found, insert new
32
ELSE
33
INSERT INTO public.company_users (
34
id,
35
user_id,
36
company_id,
37
effective_start_date,
38
effective_end_date,
39
created_on_utc,
40
created_by,
41
is_deleted
42
) VALUES (
43
gen_random_uuid(),
44
p_user_id,
45
p_company_id,
46
NOW(),
47
DATE '9999-12-31',
48
NOW(),
49
p_created_by,
50
false
51
);
52
53
RAISE NOTICE 'Company-user relationship created for user % in company %', p_user_id, p_company_id;
54
END IF;
55
END;
56
$procedure$
|
|||||
| Procedure | insert_into_manual_journal | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_into_manual_journal(IN p_company_id uuid, IN p_journal_header_id uuid, IN p_date date, IN p_amount numeric, IN p_note text, IN p_description text, IN p_is_debit boolean, IN p_account_id uuid, IN p_created_by uuid, IN p_journal_details_data jsonb, IN p_transaction_header_data jsonb, IN journal_entries_data jsonb)
1
CREATE OR REPLACE PROCEDURE public.insert_into_manual_journal(IN p_company_id uuid, IN p_journal_header_id uuid, IN p_date date, IN p_amount numeric, IN p_note text, IN p_description text, IN p_is_debit boolean, IN p_account_id uuid, IN p_created_by uuid, IN p_journal_details_data jsonb, IN p_transaction_header_data jsonb, IN journal_entries_data jsonb)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
DECLARE
4
DECLARE
5
v_voucher_number character varying;
5
v_voucher_number character varying;
6
v_voucher_detail JSONB;
6
v_voucher_detail JSONB;
7
v_transaction_record JSONB;
7
v_transaction_id BIGINT;
8
v_transaction_result RECORD;
8
v_journal_entry JSONB;
9
v_transactions_data JSONB;
10
BEGIN
9
BEGIN
11
v_voucher_number := get_new_voucher_number(p_company_id, p_date);
10
v_voucher_number := get_new_voucher_number(p_company_id,p_date);
12
RAISE NOTICE 'new v_voucher_number: %', v_voucher_number;
11
RAISE NOTICE 'new v_voucher_number: %', v_voucher_number;
13
12
14
-- Insert into journal_voucher_headers
13
-- Insert into journal_voucher_headers
15
INSERT INTO public.journal_voucher_headers(
14
INSERT INTO public.journal_voucher_headers(
16
id,
15
id,
17
company_id,
16
company_id,
18
date,
17
date,
19
amount,
18
amount,
20
note,
19
note,
21
is_debit,
20
is_debit,
22
account_id,
21
account_id,
23
voucher_number,
22
voucher_number,
24
created_by,
23
created_by,
25
created_on_utc,
24
created_on_utc,
26
is_deleted
25
is_deleted
27
)
26
)
28
VALUES (
27
VALUES (
29
p_journal_header_id,
28
p_journal_header_id,
30
p_company_id,
29
p_company_id,
31
p_date,
30
p_date,
32
p_amount,
31
p_amount,
33
p_note,
32
p_note,
34
p_is_debit,
33
p_is_debit,
35
p_account_id,
34
p_account_id,
36
v_voucher_number,
35
v_voucher_number,
37
p_created_by,
36
p_created_by,
38
CURRENT_TIMESTAMP,
37
CURRENT_TIMESTAMP,
39
FALSE
38
FALSE
40
);
39
);
41
40
42
RAISE NOTICE 'Inserted into journal_voucher_headers: %', p_journal_header_id;
41
RAISE NOTICE 'Inserted into journal_voucher_headers: %', p_journal_header_id;
43
42
44
-- Insert into journal_voucher_details
43
-- Loop through each journal entry and insert into journal_voucher_details
45
FOR v_voucher_detail IN SELECT * FROM jsonb_array_elements(p_journal_details_data)
44
FOR v_voucher_detail IN SELECT * FROM jsonb_array_elements(p_journal_details_data)
46
LOOP
45
LOOP
47
INSERT INTO public.journal_voucher_details(
46
INSERT INTO public.journal_voucher_details(
48
id,
47
id,
49
journal_header_id,
48
journal_header_id,
50
credit_account_id,
49
credit_account_id,
51
debit_account_id,
50
debit_account_id,
52
amount,
51
amount,
53
tds_tcs,
52
tds_tcs,
54
narration,
53
narration,
55
created_by,
54
created_by,
56
created_on_utc,
55
created_on_utc,
57
is_deleted
56
is_deleted
58
)
57
)
59
VALUES (
58
VALUES (
60
(v_voucher_detail->>'detail_id')::UUID,
59
(v_voucher_detail->>'detail_id')::UUID,
61
p_journal_header_id,
60
p_journal_header_id,
62
(v_voucher_detail->>'credit_account_id')::UUID,
61
(v_voucher_detail->>'credit_account_id')::UUID,
63
(v_voucher_detail->>'debit_account_id')::UUID,
62
(v_voucher_detail->>'debit_account_id')::UUID,
64
(v_voucher_detail->>'amount')::NUMERIC,
63
(v_voucher_detail->>'amount')::NUMERIC,
65
v_voucher_detail->>'tds_tcs',
64
v_voucher_detail->>'tds_tcs',
66
v_voucher_detail->>'narration',
65
v_voucher_detail->>'narration',
67
p_created_by,
66
p_created_by,
68
CURRENT_TIMESTAMP,
67
CURRENT_TIMESTAMP,
69
FALSE
68
FALSE
70
);
69
);
71
RAISE NOTICE 'Inserted into journal_voucher_details';
70
RAISE NOTICE 'Inserted into journal_voucher_details';
72
END LOOP;
71
END LOOP;
73
72
74
-- Prepare single transaction record matching insert_multiple_transactions_and_journal_entries input
73
INSERT INTO public.transaction_headers (
75
v_transaction_record := jsonb_build_object(
74
company_id,
76
'company_id', p_transaction_header_data->>'company_id',
75
customer_id,
77
'customer_id', p_transaction_header_data->>'customer_id',
76
vendor_id,
78
'vendor_id', p_transaction_header_data->>'vendor_id',
77
employee_id,
79
'employee_id', p_transaction_header_data->>'employee_id',
78
transaction_date,
80
'transaction_date', p_transaction_header_data->>'transaction_date',
79
transaction_source_type,
81
'transaction_source_type', p_transaction_header_data->>'transaction_source_type',
80
status_id,
82
'status_id', p_transaction_header_data->>'status_id',
81
document_id,
83
'document_id', p_transaction_header_data->>'document_id',
82
document_number,
84
'document_number', v_voucher_number,
83
description,
85
'description', p_transaction_header_data->>'description',
84
created_by,
86
'user_id', p_created_by,
85
created_on_utc
87
'journal_entries', journal_entries_data
86
)
88
);
87
VALUES(
88
(p_transaction_header_data->>'company_id')::UUID,
89
NULLIF((p_transaction_header_data->>'customer_id')::UUID, '00000000-0000-0000-0000-000000000000'),
90
NULLIF((p_transaction_header_data->>'vendor_id')::UUID, '00000000-0000-0000-0000-000000000000'),
91
NULLIF((p_transaction_header_data->>'employee_id')::UUID, '00000000-0000-0000-0000-000000000000'),
92
(p_transaction_header_data->>'transaction_date')::DATE,
93
(p_transaction_header_data->>'transaction_source_type')::INT,
94
(p_transaction_header_data->>'status_id')::INT,
95
(p_transaction_header_data->>'document_id')::UUID,
96
v_voucher_number,
97
(p_transaction_header_data->>'description')::TEXT,
98
p_created_by,
99
CURRENT_TIMESTAMP
100
)
101
RETURNING id INTO v_transaction_id;
89
102
90
v_transactions_data := jsonb_build_array(v_transaction_record);
103
-- Loop through each journal entry and insert
91
104
FOR v_journal_entry IN SELECT * FROM jsonb_array_elements(journal_entries_data)
92
-- Call the batch function to insert into transaction_headers and journal_entries (and narration)
93
FOR v_transaction_result IN
94
SELECT * FROM public.insert_multiple_transactions_and_journal_entries(v_transactions_data)
95
LOOP
105
LOOP
96
RAISE NOTICE 'Inserted transaction_id: %, document_id: %',
106
RAISE NOTICE 'transaction_date: %', (v_journal_entry->>'transaction_date')::DATE;
97
v_transaction_result.transaction_id, v_transaction_result.document_id;
107
INSERT INTO public.journal_entries (
98
-- You can use v_transaction_result.transaction_id if you need to reference it for other inserts
108
transaction_id,
109
account_id,
110
transaction_date,
111
amount,
112
entry_type,
113
description_template_id,
114
dynamic_data,
115
entry_source_id
116
)
117
VALUES (
118
v_transaction_id,
119
(v_journal_entry->>'account_id')::uuid,
120
(v_journal_entry->>'transaction_date')::date,
121
(v_journal_entry->>'amount')::decimal,
122
(v_journal_entry->>'entry_type')::char,
123
(v_journal_entry->>'description_template_id')::int,
124
(v_journal_entry->>'dynamic_data')::jsonb,
125
(v_journal_entry->>'entry_source_id')::int
126
);
99
END LOOP;
127
END LOOP;
100
128
101
EXCEPTION
102
WHEN OTHERS THEN
103
RAISE EXCEPTION 'Error occurred: %', SQLERRM;
104
END;
129
END;
105
$procedure$
130
$procedure$
|
|||||
| Procedure | insert_organization_company | Match | ||||||
| Procedure | insert_organization_user | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_organization_user(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid)
1
CREATE OR REPLACE PROCEDURE public.insert_organization_user(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
DECLARE
5
v_existing_id uuid;
6
v_is_deleted boolean;
7
BEGIN
4
BEGIN
8
-- Check if user-organization relationship exists (including soft-deleted)
5
-- Check if this user-organization relationship already exists
9
SELECT id, is_deleted
6
IF NOT EXISTS (
10
INTO v_existing_id, v_is_deleted
7
SELECT 1
11
FROM public.organization_users
8
FROM public.organization_users
12
WHERE user_id = p_user_id
9
WHERE user_id = p_user_id
13
AND organization_id = p_organization_id
10
AND organization_id = p_organization_id
14
LIMIT 1;
11
) THEN
15
12
-- Only insert if the relationship doesn't exist
16
-- If exists and active (not deleted), do nothing
13
INSERT INTO public.organization_users (
17
-- If exists and active (not deleted), do nothing
18
-- If exists and active (not deleted), do nothing
19
-- If exists and active (not deleted), do nothing
20
-- If exists and active (not deleted), do nothing
21
IF FOUND AND v_is_deleted = false THEN
22
RAISE NOTICE 'User % already exists in organization % (active), skipping insert.', p_user_id, p_organization_id;
23
24
-- If exists but soft-deleted, reactivate the record
25
ELSIF FOUND AND v_is_deleted = true THEN
26
UPDATE public.organization_users
27
SET is_deleted = false,
28
modified_on_utc = NOW(),
29
modified_by = p_created_by,
30
deleted_on_utc = NULL
31
WHERE id = v_existing_id;
32
33
RAISE NOTICE 'User % re-activated in organization % (id=%)', p_user_id, p_organization_id, v_existing_id;
34
35
-- If no record found, insert new
36
ELSE
37
INSERT INTO public.organization_users(
38
id,
14
id,
39
user_id,
15
user_id,
40
organization_id,
41
effective_start_date,
16
effective_start_date,
42
effective_end_date,
17
effective_end_date,
43
created_on_utc,
18
created_on_utc,
44
created_by,
19
created_by,
45
is_deleted
20
organization_id
46
)
21
) VALUES (
47
VALUES (
48
gen_random_uuid(),
22
gen_random_uuid(),
49
p_user_id,
23
p_user_id,
50
p_organization_id,
51
NOW(),
24
NOW(),
52
DATE '9999-12-31',
25
DATE '9999-12-31',
53
NOW(),
26
NOW(),
54
p_created_by,
27
p_created_by,
55
false
28
p_organization_id
56
);
29
);
57
30
58
RAISE NOTICE 'Organization-user relationship created for user % in organization %', p_user_id, p_organization_id;
31
RAISE NOTICE 'Organization-user relationship created for user % in organization %', p_user_id, p_organization_id;
32
ELSE
33
RAISE NOTICE 'User % already exists in organization %. Skipping insertion.', p_user_id, p_organization_id;
59
END IF;
34
END IF;
60
END;
35
END;
61
$procedure$
36
$procedure$
|
|||||
| Procedure | insert_user_role | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_user_role(IN p_user_id uuid, IN p_role_id integer, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_existing_id INT;
6
v_is_deleted BOOLEAN;
7
BEGIN
8
-- Look for existing role assignment for user
9
SELECT id, is_deleted
10
INTO v_existing_id, v_is_deleted
11
FROM public.user_roles
12
WHERE user_id = p_user_id
13
AND role_id = p_role_id
14
LIMIT 1;
15
16
-- Case 1: Record exists and active -> do nothing
17
IF FOUND AND v_is_deleted = false THEN
18
RAISE NOTICE 'Role % already active for user %, skipping insert', p_role_id, p_user_id;
19
20
-- Case 2: Record exists but soft-deleted -> reactivate
21
ELSIF FOUND AND v_is_deleted = true THEN
22
UPDATE public.user_roles
23
SET is_deleted = false,
24
modified_on_utc = NOW(),
25
modified_by = p_created_by,
26
deleted_on_utc = NULL
27
WHERE id = v_existing_id;
28
29
RAISE NOTICE 'Role % re-activated for user % (id=%)', p_role_id, p_user_id, v_existing_id;
30
31
-- Case 3: No record - insert new
32
ELSE
33
INSERT INTO public.user_roles (
34
user_id,
35
role_id,
36
created_on_utc,
37
created_by,
38
is_deleted,
39
start_date,
40
end_date
41
)
42
VALUES (
43
p_user_id,
44
p_role_id,
45
NOW(),
46
p_created_by,
47
false,
48
NOW(), -- Fixed start_date as now()
49
'9999-12-31 00:00:00'::timestamp -- Fixed end_date as default far future
50
);
51
52
RAISE NOTICE 'Role % assigned to user % (new)', p_role_id, p_user_id;
53
END IF;
54
END;
55
$procedure$
|
|||||
| Procedure | insert_user_roles | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_user_roles(IN p_user_id uuid, IN p_role_ids integer[], IN p_organization_id uuid, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_role_id integer;
6
v_existing_id INT;
7
v_is_deleted BOOLEAN;
8
BEGIN
9
FOREACH v_role_id IN ARRAY p_role_ids
10
LOOP
11
-- Find matching user-role-org record
12
SELECT id, is_deleted
13
INTO v_existing_id, v_is_deleted
14
FROM public.user_roles
15
WHERE user_id = p_user_id
16
AND role_id = v_role_id
17
AND organization_id = p_organization_id
18
LIMIT 1;
19
20
-- If active record exists -> do nothing
21
IF FOUND AND v_is_deleted = false THEN
22
RAISE NOTICE 'Role % already active for user % in organization %, skipping insert', v_role_id, p_user_id, p_organization_id;
23
24
-- If found and soft-deleted -> reactivate
25
ELSIF FOUND AND v_is_deleted = true THEN
26
UPDATE public.user_roles
27
SET is_deleted = false,
28
modified_on_utc = NOW(),
29
modified_by = p_created_by,
30
deleted_on_utc = NULL
31
WHERE id = v_existing_id;
32
RAISE NOTICE 'Role % re-activated for user % in organization % (id=%)', v_role_id, p_user_id, p_organization_id, v_existing_id;
33
34
-- No record exists, insert new
35
ELSE
36
INSERT INTO public.user_roles (
37
user_id,
38
role_id,
39
organization_id,
40
created_on_utc,
41
created_by,
42
is_deleted,
43
start_date,
44
end_date
45
)
46
VALUES (
47
p_user_id,
48
v_role_id,
49
p_organization_id,
50
NOW(),
51
p_created_by,
52
false,
53
NOW(),
54
'9999-12-31 00:00:00'::timestamp
55
);
56
RAISE NOTICE 'Role % assigned to user % in organization % (new)', v_role_id, p_user_id, p_organization_id;
57
END IF;
58
END LOOP;
59
END;
60
$procedure$
|
|||||
| Procedure | migrate_opening_balances_for_organization | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.migrate_opening_balances_for_organization(IN p_organization_id uuid, IN p_finyear_id integer, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_status_id integer := 1;
6
v_transaction_source_type_id integer := 18; -- as per your instruction
7
v_description_template_id integer := 1;
8
v_entry_source_id integer := 1;
9
v_opening_equity uuid;
10
v_sundry_debtors uuid;
11
v_sundry_creditors uuid;
12
v_salary_payable uuid;
13
14
rec_company RECORD;
15
ob_rec RECORD;
16
v_transaction_id bigint;
17
v_document_number text;
18
v_transaction_date timestamp;
19
v_entry_type text;
20
v_entity_type text;
21
v_entity_id uuid;
22
v_main_account uuid;
23
v_should_insert boolean;
24
v_name text;
25
v_customer_id uuid;
26
v_vendor_id uuid;
27
v_employee_id uuid;
28
v_account_id uuid;
29
v_year_label text;
30
BEGIN
31
-- COA account IDs
32
SELECT id INTO v_opening_equity
33
FROM public.chart_of_accounts
34
WHERE account_type_id = (SELECT account_type_id FROM public.chart_of_accounts WHERE name = 'Opening Balance Equity' LIMIT 1)
35
AND name = 'Opening Balance Equity'
36
LIMIT 1;
37
IF v_opening_equity IS NULL THEN
38
RAISE EXCEPTION 'Chart of Accounts "Opening Balance Equity" not found!';
39
END IF;
40
41
SELECT accounts_receivable_account_id, accounts_payable_account_id, salary_payable_account_id INTO v_sundry_debtors, v_sundry_creditors, v_salary_payable
42
FROM public.organization_accounts
43
WHERE organization_id = p_organization_id;
44
45
IF v_sundry_debtors IS NULL THEN
46
RAISE EXCEPTION 'Chart of Accounts "Sundry Debtors" not found!';
47
END IF;
48
49
IF v_sundry_creditors IS NULL THEN
50
RAISE EXCEPTION 'Chart of Accounts "Sundry Creditors" not found!';
51
END IF;
52
53
IF v_salary_payable IS NULL THEN
54
RAISE EXCEPTION 'Chart of Accounts "Salary Payable" not found!';
55
END IF;
56
57
-- Set transaction_date and year label from the finance year
58
SELECT start_date, "year" INTO v_transaction_date, v_year_label FROM public.finance_year WHERE id = p_finyear_id;
59
IF v_transaction_date IS NULL OR v_year_label IS NULL THEN
60
RAISE EXCEPTION 'Finance year with id % not found or year column is NULL!', p_finyear_id;
61
END IF;
62
63
-- Create a temporary table to track inserted COA accounts
64
CREATE TEMP TABLE IF NOT EXISTS temp_inserted_coa_accounts (account_id uuid PRIMARY KEY);
65
66
-- For each company in the organization, with the selected finance year
67
FOR rec_company IN
68
SELECT c.id AS company_id
69
FROM public.companies c
70
JOIN public.company_finance_year cfy ON c.id = cfy.company_id
71
WHERE c.organization_id = p_organization_id
72
AND cfy.finance_year_id = p_finyear_id
73
LOOP
74
-- For each opening balance for this organization and year
75
FOR ob_rec IN
76
SELECT *
77
FROM public.opening_balances
78
WHERE organization_id = p_organization_id
79
AND finyear_id = p_finyear_id
80
LOOP
81
v_should_insert := false;
82
v_name := null;
83
v_customer_id := null;
84
v_vendor_id := null;
85
v_employee_id := null;
86
v_account_id := null;
87
88
-- Determine main account and entity type, and check mapping
89
IF ob_rec.customer_id IS NOT NULL THEN
90
v_entity_type := 'Customer';
91
v_entity_id := ob_rec.customer_id;
92
v_main_account := v_sundry_debtors;
93
94
-- Check customers.company_id = rec_company.company_id
95
SELECT c.name INTO v_name
96
FROM public.customers c
97
WHERE c.id = ob_rec.customer_id AND c.company_id = rec_company.company_id;
98
IF v_name IS NOT NULL THEN
99
v_should_insert := true;
100
v_document_number := v_name; -- Only the name, not 'OPENBAL-' || v_name
101
v_customer_id := ob_rec.customer_id;
102
END IF;
103
104
ELSIF ob_rec.vendor_id IS NOT NULL THEN
105
v_entity_type := 'Vendor';
106
v_entity_id := ob_rec.vendor_id;
107
v_main_account := v_sundry_creditors;
108
109
SELECT v.name INTO v_name
110
FROM public.vendors v
111
WHERE v.id = ob_rec.vendor_id AND v.company_id = rec_company.company_id;
112
IF v_name IS NOT NULL THEN
113
v_should_insert := true;
114
v_document_number := v_name; -- Only the name, not 'OPENBAL-' || v_name
115
v_vendor_id := ob_rec.vendor_id;
116
END IF;
117
118
ELSIF ob_rec.employee_id IS NOT NULL THEN
119
v_entity_type := 'Employee';
120
v_entity_id := ob_rec.employee_id;
121
v_main_account := v_salary_payable;
122
123
SELECT e.first_name INTO v_name
124
FROM public.employees e
125
WHERE e.id = ob_rec.employee_id AND e.company_id = rec_company.company_id;
126
IF v_name IS NOT NULL THEN
127
v_should_insert := true;
128
v_document_number := v_name; -- Only the name, not 'OPENBAL-' || v_name
129
v_employee_id := ob_rec.employee_id;
130
END IF;
131
132
ELSE
133
-- COA-only
134
v_entity_type := 'COA';
135
v_entity_id := ob_rec.account_id;
136
v_main_account := ob_rec.account_id;
137
138
-- Get COA name
139
SELECT c.name INTO v_name
140
FROM public.chart_of_accounts c
141
WHERE c.id = ob_rec.account_id;
142
IF v_name IS NOT NULL THEN
143
v_document_number := v_name; -- Only the name, not 'OPENBAL-' || v_name
144
v_account_id := ob_rec.account_id;
145
-- insert only if this coa id is not already present
146
IF NOT EXISTS (SELECT 1 FROM temp_inserted_coa_accounts WHERE account_id = v_account_id) THEN
147
v_should_insert := true;
148
INSERT INTO temp_inserted_coa_accounts(account_id) VALUES (v_account_id);
149
END IF;
150
END IF;
151
END IF;
152
153
IF v_should_insert THEN
154
-- Insert into transaction_headers
155
INSERT INTO public.transaction_headers (
156
company_id, transaction_date, status_id, document_number, description, created_on_utc, created_by, transaction_source_type,
157
customer_id, vendor_id, employee_id, document_id
158
)
159
VALUES (
160
rec_company.company_id,
161
v_transaction_date,
162
v_status_id,
163
v_document_number,
164
'Opening Balance for ' || v_year_label, -- Only the year label from finance_year
165
v_transaction_date,
166
p_created_by,
167
v_transaction_source_type_id,
168
v_customer_id,
169
v_vendor_id,
170
v_employee_id,
171
v_account_id
172
)
173
RETURNING id INTO v_transaction_id;
174
175
-- Insert into journal_entries (double-entry)
176
INSERT INTO public.journal_entries (
177
transaction_id, transaction_date, amount, entry_type, description_template_id, dynamic_data, entry_source_id, account_id
178
) VALUES (
179
v_transaction_id, v_transaction_date, ob_rec.balance, ob_rec.entry_type, v_description_template_id, '{}'::jsonb, v_entry_source_id, v_main_account
180
);
181
182
-- Opening Balance Equity
183
IF ob_rec.entry_type = 'D' THEN
184
v_entry_type := 'C';
185
ELSE
186
v_entry_type := 'D';
187
END IF;
188
INSERT INTO public.journal_entries (
189
transaction_id, transaction_date, amount, entry_type, description_template_id, dynamic_data, entry_source_id, account_id
190
) VALUES (
191
v_transaction_id, v_transaction_date, ob_rec.balance, v_entry_type, v_description_template_id, '{}'::jsonb, v_entry_source_id, v_opening_equity
192
);
193
END IF;
194
END LOOP;
195
END LOOP;
196
197
DROP TABLE IF EXISTS temp_inserted_coa_accounts;
198
END;
199
$procedure$
|
|||||
| Procedure | update_bank_transfers | Match | ||||||
| Procedure | upsert_user_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.upsert_user_permissions(IN p_user_id uuid, IN p_permission_ids uuid[], IN p_organization_id uuid, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_permission_id uuid;
6
v_existing_id int;
7
v_existing_deleted bool;
8
v_inserted_id int;
9
BEGIN
10
FOR v_permission_id IN SELECT unnest(p_permission_ids) LOOP
11
SELECT id, is_deleted INTO v_existing_id, v_existing_deleted
12
FROM public.user_permissions
13
WHERE user_id = p_user_id
14
AND permission_id = v_permission_id
15
AND organization_id = p_organization_id
16
FOR UPDATE;
17
18
IF v_existing_id IS NOT NULL AND NOT v_existing_deleted THEN
19
CONTINUE;
20
ELSIF v_existing_id IS NOT NULL AND v_existing_deleted THEN
21
UPDATE public.user_permissions
22
SET is_deleted = false,
23
deleted_on_utc = NULL,
24
modified_by = p_created_by,
25
modified_on_utc = NOW()
26
WHERE id = v_existing_id;
27
ELSE
28
INSERT INTO public.user_permissions (
29
user_id,
30
permission_id,
31
organization_id,
32
created_on_utc,
33
created_by,
34
is_deleted
35
) VALUES (
36
p_user_id,
37
v_permission_id,
38
p_organization_id,
39
NOW(),
40
p_created_by,
41
false
42
) RETURNING id INTO v_inserted_id;
43
END IF;
44
END LOOP;
45
END;
46
$procedure$
|
|||||
| Procedure | purge_common_organization_data | Match | ||||||
| Procedure | insert_user_permission | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_user_permission(IN p_user_id uuid, IN p_organization_id uuid, IN p_permission_ids uuid[], IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_permission_id uuid;
6
v_existing_id INT;
7
v_is_deleted BOOLEAN;
8
BEGIN
9
RAISE NOTICE '▶️ START insert_user_permission for user_id=%, org_id=%, created_by=%, permissions=%',
10
p_user_id, p_organization_id, p_created_by, p_permission_ids;
11
12
IF p_user_id IS NULL OR p_organization_id IS NULL OR p_created_by IS NULL THEN
13
RAISE EXCEPTION '❌ One of the input parameters is NULL (user_id=%, org_id=%, created_by=%)',
14
p_user_id, p_organization_id, p_created_by;
15
END IF;
16
17
FOREACH v_permission_id IN ARRAY p_permission_ids
18
LOOP
19
RAISE NOTICE '🔍 Checking permission_id=% for user_id=% in org_id=%',
20
v_permission_id, p_user_id, p_organization_id;
21
22
-- Check if the record exists
23
SELECT id, is_deleted
24
INTO v_existing_id, v_is_deleted
25
FROM public.user_permissions
26
WHERE user_id = p_user_id
27
AND organization_id = p_organization_id
28
AND permission_id = v_permission_id
29
LIMIT 1;
30
31
IF FOUND THEN
32
RAISE NOTICE '✅ Found existing record with id=%, is_deleted=%', v_existing_id, v_is_deleted;
33
ELSE
34
RAISE NOTICE '❌ No existing record found for this combination';
35
END IF;
36
37
IF FOUND AND v_is_deleted = false THEN
38
RAISE NOTICE '⚠️ Permission % already active for user %, skipping insert',
39
v_permission_id, p_user_id;
40
41
ELSIF FOUND AND v_is_deleted = true THEN
42
UPDATE public.user_permissions
43
SET is_deleted = false,
44
modified_on_utc = NOW(),
45
modified_by = p_created_by,
46
deleted_on_utc = NULL
47
WHERE id = v_existing_id;
48
49
RAISE NOTICE '♻️ Permission % re-activated for user % (id=%)',
50
v_permission_id, p_user_id, v_existing_id;
51
52
ELSE
53
RAISE NOTICE '✅ Inserting Permission % assigned to user % (new) org: %', v_permission_id, p_user_id, p_organization_id;
54
INSERT INTO public.user_permissions (
55
user_id,
56
organization_id,
57
permission_id,
58
created_on_utc,
59
created_by,
60
is_deleted
61
)
62
VALUES (
63
p_user_id,
64
p_organization_id,
65
v_permission_id,
66
NOW(),
67
p_created_by,
68
false
69
);
70
71
72
END IF;
73
END LOOP;
74
75
RAISE NOTICE '✅ Completed insert_user_permission for user_id=%', p_user_id;
76
END;
77
$procedure$
|
|||||
| Procedure | upsert_user_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.upsert_user_permissions(IN p_user_id uuid, IN p_permission_ids uuid[], IN p_role_ids integer[], IN p_organization_id uuid, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_permission_id uuid;
6
v_role_id int;
7
v_existing_id int;
8
v_existing_deleted bool;
9
v_existing_role_id int;
10
BEGIN
11
-- 0️⃣ Soft-delete permissions NOT in the new list
12
UPDATE public.user_permissions
13
SET is_deleted = TRUE,
14
deleted_on_utc = NOW(),
15
modified_by = p_created_by,
16
modified_on_utc = NOW()
17
WHERE user_id = p_user_id
18
AND organization_id = p_organization_id
19
AND (p_permission_ids IS NULL OR permission_id <> ALL(p_permission_ids))
20
AND is_deleted = FALSE;
21
22
-- 1️⃣ Upsert (reactivate or insert) listed permissions
23
FOR v_permission_id IN SELECT unnest(p_permission_ids) LOOP
24
SELECT id, is_deleted
25
INTO v_existing_id, v_existing_deleted
26
FROM public.user_permissions
27
WHERE user_id = p_user_id
28
AND permission_id = v_permission_id
29
AND organization_id = p_organization_id
30
FOR UPDATE;
31
32
IF v_existing_id IS NOT NULL AND NOT v_existing_deleted THEN
33
CONTINUE;
34
ELSIF v_existing_id IS NOT NULL AND v_existing_deleted THEN
35
UPDATE public.user_permissions
36
SET is_deleted = FALSE,
37
deleted_on_utc = NULL,
38
modified_by = p_created_by,
39
modified_on_utc = NOW()
40
WHERE id = v_existing_id;
41
ELSE
42
INSERT INTO public.user_permissions (
43
user_id, permission_id, organization_id,
44
created_on_utc, created_by, is_deleted
45
) VALUES (
46
p_user_id, v_permission_id, p_organization_id,
47
NOW(), p_created_by, false
48
);
49
END IF;
50
END LOOP;
51
52
-- 2️⃣ Soft-delete roles NOT in the new list
53
UPDATE public.user_roles
54
SET is_deleted = TRUE,
55
deleted_on_utc = NOW(),
56
modified_by = p_created_by,
57
modified_on_utc = NOW()
58
WHERE user_id = p_user_id
59
AND organization_id = p_organization_id
60
AND (p_role_ids IS NULL OR role_id <> ALL(p_role_ids))
61
AND is_deleted = FALSE;
62
63
-- 3️⃣ Upsert (reactivate or insert) listed roles
64
FOR v_role_id IN SELECT unnest(p_role_ids) LOOP
65
SELECT id
66
INTO v_existing_role_id
67
FROM public.user_roles
68
WHERE user_id = p_user_id
69
AND role_id = v_role_id
70
AND organization_id = p_organization_id;
71
72
IF v_existing_role_id IS NULL THEN
73
INSERT INTO public.user_roles (
74
user_id, role_id, organization_id,
75
created_on_utc, created_by, start_date, is_deleted
76
) VALUES (
77
p_user_id, v_role_id, p_organization_id,
78
NOW(), p_created_by, NOW(), FALSE
79
);
80
ELSE
81
UPDATE public.user_roles
82
SET is_deleted = FALSE,
83
deleted_on_utc = NULL,
84
modified_by = p_created_by,
85
modified_on_utc = NOW()
86
WHERE id = v_existing_role_id
87
AND is_deleted = TRUE;
88
END IF;
89
END LOOP;
90
END;
91
$procedure$
|
|||||
| Procedure | upsert_user_permission_template | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.upsert_user_permission_template(IN p_user_id uuid, IN p_permission_template_ids integer[], IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_permission_id uuid;
6
v_existing_up_id int;
7
v_existing_up_deleted boolean;
8
v_inserted_up_id int;
9
v_org_user_id uuid;
10
BEGIN
11
-- nothing to do if no templates provided
12
IF p_permission_template_ids IS NULL OR array_length(p_permission_template_ids, 1) IS NULL THEN
13
RETURN;
14
END IF;
15
16
----------------------------------------------------------------
17
-- 1) Ensure organization_users row exists for this user + org
18
-- (Do this before touching user_permissions)
19
----------------------------------------------------------------
20
SELECT id
21
INTO v_org_user_id
22
FROM public.organization_users
23
WHERE user_id = p_user_id
24
AND organization_id IS NOT DISTINCT FROM p_organization_id
25
FOR UPDATE;
26
27
IF v_org_user_id IS NULL THEN
28
INSERT INTO public.organization_users (
29
id,
30
user_id,
31
effective_start_date,
32
effective_end_date,
33
created_on_utc,
34
is_deleted,
35
created_by,
36
organization_id
37
) VALUES (
38
gen_random_uuid(), -- requires pgcrypto (or replace with uuid_generate_v4())
39
p_user_id,
40
NOW(), -- effective_start_date
41
NOW() + INTERVAL '1 year', -- effective_end_date
42
NOW(), -- created_on_utc
43
false, -- is_deleted
44
p_created_by,
45
COALESCE(p_organization_id, '00000000-0000-0000-0000-000000000000'::uuid)
46
)
47
RETURNING id INTO v_org_user_id;
48
END IF;
49
50
----------------------------------------------------------------
51
-- 2) Upsert user_permissions for permission_ids from templates
52
----------------------------------------------------------------
53
FOR v_permission_id IN
54
SELECT DISTINCT ptm.permission_id
55
FROM public.permission_template_mappings ptm
56
WHERE ptm.permission_template_id = ANY (p_permission_template_ids)
57
LOOP
58
-- Look for an existing user_permissions row for this user, permission and organization.
59
SELECT id, is_deleted
60
INTO v_existing_up_id, v_existing_up_deleted
61
FROM public.user_permissions
62
WHERE user_id = p_user_id
63
AND permission_id = v_permission_id
64
AND organization_id IS NOT DISTINCT FROM p_organization_id
65
FOR UPDATE;
66
67
-- If it exists and is active, nothing to do.
68
IF v_existing_up_id IS NOT NULL AND NOT v_existing_up_deleted THEN
69
CONTINUE;
70
71
-- If it exists and is marked deleted, reactivate it.
72
ELSIF v_existing_up_id IS NOT NULL AND v_existing_up_deleted THEN
73
UPDATE public.user_permissions
74
SET is_deleted = false,
75
deleted_on_utc = NULL,
76
modified_by = p_created_by,
77
modified_on_utc = NOW()
78
WHERE id = v_existing_up_id;
79
80
-- Otherwise insert a new row.
81
ELSE
82
INSERT INTO public.user_permissions (
83
user_id,
84
permission_id,
85
organization_id,
86
created_on_utc,
87
created_by,
88
is_deleted
89
) VALUES (
90
p_user_id,
91
v_permission_id,
92
p_organization_id,
93
NOW(),
94
p_created_by,
95
false
96
) RETURNING id INTO v_inserted_up_id;
97
END IF;
98
END LOOP;
99
END;
100
$procedure$
|
|||||
| Procedure | insert_into_bank_transfers | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_into_bank_transfers(IN p_company_id uuid, IN p_bank_transfer_id uuid, IN p_transfer_date date, IN p_amount numeric, IN p_description text, IN p_source_account_id uuid, IN p_target_account_id uuid, IN p_mode_of_payment integer, IN p_reference text, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_transfer_number text;
6
v_transaction_id bigint;
7
v_narration_id bigint;
8
BEGIN
9
-- 1️⃣ Generate transfer number
10
v_transfer_number :=
11
get_new_transfer_number(p_company_id, p_transfer_date);
12
13
-- 2️⃣ Insert bank transfer
14
INSERT INTO public.bank_transfers (
15
id,
16
company_id,
17
source_account_id,
18
target_account_id,
19
amount,
20
transfer_date,
21
mode_of_payment,
22
reference,
23
description,
24
is_bulk,
25
created_by,
26
created_on_utc,
27
is_deleted,
28
transfer_number
29
)
30
VALUES (
31
p_bank_transfer_id,
32
p_company_id,
33
p_source_account_id,
34
p_target_account_id,
35
p_amount,
36
p_transfer_date,
37
COALESCE(p_mode_of_payment, 0),
38
NULLIF(p_reference, ''),
39
p_description,
40
FALSE,
41
p_created_by,
42
CURRENT_TIMESTAMP,
43
FALSE,
44
v_transfer_number
45
);
46
47
-- 3️⃣ Insert transaction header
48
INSERT INTO public.transaction_headers (
49
company_id,
50
transaction_date,
51
transaction_source_type,
52
status_id,
53
document_id,
54
document_number,
55
description,
56
created_by,
57
created_on_utc
58
)
59
VALUES (
60
p_company_id,
61
p_transfer_date,
62
19, -- BANK_TRANSFER
63
1,
64
p_bank_transfer_id,
65
v_transfer_number,
66
p_description,
67
p_created_by,
68
CURRENT_TIMESTAMP
69
)
70
RETURNING id INTO v_transaction_id;
71
72
-- 4️⃣ Create narration ONCE
73
INSERT INTO public.journal_narrations (
74
transaction_date,
75
description_template_id,
76
dynamic_data,
77
created_on_utc,
78
created_by
79
)
80
VALUES (
81
p_transfer_date,
82
49, -- BANK_TRANSFER narration template
83
jsonb_build_object(
84
'type', 'bank_transfer',
85
'reference', p_reference
86
)::text,
87
CURRENT_TIMESTAMP,
88
p_created_by
89
)
90
RETURNING id INTO v_narration_id;
91
92
-- 5️⃣ Credit source account
93
INSERT INTO public.journal_entries (
94
transaction_id,
95
transaction_date,
96
account_id,
97
amount,
98
entry_type,
99
entry_source_id,
100
journal_narration_id
101
)
102
VALUES (
103
v_transaction_id,
104
p_transfer_date,
105
p_source_account_id,
106
p_amount,
107
'C',
108
2,
109
v_narration_id
110
);
111
112
-- 6️⃣ Debit target account
113
INSERT INTO public.journal_entries (
114
transaction_id,
115
transaction_date,
116
account_id,
117
amount,
118
entry_type,
119
entry_source_id,
120
journal_narration_id
121
)
122
VALUES (
123
v_transaction_id,
124
p_transfer_date,
125
p_target_account_id,
126
p_amount,
127
'D',
128
2,
129
v_narration_id
130
);
131
132
END;
133
$procedure$
|
|||||
| Procedure | update_existing_user_role_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_existing_user_role_permissions(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_permission_ids uuid[] DEFAULT NULL::uuid[], IN p_role_ids integer[] DEFAULT NULL::integer[])
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_role_permission_ids uuid[];
6
v_final_permission_ids uuid[];
7
BEGIN
8
RAISE NOTICE 'Procedure started for user_id=%', p_user_id;
9
10
-- Insert user to organization
11
IF p_organization_id IS NOT NULL THEN
12
RAISE NOTICE 'Inserting user into organization: %', p_organization_id;
13
14
CALL public.insert_organization_user(
15
p_user_id,
16
p_created_by,
17
p_organization_id
18
);
19
END IF;
20
21
-- Insert user to company
22
IF p_company_id IS NOT NULL THEN
23
RAISE NOTICE 'Inserting user into company: %', p_company_id;
24
25
CALL public.insert_company_user(
26
p_user_id,
27
p_created_by,
28
p_company_id
29
);
30
END IF;
31
32
/* ---------------------------------------------
33
1. Fetch permission_ids from role_permissions
34
--------------------------------------------- */
35
IF p_role_ids IS NOT NULL THEN
36
RAISE NOTICE 'Fetching permissions for roles: %', p_role_ids;
37
38
SELECT ARRAY_AGG(DISTINCT rp.permission_id)
39
INTO v_role_permission_ids
40
FROM public.role_permissions rp
41
WHERE rp.role_id = ANY (p_role_ids)
42
AND rp.is_deleted = false;
43
44
RAISE NOTICE 'Permissions from roles: %', v_role_permission_ids;
45
END IF;
46
47
/* ---------------------------------------------
48
2. Merge direct permissions + role permissions
49
--------------------------------------------- */
50
v_final_permission_ids :=
51
ARRAY(
52
SELECT DISTINCT unnest(
53
COALESCE(p_permission_ids, '{}') ||
54
COALESCE(v_role_permission_ids, '{}')
55
)
56
);
57
58
RAISE NOTICE 'Final permission list: %', v_final_permission_ids;
59
60
/* ---------------------------------------------
61
3. Insert permissions to user
62
--------------------------------------------- */
63
IF v_final_permission_ids IS NOT NULL
64
AND array_length(v_final_permission_ids, 1) > 0
65
AND p_organization_id IS NOT NULL THEN
66
67
RAISE NOTICE 'Inserting permissions for user into organization %', p_organization_id;
68
69
CALL public.insert_user_permission(
70
p_user_id,
71
p_organization_id,
72
v_final_permission_ids,
73
p_created_by
74
);
75
ELSE
76
RAISE NOTICE 'No permissions inserted (empty list or organization missing)';
77
END IF;
78
79
/* ---------------------------------------------
80
4. Insert user roles
81
--------------------------------------------- */
82
IF p_role_ids IS NOT NULL THEN
83
RAISE NOTICE 'Assigning roles to user: %', p_role_ids;
84
85
CALL public.insert_user_roles(
86
p_user_id,
87
p_role_ids,
88
p_organization_id,
89
p_created_by
90
);
91
END IF;
92
93
RAISE NOTICE 'Procedure completed successfully for user_id=%', p_user_id;
94
END;
95
$procedure$
|
|||||
| Procedure | update_existing_user_role_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_existing_user_role_permissions(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_role_ids integer[] DEFAULT NULL::integer[])
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_role_permission_ids uuid[];
6
BEGIN
7
RAISE NOTICE 'UPDATE procedure started for user_id=%', p_user_id;
8
9
/* ---------------------------------------------
10
Ensure user-organization & company mapping
11
--------------------------------------------- */
12
IF p_organization_id IS NOT NULL THEN
13
RAISE NOTICE 'Ensuring user exists in organization: %', p_organization_id;
14
15
CALL public.insert_organization_user(
16
p_user_id,
17
p_created_by,
18
p_organization_id
19
);
20
END IF;
21
22
IF p_company_id IS NOT NULL THEN
23
RAISE NOTICE 'Ensuring user exists in company: %', p_company_id;
24
25
CALL public.insert_company_user(
26
p_user_id,
27
p_created_by,
28
p_company_id
29
);
30
END IF;
31
32
/* ---------------------------------------------
33
1. Fetch permissions ONLY from roles
34
--------------------------------------------- */
35
IF p_role_ids IS NOT NULL THEN
36
RAISE NOTICE 'Roles received for update: %', p_role_ids;
37
38
SELECT ARRAY_AGG(DISTINCT rp.permission_id)
39
INTO v_role_permission_ids
40
FROM public.role_permissions rp
41
WHERE rp.role_id = ANY (p_role_ids)
42
AND rp.is_deleted = false;
43
44
RAISE NOTICE 'Permissions derived from roles: %', v_role_permission_ids;
45
ELSE
46
RAISE NOTICE 'No roles provided, skipping permission derivation';
47
END IF;
48
49
/* ---------------------------------------------
50
2. Update user permissions (role-based only)
51
--------------------------------------------- */
52
IF v_role_permission_ids IS NOT NULL
53
AND array_length(v_role_permission_ids, 1) > 0
54
AND p_organization_id IS NOT NULL THEN
55
56
RAISE NOTICE 'Updating user permissions using role-based permissions';
57
58
CALL public.insert_user_permission(
59
p_user_id,
60
p_organization_id,
61
v_role_permission_ids,
62
p_created_by
63
);
64
ELSE
65
RAISE NOTICE 'No role-based permissions to apply';
66
END IF;
67
68
/* ---------------------------------------------
69
3. Update user roles
70
--------------------------------------------- */
71
IF p_role_ids IS NOT NULL THEN
72
RAISE NOTICE 'Updating user roles: %', p_role_ids;
73
74
CALL public.insert_user_roles(
75
p_user_id,
76
p_role_ids,
77
p_organization_id,
78
p_created_by
79
);
80
END IF;
81
82
RAISE NOTICE 'UPDATE procedure completed successfully for user_id=%', p_user_id;
83
END;
84
$procedure$
|
|||||
| Procedure | update_existing_users_role_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_existing_users_role_permissions(IN p_user_ids uuid[], IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_role_ids integer[] DEFAULT NULL::integer[])
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_user_id uuid;
6
v_role_permission_ids uuid[];
7
BEGIN
8
RAISE NOTICE 'UPDATE procedure started for users=%', p_user_ids;
9
10
/* ---------------------------------------------
11
1. Fetch permissions ONLY from roles (once)
12
--------------------------------------------- */
13
IF p_role_ids IS NOT NULL THEN
14
RAISE NOTICE 'Roles received: %', p_role_ids;
15
16
SELECT ARRAY_AGG(DISTINCT rp.permission_id)
17
INTO v_role_permission_ids
18
FROM public.role_permissions rp
19
WHERE rp.role_id = ANY (p_role_ids)
20
AND rp.is_deleted = false;
21
22
RAISE NOTICE 'Permissions derived from roles: %', v_role_permission_ids;
23
ELSE
24
RAISE NOTICE 'No roles provided, skipping permission derivation';
25
END IF;
26
27
/* ---------------------------------------------
28
2. Loop through each user
29
--------------------------------------------- */
30
FOREACH v_user_id IN ARRAY p_user_ids
31
LOOP
32
RAISE NOTICE 'Processing user_id=%', v_user_id;
33
34
-- Ensure user-organization mapping
35
IF p_organization_id IS NOT NULL THEN
36
CALL public.insert_organization_user(
37
v_user_id,
38
p_created_by,
39
p_organization_id
40
);
41
END IF;
42
43
-- Ensure user-company mapping
44
IF p_company_id IS NOT NULL THEN
45
CALL public.insert_company_user(
46
v_user_id,
47
p_created_by,
48
p_company_id
49
);
50
END IF;
51
52
-- Assign role-based permissions
53
IF v_role_permission_ids IS NOT NULL
54
AND array_length(v_role_permission_ids, 1) > 0
55
AND p_organization_id IS NOT NULL THEN
56
57
CALL public.insert_user_permission(
58
v_user_id,
59
p_organization_id,
60
v_role_permission_ids,
61
p_created_by
62
);
63
END IF;
64
65
-- Assign roles
66
IF p_role_ids IS NOT NULL THEN
67
CALL public.insert_user_roles(
68
v_user_id,
69
p_role_ids,
70
p_organization_id,
71
p_created_by
72
);
73
END IF;
74
75
RAISE NOTICE 'Completed user_id=%', v_user_id;
76
END LOOP;
77
78
RAISE NOTICE 'UPDATE procedure completed for all users';
79
END;
80
$procedure$
|
|||||
| Procedure | add_existing_user_role_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.add_existing_user_role_permissions(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_permission_ids uuid[] DEFAULT NULL::uuid[], IN p_role_ids integer[] DEFAULT NULL::integer[])
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
-- Insert user to organization if organization_id is provided
6
IF p_organization_id IS NOT NULL THEN
7
CALL public.insert_organization_user(p_user_id, p_created_by, p_organization_id);
8
END IF;
9
10
-- Insert user to company if company_id is provided
11
IF p_company_id IS NOT NULL THEN
12
CALL public.insert_company_user(p_user_id, p_created_by, p_company_id);
13
END IF;
14
15
-- Insert permissions if both permission_ids array and organization_id are provided
16
IF p_permission_ids IS NOT NULL AND p_organization_id IS NOT NULL THEN
17
CALL public.insert_user_permission(p_user_id, p_organization_id, p_permission_ids, p_created_by);
18
END IF;
19
20
-- Insert roles if role_ids array is provided
21
IF p_role_ids IS NOT NULL THEN
22
CALL public.insert_user_roles(p_user_id, p_role_ids, p_organization_id, p_created_by);
23
END IF;
24
END;
25
$procedure$
|
|||||
| Procedure | insert_multiple_bank_statements_by_excel | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_multiple_bank_statements_by_excel(IN p_statements jsonb)
1
CREATE OR REPLACE PROCEDURE public.insert_multiple_bank_statements_by_excel(IN p_statements jsonb)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
4
DECLARE
5
DECLARE
5
stmt RECORD;
6
stmt RECORD;
6
v_created_by UUID;
7
v_created_by UUID;
7
v_existing_id INT;
8
BEGIN
8
BEGIN
9
/* -------------------------------------------------
9
-- Use system user or default to a fallback ID if required
10
Resolve System user (mandatory fallback)
10
SELECT id INTO v_created_by
11
------------------------------------------------- */
11
FROM users
12
SELECT id
13
INTO v_created_by
14
FROM public.users
15
WHERE first_name = 'System'
12
WHERE first_name = 'System'
16
LIMIT 1;
13
LIMIT 1;
17
14
18
IF v_created_by IS NULL THEN
15
-- Loop through each bank statement in the JSONB array
19
RAISE EXCEPTION '❌ System user not found. created_by cannot be resolved.';
20
END IF;
21
22
/* -------------------------------------------------
23
Iterate input JSON
24
------------------------------------------------- */
25
FOR stmt IN
16
FOR stmt IN
26
SELECT *
17
SELECT * FROM jsonb_to_recordset(p_statements) AS (
27
FROM jsonb_to_recordset(p_statements) AS (
18
company_id UUID,
28
organization_id UUID,
19
txn_date DATE,
29
bank_id UUID,
30
txn_date TIMESTAMP,
31
cheque_number TEXT,
20
cheque_number TEXT,
32
description TEXT,
21
description TEXT,
33
value_date TIMESTAMP,
22
value_date DATE,
34
branch_code TEXT,
23
branch_code TEXT,
35
debit_amount NUMERIC,
24
debit_amount NUMERIC,
36
credit_amount NUMERIC,
25
credit_amount NUMERIC,
37
balance NUMERIC,
26
balance NUMERIC,
38
created_by UUID
27
bank_id UUID
39
)
28
)
40
LOOP
29
LOOP
41
BEGIN
30
BEGIN
42
/* -------------------------------------------------
31
-- Check for existing bank statement with same txn_date and description
43
Hard validations (NO silent corruption)
32
IF NOT EXISTS (
44
------------------------------------------------- */
33
SELECT 1
45
IF stmt.organization_id IS NULL THEN
34
FROM public.bank_statements
46
RAISE EXCEPTION
35
WHERE company_id = stmt.company_id
47
'organization_id cannot be NULL (txn_date=%, desc=%)',
36
AND bank_id = stmt.bank_id
48
stmt.txn_date, stmt.description;
37
AND txn_date = stmt.txn_date
49
END IF;
38
AND description = stmt.description
50
39
AND is_deleted = false
51
IF stmt.bank_id IS NULL THEN
40
) THEN
52
RAISE EXCEPTION
41
-- Insert new bank statement
53
'bank_id cannot be NULL (txn_date=%, desc=%)',
54
stmt.txn_date, stmt.description;
55
END IF;
56
57
IF stmt.created_by IS NULL THEN
58
stmt.created_by := v_created_by;
59
END IF;
60
61
/* -------------------------------------------------
62
Detect existing row (idempotent key)
63
------------------------------------------------- */
64
SELECT id
65
INTO v_existing_id
66
FROM public.bank_statements bs
67
WHERE bs.organization_id = stmt.organization_id
68
AND bs.bank_id = stmt.bank_id
69
AND bs.is_deleted = FALSE
70
AND (
71
(stmt.txn_date::time <> '00:00:00'
72
AND bs.txn_date = stmt.txn_date)
73
OR
74
(stmt.txn_date::time = '00:00:00'
75
AND bs.txn_date::date = stmt.txn_date::date)
76
)
77
AND COALESCE(bs.debit_amount, 0) = COALESCE(stmt.debit_amount, 0)
78
AND COALESCE(bs.credit_amount, 0) = COALESCE(stmt.credit_amount, 0)
79
AND COALESCE(bs.cheque_number, '') = COALESCE(stmt.cheque_number, '')
80
AND COALESCE(bs.description, '') = COALESCE(stmt.description, '')
81
LIMIT 1;
82
83
/* -------------------------------------------------
84
INSERT
85
------------------------------------------------- */
86
IF v_existing_id IS NULL THEN
87
88
INSERT INTO public.bank_statements (
42
INSERT INTO public.bank_statements (
89
organization_id,
43
company_id,
90
bank_id,
91
txn_date,
44
txn_date,
92
cheque_number,
45
cheque_number,
93
description,
46
description,
94
value_date,
47
value_date,
95
branch_code,
48
branch_code,
96
debit_amount,
49
debit_amount,
97
credit_amount,
50
credit_amount,
98
balance,
51
balance,
99
has_reconciled,
52
bank_id,
100
created_by,
53
created_by,
101
created_on_utc,
54
created_on_utc,
102
is_deleted
55
is_deleted,
103
)
56
has_reconciled
104
VALUES (
57
) VALUES (
105
stmt.organization_id,
58
stmt.company_id,
106
stmt.bank_id,
107
stmt.txn_date,
59
stmt.txn_date,
108
stmt.cheque_number,
60
stmt.cheque_number,
109
stmt.description,
61
stmt.description,
110
stmt.value_date,
62
stmt.value_date,
111
stmt.branch_code,
63
stmt.branch_code,
112
stmt.debit_amount,
64
stmt.debit_amount,
113
stmt.credit_amount,
65
stmt.credit_amount,
114
stmt.balance,
66
stmt.balance,
115
FALSE,
67
stmt.bank_id,
116
stmt.created_by,
68
v_created_by,
117
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
69
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
118
FALSE
70
false,
71
false
119
);
72
);
120
121
RAISE NOTICE '✅ Inserted: %, %',
122
stmt.txn_date, stmt.description;
123
73
124
/* -------------------------------------------------
74
RAISE NOTICE 'Inserted bank statement for date: %, company: %', stmt.txn_date, stmt.company_id;
125
UPDATE (single-row safe)
126
------------------------------------------------- */
127
ELSE
75
ELSE
76
-- Update the existing statement
128
UPDATE public.bank_statements
77
UPDATE public.bank_statements
129
SET
78
SET
130
cheque_number = stmt.cheque_number,
79
cheque_number = stmt.cheque_number,
131
value_date = stmt.value_date,
80
value_date = stmt.value_date,
132
branch_code = stmt.branch_code,
81
branch_code = stmt.branch_code,
133
balance = COALESCE(stmt.balance, balance),
82
debit_amount = stmt.debit_amount,
134
modified_by = stmt.created_by,
83
credit_amount = stmt.credit_amount,
135
modified_on_utc =
84
balance = stmt.balance,
136
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp
85
modified_by = v_created_by,
137
WHERE id = v_existing_id;
86
modified_on_utc = (CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp
87
WHERE company_id = stmt.company_id
88
AND bank_id = stmt.bank_id
89
AND txn_date = stmt.txn_date
90
AND description = stmt.description
91
AND is_deleted = false;
138
92
139
RAISE NOTICE '🔄 Updated: %, %',
93
RAISE NOTICE 'Updated existing bank statement for date: %, company: %', stmt.txn_date, stmt.company_id;
140
stmt.txn_date, stmt.description;
141
END IF;
94
END IF;
142
95
143
EXCEPTION
96
EXCEPTION
144
WHEN OTHERS THEN
97
WHEN OTHERS THEN
145
RAISE EXCEPTION
98
RAISE EXCEPTION 'Error in processing statement for txn_date: %, description: %, Error: %', stmt.txn_date, stmt.description, SQLERRM;
146
'❌ Error for org=%, bank=%, txn_date=%, desc=% → %',
147
stmt.organization_id,
148
stmt.bank_id,
149
stmt.txn_date,
150
stmt.description,
151
SQLERRM;
152
END;
99
END;
153
END LOOP;
100
END LOOP;
154
101
155
RAISE NOTICE '🎉 All bank statements processed successfully.';
102
RAISE NOTICE 'All bank statements processed successfully';
156
END;
103
END;
157
$procedure$
104
$procedure$
|
|||||
| View | vw_permission_hierarchy | Missing in Target |
Source Script
Target Script
1
WITH RECURSIVE permission_tree AS (
2
SELECT p.id,
3
p.name,
4
p.parent_permission_id,
5
p.description,
6
1 AS level,
7
p.id AS root_permission_id
8
FROM permissions p
9
UNION ALL
10
SELECT c.id,
11
c.name,
12
c.parent_permission_id,
13
c.description,
14
(pt.level + 1) AS level,
15
pt.root_permission_id
16
FROM (permissions c
17
JOIN permission_tree pt ON ((c.parent_permission_id = pt.id)))
18
)
19
SELECT root_permission_id,
20
id AS permission_id,
21
name AS permission_name,
22
parent_permission_id,
23
description,
24
level
25
FROM permission_tree;
|
|||||
| View | vw_organization_summary | Missing in Target |
Source Script
Target Script
1
SELECT DISTINCT o.id AS organization_id,
2
o.name AS organization_name,
3
c.id AS company_id,
4
c.name AS company_name,
5
u.id AS user_id,
6
concat(u.first_name, ' ', u.last_name) AS user_full_name,
7
u.email AS user_email,
8
u.phone_number AS user_phone,
9
'Company User'::text AS user_role_source,
10
o.created_on_utc AS organization_created_on
11
FROM (((organizations o
12
JOIN companies c ON ((c.organization_id = o.id)))
13
JOIN company_users cu ON ((cu.company_id = c.id)))
14
JOIN users u ON ((u.id = cu.user_id)))
15
UNION
16
SELECT DISTINCT o.id AS organization_id,
17
o.name AS organization_name,
18
NULL::uuid AS company_id,
19
NULL::text AS company_name,
20
u.id AS user_id,
21
concat(u.first_name, ' ', u.last_name) AS user_full_name,
22
u.email AS user_email,
23
u.phone_number AS user_phone,
24
'Organization User'::text AS user_role_source,
25
o.created_on_utc AS organization_created_on
26
FROM ((organizations o
27
JOIN organization_users ou ON ((ou.organization_id = o.id)))
28
JOIN users u ON ((u.id = ou.user_id)));
|
|||||
| View | transaction_sums_by_party | Missing in Target |
Source Script
Target Script
1
SELECT th.customer_id,
2
th.vendor_id,
3
th.employee_id,
4
c.name AS customer_name,
5
v.name AS vendor_name,
6
sum(
7
CASE
8
WHEN ((je.entry_type = 'C'::bpchar) AND (coa.account_type_id = ANY (ARRAY[4, 14, 15, 19, 20]))) THEN je.amount
9
ELSE (0)::numeric
10
END) AS total_invoiced,
11
sum(
12
CASE
13
WHEN ((je.entry_type = 'D'::bpchar) AND (coa.account_type_id = ANY (ARRAY[5, 16, 17, 18, 21, 22]))) THEN je.amount
14
ELSE (0)::numeric
15
END) AS total_billed,
16
sum(
17
CASE
18
WHEN ((je.entry_type = 'C'::bpchar) AND (coa.account_type_id = ANY (ARRAY[8, 9]))) THEN je.amount
19
ELSE (0)::numeric
20
END) AS total_received,
21
sum(
22
CASE
23
WHEN ((je.entry_type = 'D'::bpchar) AND (coa.account_type_id = ANY (ARRAY[8, 9]))) THEN je.amount
24
ELSE (0)::numeric
25
END) AS total_paid
26
FROM ((((transaction_headers th
27
LEFT JOIN customers c ON ((th.customer_id = c.id)))
28
LEFT JOIN vendors v ON ((th.vendor_id = v.id)))
29
JOIN journal_entries je ON ((je.transaction_id = th.id)))
30
JOIN chart_of_accounts coa ON ((je.account_id = coa.id)))
31
WHERE ((th.is_deleted = false) AND (je.is_deleted = false) AND (th.company_id = '9891a03a-d80a-430e-ab33-c419f99cffa0'::uuid))
32
GROUP BY th.customer_id, th.vendor_id, th.employee_id, c.name, v.name
33
ORDER BY COALESCE(c.name, v.name);
|
|||||
| View | user_permission_view | Match | ||||||
| View | vw_user_permissions_details | Missing in Target |
Source Script
Target Script
1
SELECT up.id AS user_permission_id,
2
u.id AS user_id,
3
u.first_name AS user_name,
4
p.id AS permission_id,
5
p.name AS permission_name,
6
o.id AS organization_id,
7
o.name AS organization_name,
8
o.short_name AS organization_short_name,
9
o.phone_number AS organization_phone_number,
10
o.email AS organization_email,
11
o.gstin AS organization_gstin,
12
up.created_on_utc,
13
up.created_by,
14
up.modified_on_utc,
15
up.modified_by,
16
up.is_deleted,
17
p.deleted_on_utc AS permission_deleted_on_utc,
18
up.deleted_on_utc AS user_permission_deleted_on_utc
19
FROM (((user_permissions up
20
JOIN permissions p ON ((up.permission_id = p.id)))
21
JOIN users u ON ((up.user_id = u.id)))
22
JOIN organizations o ON ((up.organization_id = o.id)))
23
WHERE ((up.is_deleted = false) AND (p.is_deleted = false) AND (o.is_deleted = false));
|
|||||
| View | transaction_party_view | Missing in Target |
Source Script
Target Script
1
SELECT th.id AS transaction_id,
2
th.company_id,
3
th.transaction_date,
4
th.document_number,
5
th.description,
6
th.customer_id,
7
c.name AS customer_name,
8
th.vendor_id,
9
v.name AS vendor_name,
10
th.employee_id,
11
je.entry_type,
12
coa.account_type_id,
13
coa.name AS account_name,
14
je.amount,
15
CASE
16
WHEN ((je.entry_type = 'C'::bpchar) AND (coa.account_type_id = ANY (ARRAY[4, 14, 15, 19, 20]))) THEN je.amount
17
ELSE (0)::numeric
18
END AS invoiced,
19
CASE
20
WHEN ((je.entry_type = 'C'::bpchar) AND (coa.account_type_id = ANY (ARRAY[8, 9]))) THEN je.amount
21
ELSE (0)::numeric
22
END AS received,
23
CASE
24
WHEN ((je.entry_type = 'D'::bpchar) AND (coa.account_type_id = ANY (ARRAY[5, 16, 17, 18, 21, 22]))) THEN je.amount
25
ELSE (0)::numeric
26
END AS billed,
27
CASE
28
WHEN ((je.entry_type = 'D'::bpchar) AND (coa.account_type_id = ANY (ARRAY[8, 9]))) THEN je.amount
29
ELSE (0)::numeric
30
END AS paid
31
FROM ((((transaction_headers th
32
LEFT JOIN customers c ON ((th.customer_id = c.id)))
33
LEFT JOIN vendors v ON ((th.vendor_id = v.id)))
34
JOIN journal_entries je ON ((je.transaction_id = th.id)))
35
JOIN chart_of_accounts coa ON ((je.account_id = coa.id)))
36
WHERE ((th.is_deleted = false) AND (je.is_deleted = false));
|
|||||
| View | view_permissions | Match | ||||||
| Trigger | trg_user_permissions_notify | Missing in Target |
Source Script
Target Script
1
CREATE TRIGGER trg_user_permissions_notify AFTER INSERT OR DELETE OR UPDATE ON user_permissions FOR EACH ROW EXECUTE FUNCTION notify_user_permission_change()
|
-- Table: module_permissions
Exists in source, missing in target
-- Table: company_users
-- ForeignKeys: MissingInTarget
ALTER TABLE "company_users" ADD CONSTRAINT "fk_company_users_user_id" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
-- Table: user_roles
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:end_date:timestamp without time zone:False::'0001-01-01 00:00:00'::timestamp without time zone:False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:organization_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:role_id:integer:False:::False|COL:start_date:timestamp without time zone:False::'0001-01-01 00:00:00'::timestamp without time zone:False|COL:user_id:uuid:False:::False|PK:|FK:created_by→users.id|FK:modified_by→users.id|FK:organization_id→organizations.id|FK:role_id→roles.id|FK:user_id→users.id|IDX:btree:nonunique:user_id,is_deleted,organization_id:|IDX:btree:unique:id:|IDX:btree:unique:user_id,role_id,organization_id:
-- TARGET SIGNATURE
COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:end_date:timestamp without time zone:False::'0001-01-01 00:00:00'::timestamp without time zone:False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:role_id:integer:False:::False|COL:start_date:timestamp without time zone:False::'0001-01-01 00:00:00'::timestamp without time zone:False|COL:user_id:uuid:False:::False|PK:|FK:created_by→users.id|FK:modified_by→users.id|FK:role_id→roles.id|FK:user_id→users.id|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "user_roles" ("user_id" uuid NOT NULL, "role_id" integer NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "end_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "start_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "organization_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "id" integer NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "user_roles" ("id" integer NOT NULL, "user_id" uuid NOT NULL, "role_id" integer NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "end_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "start_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "user_roles" ADD COLUMN "organization_id" uuid NOT NULL;
-- ForeignKeys: MissingInTarget
ALTER TABLE "user_roles" ADD CONSTRAINT "fk_user_roles_organization" FOREIGN KEY (organization_id) REFERENCES organizations(id);
-- Indexes: MissingInTarget
CREATE INDEX ix_user_roles_user_id_organization_id_is_deleted ON public.user_roles USING btree (user_id, organization_id, is_deleted)
-- Indexes: MissingInTarget
CREATE UNIQUE INDEX uq_user_roles_unique ON public.user_roles USING btree (user_id, role_id, organization_id)
-- Table: description_templates
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:deleted_on_utc:timestamp without time zone:True:::False|COL:id:integer:False::nextval('description_templates_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:name:text:False::''::text:False|COL:template_text:text:False:::False|PK:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:id:integer:False::nextval('description_templates_id_seq'::regclass):False|COL:template_text:text:False:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "description_templates" ("id" integer DEFAULT nextval('description_templates_id_seq'::regclass) NOT NULL, "template_text" text NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "name" text DEFAULT ''::text NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "description_templates" ("id" integer DEFAULT nextval('description_templates_id_seq'::regclass) NOT NULL, "template_text" text NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "description_templates" ADD COLUMN "deleted_on_utc" timestamp without time zone ;
-- Columns: MissingInTarget
ALTER TABLE "description_templates" ADD COLUMN "is_deleted" boolean NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "description_templates" ADD COLUMN "name" text NOT NULL;
-- Table: role_permissions
Exists in source, missing in target
-- Table: chart_of_accounts
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:account_group_code:integer:False::0:False|COL:account_number:text:False:::False|COL:account_type_id:integer:False:::False|COL:alternative_name:text:True:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:current_balance:numeric:True::0.00:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False::''::text:False|COL:id:uuid:False::gen_random_uuid():False|COL:is_default_account:boolean:False::false:False|COL:is_deleted:boolean:False::false:False|COL:is_ledger_total:boolean:False::false:False|COL:is_pg_bank_account:boolean:False::false:False|COL:is_show_outs:boolean:False::false:False|COL:is_tds_tcs:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:name:text:False:::False|COL:opening_balance:numeric:False::0.0:False|COL:organization_id:uuid:False:::False|COL:parent_account_id:uuid:True:::False|COL:second_group_code:integer:False::0:False|PK:|FK:account_type_id→account_types.id|FK:created_by→users.id|FK:modified_by→users.id|FK:organization_id→organizations.id|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:account_group_code:integer:False::0:False|COL:account_number:text:False:::False|COL:account_type_id:integer:False:::False|COL:alternative_name:text:True:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:current_balance:numeric:True::0.00:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False::''::text:False|COL:id:uuid:False::gen_random_uuid():False|COL:is_default_account:boolean:False::false:False|COL:is_deleted:boolean:False::false:False|COL:is_ledger_total:boolean:False::false:False|COL:is_show_outs:boolean:False::false:False|COL:is_tds_tcs:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:name:text:False:::False|COL:opening_balance:numeric:False::0.0:False|COL:organization_id:uuid:False:::False|COL:parent_account_id:uuid:True:::False|COL:second_group_code:integer:False::0:False|PK:|FK:account_type_id→account_types.id|FK:created_by→users.id|FK:modified_by→users.id|FK:organization_id→organizations.id|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "chart_of_accounts" ("id" uuid DEFAULT gen_random_uuid() NOT NULL, "account_number" text NOT NULL, "name" text NOT NULL, "organization_id" uuid NOT NULL, "parent_account_id" uuid, "description" text DEFAULT ''::text NOT NULL, "account_type_id" integer NOT NULL, "account_group_code" integer DEFAULT 0 NOT NULL, "second_group_code" integer DEFAULT 0 NOT NULL, "alternative_name" text, "is_ledger_total" boolean DEFAULT false NOT NULL, "is_show_outs" boolean DEFAULT false NOT NULL, "is_tds_tcs" boolean DEFAULT false NOT NULL, "opening_balance" numeric(,) DEFAULT 0.0 NOT NULL, "current_balance" numeric(,) DEFAULT 0.00, "created_by" uuid NOT NULL, "modified_by" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "is_default_account" boolean DEFAULT false NOT NULL, "is_pg_bank_account" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "chart_of_accounts" ("id" uuid DEFAULT gen_random_uuid() NOT NULL, "account_number" text NOT NULL, "name" text NOT NULL, "organization_id" uuid NOT NULL, "parent_account_id" uuid, "description" text DEFAULT ''::text NOT NULL, "account_type_id" integer NOT NULL, "account_group_code" integer DEFAULT 0 NOT NULL, "second_group_code" integer DEFAULT 0 NOT NULL, "alternative_name" text, "is_ledger_total" boolean DEFAULT false NOT NULL, "is_show_outs" boolean DEFAULT false NOT NULL, "is_tds_tcs" boolean DEFAULT false NOT NULL, "opening_balance" numeric(,) DEFAULT 0.0 NOT NULL, "current_balance" numeric(15,2) DEFAULT 0.00, "created_by" uuid NOT NULL, "modified_by" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "is_default_account" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "chart_of_accounts" ADD COLUMN "is_pg_bank_account" boolean NOT NULL;
-- Table: users_bk
Exists in source, missing in target
-- Table: v_permission_id
Exists in source, missing in target
-- Table: users
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:address_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:company_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:date_of_birth:timestamp without time zone:False::'0001-01-01'::date:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:department_id:integer:False::0:False|COL:designation_id:integer:False::0:False|COL:email:character varying:False:256::False|COL:first_name:character varying:False:100::False|COL:id:uuid:False:::False|COL:is_deleted:boolean:False::false:False|COL:last_name:character varying:False:100::False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:note:text:True:::False|COL:password_hash:text:False:::False|COL:phone_number:character varying:False:15:''::character varying:False|COL:roles:array:True::array[]::text[]:False|COL:role_id:integer:True::0:False|COL:third_party_id:text:False::''::text:False|COL:user_name:text:False::''::text:False|PK:|FK:address_id→addresses.id|FK:company_id→companies.id|FK:created_by→users.id|FK:modified_by→users.id|IDX:btree:unique:email,phone_number:|IDX:btree:unique:email:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:address_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:company_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:date_of_birth:timestamp without time zone:False::'0001-01-01'::date:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:department_id:integer:False::0:False|COL:designation_id:integer:False::0:False|COL:email:character varying:False:256::False|COL:first_name:character varying:False:100::False|COL:id:uuid:False:::False|COL:is_deleted:boolean:False::false:False|COL:last_name:character varying:False:100::False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:note:text:True:::False|COL:password_hash:text:False:::False|COL:phone_number:character varying:False:15:''::character varying:False|COL:roles:array:True::array[]::text[]:False|COL:role_id:integer:True::0:False|COL:third_party_id:text:False::''::text:False|COL:user_name:text:False::''::text:False|PK:|FK:address_id→addresses.id|FK:created_by→users.id|FK:modified_by→users.id|IDX:btree:unique:email,phone_number:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "users" ("id" uuid NOT NULL, "first_name" varchar(100) NOT NULL, "last_name" varchar(100) NOT NULL, "email" varchar(256) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "password_hash" text NOT NULL, "third_party_id" text DEFAULT ''::text NOT NULL, "roles" ARRAY DEFAULT ARRAY[]::text[], "address_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "date_of_birth" timestamp without time zone DEFAULT '0001-01-01'::date NOT NULL, "note" text, "role_id" integer DEFAULT 0, "designation_id" integer DEFAULT 0 NOT NULL, "department_id" integer DEFAULT 0 NOT NULL, "phone_number" varchar(15) DEFAULT ''::character varying NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "user_name" text DEFAULT ''::text NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "users" ("id" uuid NOT NULL, "first_name" varchar(100) NOT NULL, "last_name" varchar(100) NOT NULL, "email" varchar(256) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "password_hash" text NOT NULL, "third_party_id" text DEFAULT ''::text NOT NULL, "roles" ARRAY DEFAULT ARRAY[]::text[], "phone_number" varchar(15) DEFAULT ''::character varying NOT NULL, "address_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "date_of_birth" timestamp without time zone DEFAULT '0001-01-01'::date NOT NULL, "note" text, "role_id" integer DEFAULT 0, "designation_id" integer DEFAULT 0 NOT NULL, "department_id" integer DEFAULT 0 NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "user_name" text DEFAULT ''::text NOT NULL, PRIMARY KEY ("id"));
-- ForeignKeys: MissingInTarget
ALTER TABLE "users" ADD CONSTRAINT "fk_users_company_id" FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE RESTRICT;
-- Indexes: MissingInTarget
CREATE UNIQUE INDEX uq_users_email ON public.users USING btree (email)
-- Table: user_permissions
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:is_explicit:boolean:True::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:organization_id:uuid:True:::False|COL:permission_id:uuid:False:::False|COL:user_id:uuid:False:::False|FK:created_by→users.id|FK:modified_by→users.id|FK:organization_id→organizations.id|FK:permission_id→permissions.id|FK:user_id→users.id|IDX:btree:nonunique:user_id,is_deleted,organization_id:|IDX:btree:nonunique:user_id,permission_id,organization_id:|IDX:btree:unique:id:|IDX:btree:unique:user_id,permission_id,organization_id:
-- TARGET SIGNATURE
COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:organization_id:uuid:False:::False|COL:permission_id:uuid:False:::False|COL:user_id:uuid:False:::False|PK:|FK:created_by→users.id|FK:modified_by→users.id|FK:organization_id→organizations.id|FK:permission_id→permissions.id|IDX:btree:unique:id:|IDX:btree:unique:user_id,organization_id,permission_id:
-- SOURCE SCRIPT
CREATE TABLE "user_permissions" ("id" integer NOT NULL, "user_id" uuid NOT NULL, "permission_id" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "organization_id" uuid, "is_explicit" boolean DEFAULT false);
-- TARGET SCRIPT
CREATE TABLE "user_permissions" ("id" integer NOT NULL, "user_id" uuid NOT NULL, "organization_id" uuid NOT NULL, "permission_id" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "user_permissions" ADD COLUMN "is_explicit" boolean ;
-- ForeignKeys: MissingInTarget
ALTER TABLE "user_permissions" ADD CONSTRAINT "fk_user_permissions_users_user_id" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT;
-- Indexes: MissingInTarget
CREATE INDEX idx_user_permissions_active ON public.user_permissions USING btree (user_id, organization_id, permission_id) WHERE (is_deleted = false)
-- Indexes: MissingInTarget
CREATE INDEX ix_user_permissions_user_id_organization_id_is_deleted ON public.user_permissions USING btree (user_id, organization_id, is_deleted)
-- Indexes: MissingInTarget
CREATE UNIQUE INDEX uq_user_permissions ON public.user_permissions USING btree (user_id, organization_id, permission_id)
-- Indexes: MissingInSource
-- Optionally drop: DROP INDEX IF EXISTS "uq_user_permissions";
-- Table: companies
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:account_id:uuid:True:::False|COL:billing_address_id:uuid:True:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:currency:text:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:True:::False|COL:gstin:text:False:::False|COL:has_gstin:boolean:True::false:False|COL:id:uuid:False:::False|COL:image_id:integer:True:::False|COL:interest_percentage:numeric:False:::False|COL:is_apartment:boolean:True::false:False|COL:is_deleted:boolean:False::false:False|COL:is_non_work:boolean:True:::False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:name:text:False:::False|COL:organization_id:uuid:False:::False|COL:outstanding_limit:numeric:False:::False|COL:pan:text:True:::False|COL:proprietor_name:text:True:::False|COL:shipping_address_id:uuid:True:::False|COL:short_name:text:True:::False|COL:tag_line:text:True:::False|COL:tan:text:True:::False|PK:|FK:billing_address_id→addresses.id|FK:image_id→images.id|FK:organization_id→organizations.id|FK:shipping_address_id→addresses.id|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:billing_address_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:currency:text:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:True:::False|COL:gstin:text:False:::False|COL:has_gstin:boolean:True::false:False|COL:id:uuid:False:::False|COL:image_id:integer:True:::False|COL:interest_percentage:numeric:False:::False|COL:is_apartment:boolean:True::false:False|COL:is_deleted:boolean:False::false:False|COL:is_non_work:boolean:True:::False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:name:text:False:::False|COL:organization_id:uuid:False:::False|COL:outstanding_limit:numeric:False:::False|COL:pan:text:True:::False|COL:proprietor_name:text:True:::False|COL:shipping_address_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:short_name:text:True:::False|COL:tag_line:text:True:::False|COL:tan:text:True:::False|PK:|FK:image_id→images.id|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "companies" ("id" uuid NOT NULL, "name" text NOT NULL, "organization_id" uuid NOT NULL, "description" text, "account_id" uuid, "billing_address_id" uuid, "shipping_address_id" uuid, "gstin" text NOT NULL, "pan" text, "tan" text, "currency" text, "short_name" text, "tag_line" text, "proprietor_name" text, "outstanding_limit" numeric(,) NOT NULL, "is_non_work" boolean, "interest_percentage" numeric(,) NOT NULL, "is_apartment" boolean DEFAULT false, "has_gstin" boolean DEFAULT false, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "image_id" integer, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "companies" ("id" uuid NOT NULL, "name" text NOT NULL, "organization_id" uuid NOT NULL, "description" text, "account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "gstin" text NOT NULL, "pan" text, "tan" text, "currency" text, "short_name" text, "tag_line" text, "proprietor_name" text, "outstanding_limit" numeric(,) NOT NULL, "is_non_work" boolean, "interest_percentage" numeric(,) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "is_apartment" boolean DEFAULT false, "has_gstin" boolean DEFAULT false, "billing_address_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "shipping_address_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "image_id" integer, PRIMARY KEY ("id"));
-- ForeignKeys: MissingInTarget
ALTER TABLE "companies" ADD CONSTRAINT "fk_companies_billing_address_id" FOREIGN KEY (billing_address_id) REFERENCES addresses(id) ON DELETE SET NULL;
-- ForeignKeys: MissingInTarget
ALTER TABLE "companies" ADD CONSTRAINT "fk_companies_organization_id" FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "companies" ADD CONSTRAINT "fk_companies_shipping_address_id" FOREIGN KEY (shipping_address_id) REFERENCES addresses(id) ON DELETE SET NULL;
-- Table: document_meta_datas
-- ForeignKeys: MissingInTarget
ALTER TABLE "document_meta_datas" ADD CONSTRAINT "fk_document_meta_datas_company_id" FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE NOT VALID;
-- Table: payment_references
Exists in source, missing in target
-- Table: temp_paymnet_data
Exists in source, missing in target
-- Table: temp_organization_logs
-- ForeignKeys: MissingInTarget
ALTER TABLE "temp_organization_logs" ADD CONSTRAINT "fk_temp_organization_logs_organization_id" FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE NOT VALID;
-- Table: bank_reconciliation_stagings
Exists in source, missing in target
-- Table: account_opening_balances
-- ForeignKeys: MissingInTarget
ALTER TABLE "account_opening_balances" ADD CONSTRAINT "fk_account_opening_balances_organization_id" FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT NOT VALID;
-- Table: bank_reconciliation_links
Exists in source, missing in target
-- Table: journal_narrations
Exists in source, missing in target
-- Table: transaction_header_2024_2025
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:is_reversed:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "transaction_header_2024_2025" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "transaction_header_2024_2025" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "transaction_header_2024_2025" ADD COLUMN "is_reversed" boolean NOT NULL;
-- Table: organization_users
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_users" ADD CONSTRAINT "fk_user_id_organization_users" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT NOT VALID;
-- Table: cities
-- ForeignKeys: MissingInTarget
ALTER TABLE "cities" ADD CONSTRAINT "fk_cities_state_id" FOREIGN KEY (state_id) REFERENCES states(id) ON DELETE RESTRICT NOT VALID;
-- Table: budgets
-- ForeignKeys: MissingInTarget
ALTER TABLE "budgets" ADD CONSTRAINT "fk_budgets_company_id" FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "budgets" ADD CONSTRAINT "fk_budgets_finance_year_id" FOREIGN KEY (finance_year_id) REFERENCES finance_year(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "budgets" ADD CONSTRAINT "fk_budgets_status_id" FOREIGN KEY (status_id) REFERENCES budget_statuses(id) ON DELETE RESTRICT NOT VALID;
-- Table: budget_workflow
-- ForeignKeys: MissingInTarget
ALTER TABLE "budget_workflow" ADD CONSTRAINT "fk_budget_workflow_company_id" FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE NOT VALID;
-- Table: budget_lines
-- ForeignKeys: MissingInTarget
ALTER TABLE "budget_lines" ADD CONSTRAINT "fk_budget_lines_budget_id" FOREIGN KEY (budget_id) REFERENCES budgets(id) ON DELETE CASCADE NOT VALID;
-- Table: bank_transfer_ids
-- ForeignKeys: MissingInTarget
ALTER TABLE "bank_transfer_ids" ADD CONSTRAINT "fk_bank_transfer_ids_company_id" FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE NOT VALID;
-- Table: cron_dummy_log
Exists in source, missing in target
-- Table: journal_entries
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:True:::False|COL:dynamic_data:jsonb:True:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:journal_narration_id:bigint:False::0:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:False:::False|COL:dynamic_data:jsonb:False:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:transaction_date→transaction_header_2022_2023.id|FK:transaction_date→transaction_header_2022_2023.transaction_date|FK:transaction_date→transaction_header_2023_2024.id|FK:transaction_date→transaction_header_2023_2024.transaction_date|FK:transaction_date→transaction_header_2024_2025.id|FK:transaction_date→transaction_header_2024_2025.transaction_date|FK:transaction_date→transaction_header_2025_2026.id|FK:transaction_date→transaction_header_2025_2026.transaction_date|FK:transaction_date→transaction_headers.id|FK:transaction_date→transaction_headers.transaction_date|FK:transaction_id→transaction_header_2022_2023.id|FK:transaction_id→transaction_header_2022_2023.transaction_date|FK:transaction_id→transaction_header_2023_2024.id|FK:transaction_id→transaction_header_2023_2024.transaction_date|FK:transaction_id→transaction_header_2024_2025.id|FK:transaction_id→transaction_header_2024_2025.transaction_date|FK:transaction_id→transaction_header_2025_2026.id|FK:transaction_id→transaction_header_2025_2026.transaction_date|FK:transaction_id→transaction_headers.id|FK:transaction_id→transaction_headers.transaction_date|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "journal_entries" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "journal_entries" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "journal_entries" ADD COLUMN "journal_narration_id" bigint NOT NULL;
-- ForeignKeys: MissingInTarget
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_journal_narration_id" FOREIGN KEY (journal_narration_id) REFERENCES journal_narrations(id);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_1" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2022_2023(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_1" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2022_2023(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_1" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2022_2023(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_1" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2022_2023(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_2" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2023_2024(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_2" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2023_2024(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_2" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2023_2024(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_2" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2023_2024(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_3" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2024_2025(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_3" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2024_2025(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_3" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2024_2025(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_3" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2024_2025(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_4" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2025_2026(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_4" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2025_2026(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_4" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2025_2026(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries" ADD CONSTRAINT "fk_journal_entries_transaction_header_4" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_header_2025_2026(id, transaction_date);
-- Table: company_contacts
-- ForeignKeys: MissingInTarget
ALTER TABLE "company_contacts" ADD CONSTRAINT "fk_company_contacts_company_id" FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE NOT VALID;
-- Table: transaction_header_2022_2023
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:is_reversed:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "transaction_header_2022_2023" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "transaction_header_2022_2023" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "transaction_header_2022_2023" ADD COLUMN "is_reversed" boolean NOT NULL;
-- Table: transaction_header_2023_2024
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:is_reversed:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "transaction_header_2023_2024" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "transaction_header_2023_2024" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "transaction_header_2023_2024" ADD COLUMN "is_reversed" boolean NOT NULL;
-- Table: feature_toggles
Exists in source, missing in target
-- Table: user_permissions_backup
Exists in source, missing in target
-- Table: bank_reconciliations
Exists in source, missing in target
-- Table: journal_entries_2022_2023
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:True:::False|COL:dynamic_data:jsonb:True:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:journal_narration_id:bigint:False::0:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:False:::False|COL:dynamic_data:jsonb:False:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:transaction_date→transaction_headers.id|FK:transaction_date→transaction_headers.transaction_date|FK:transaction_id→transaction_headers.id|FK:transaction_id→transaction_headers.transaction_date|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "journal_entries_2022_2023" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "journal_entries_2022_2023" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "journal_entries_2022_2023" ADD COLUMN "journal_narration_id" bigint NOT NULL;
-- ForeignKeys: MissingInTarget
ALTER TABLE "journal_entries_2022_2023" ADD CONSTRAINT "fk_journal_entries_journal_narration_id" FOREIGN KEY (journal_narration_id) REFERENCES journal_narrations(id);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2022_2023" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2022_2023" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2022_2023" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2022_2023" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- Table: feature_toggle_overrides
Exists in source, missing in target
-- Table: user_permission_sync_runs
Exists in source, missing in target
-- Table: scope_types
Exists in source, missing in target
-- Table: journal_entries_2023_2024
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:True:::False|COL:dynamic_data:jsonb:True:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:journal_narration_id:bigint:False::0:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:False:::False|COL:dynamic_data:jsonb:False:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:transaction_date→transaction_headers.id|FK:transaction_date→transaction_headers.transaction_date|FK:transaction_id→transaction_headers.id|FK:transaction_id→transaction_headers.transaction_date|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "journal_entries_2023_2024" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "journal_entries_2023_2024" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "journal_entries_2023_2024" ADD COLUMN "journal_narration_id" bigint NOT NULL;
-- ForeignKeys: MissingInTarget
ALTER TABLE "journal_entries_2023_2024" ADD CONSTRAINT "fk_journal_entries_journal_narration_id" FOREIGN KEY (journal_narration_id) REFERENCES journal_narrations(id);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2023_2024" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2023_2024" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2023_2024" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2023_2024" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- Table: journal_entries_2024_2025
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:True:::False|COL:dynamic_data:jsonb:True:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:journal_narration_id:bigint:False::0:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:False:::False|COL:dynamic_data:jsonb:False:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:transaction_date→transaction_headers.id|FK:transaction_date→transaction_headers.transaction_date|FK:transaction_id→transaction_headers.id|FK:transaction_id→transaction_headers.transaction_date|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "journal_entries_2024_2025" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "journal_entries_2024_2025" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "journal_entries_2024_2025" ADD COLUMN "journal_narration_id" bigint NOT NULL;
-- ForeignKeys: MissingInTarget
ALTER TABLE "journal_entries_2024_2025" ADD CONSTRAINT "fk_journal_entries_journal_narration_id" FOREIGN KEY (journal_narration_id) REFERENCES journal_narrations(id);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2024_2025" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2024_2025" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2024_2025" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2024_2025" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- Table: journal_entries_2025_2026
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:True:::False|COL:dynamic_data:jsonb:True:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:journal_narration_id:bigint:False::0:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:account_id:uuid:True:::False|COL:amount:numeric:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description_template_id:integer:False:::False|COL:dynamic_data:jsonb:False:::False|COL:entry_source_id:integer:False:::False|COL:entry_type:character:False:1::False|COL:id:bigint:False::nextval('journal_entries_id_seq'::regclass):False|COL:is_deleted:boolean:False::false:False|COL:transaction_date:date:False:::False|COL:transaction_id:bigint:False:::False|PK:|PK:|FK:account_id→chart_of_accounts.id|FK:description_template_id→description_templates.id|FK:entry_source_id→entry_sources.id|FK:transaction_date→transaction_headers.id|FK:transaction_date→transaction_headers.transaction_date|FK:transaction_id→transaction_headers.id|FK:transaction_id→transaction_headers.transaction_date|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "journal_entries_2025_2026" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer, "dynamic_data" jsonb, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, "journal_narration_id" bigint DEFAULT 0 NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "journal_entries_2025_2026" ("id" bigint DEFAULT nextval('journal_entries_id_seq'::regclass) NOT NULL, "transaction_id" bigint NOT NULL, "transaction_date" date NOT NULL, "amount" numeric(15,2) NOT NULL, "entry_type" character NOT NULL, "description_template_id" integer NOT NULL, "dynamic_data" jsonb NOT NULL, "entry_source_id" integer NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "account_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "journal_entries_2025_2026" ADD COLUMN "journal_narration_id" bigint NOT NULL;
-- ForeignKeys: MissingInTarget
ALTER TABLE "journal_entries_2025_2026" ADD CONSTRAINT "fk_journal_entries_journal_narration_id" FOREIGN KEY (journal_narration_id) REFERENCES journal_narrations(id);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2025_2026" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2025_2026" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2025_2026" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- ForeignKeys: MissingInSource
ALTER TABLE "journal_entries_2025_2026" ADD CONSTRAINT "fk_journal_entries_transaction_header" FOREIGN KEY (transaction_id, transaction_date) REFERENCES transaction_headers(id, transaction_date);
-- Table: actions
Exists in source, missing in target
-- Table: feature_toggle_audits
Exists in source, missing in target
-- Table: user_global_permissions
-- ForeignKeys: MissingInTarget
ALTER TABLE "user_global_permissions" ADD CONSTRAINT "fk_user_global_permissions_permission_id" FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "user_global_permissions" ADD CONSTRAINT "fk_user_global_permissions_user_id" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE NOT VALID;
-- Table: employees
Exists in source, missing in target
-- Table: audit_query_responses
Exists in source, missing in target
-- Table: system_settings
Exists in source, missing in target
-- Table: organization_accounts
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:accounts_payable_account_id:uuid:False:::False|COL:accounts_receivable_account_id:uuid:False:::False|COL:bad_debt_expense_account_id:uuid:True:::False|COL:bank_charges_account_id:uuid:True:::False|COL:cgst_payable_account_id:uuid:False:::False|COL:cgst_receivable_account_id:uuid:False:::False|COL:cost_of_goods_sold_account_id:uuid:True:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:True:::False|COL:customer_advance_account_id:uuid:True:::False|COL:customer_security_deposit_account_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:depreciation_expense_account_id:uuid:True:::False|COL:discounts_given_account_id:uuid:True:::False|COL:discounts_received_account_id:uuid:True:::False|COL:employee_advance_account_id:uuid:True:::False|COL:employee_loan_account_id:uuid:True:::False|COL:esi_payable_employee_contribution_account_id:uuid:True:::False|COL:esi_payable_employer_contribution_account_id:uuid:True:::False|COL:foreign_exchange_gain_loss_account_id:uuid:True:::False|COL:id:bigint:False:::False|COL:igst_payable_account_id:uuid:False:::False|COL:igst_receivable_account_id:uuid:False:::False|COL:interest_expense_account_id:uuid:True:::False|COL:interest_income_account_id:uuid:True:::False|COL:inventory_account_id:uuid:True:::False|COL:is_deleted:boolean:False::false:False|COL:lwf_payable_employee_contribution_account_id:uuid:True:::False|COL:lwf_payable_employer_contribution_account_id:uuid:True:::False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:opening_balance_equity_account_id:uuid:True:::False|COL:organization_id:uuid:False:::False|COL:penalty_receivable_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:pf_payable_employee_contribution_account_id:uuid:True:::False|COL:pf_payable_employer_contribution_account_id:uuid:True:::False|COL:purchase_tax_receivable_account_id:uuid:True:::False|COL:round_off_gain_account_id:uuid:False:::False|COL:round_off_loss_account_id:uuid:True:::False|COL:salary_expense_account_id:uuid:True:::False|COL:salary_payable_account_id:uuid:True:::False|COL:sales_revenue_account_id:uuid:False:::False|COL:sales_tax_payable_account_id:uuid:True:::False|COL:sgst_payable_account_id:uuid:False:::False|COL:sgst_receivable_account_id:uuid:False:::False|COL:tds_on_contractors_account_id:uuid:True:::False|COL:tds_on_salaries_account_id:uuid:True:::False|COL:tds_payable_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:tds_receivable_account_id:uuid:True:::False|COL:vendor_advance_account_id:uuid:True:::False|COL:vendor_security_deposit_account_id:uuid:True:::False|PK:|FK:accounts_payable_account_id→chart_of_accounts.id|FK:accounts_receivable_account_id→chart_of_accounts.id|FK:bad_debt_expense_account_id→chart_of_accounts.id|FK:bank_charges_account_id→chart_of_accounts.id|FK:cgst_payable_account_id→chart_of_accounts.id|FK:cgst_receivable_account_id→chart_of_accounts.id|FK:cost_of_goods_sold_account_id→chart_of_accounts.id|FK:created_by→users.id|FK:customer_advance_account_id→chart_of_accounts.id|FK:customer_security_deposit_account_id→chart_of_accounts.id|FK:depreciation_expense_account_id→chart_of_accounts.id|FK:discounts_given_account_id→chart_of_accounts.id|FK:discounts_received_account_id→chart_of_accounts.id|FK:employee_advance_account_id→chart_of_accounts.id|FK:employee_loan_account_id→chart_of_accounts.id|FK:esi_payable_employee_contribution_account_id→chart_of_accounts.id|FK:esi_payable_employer_contribution_account_id→chart_of_accounts.id|FK:foreign_exchange_gain_loss_account_id→chart_of_accounts.id|FK:igst_payable_account_id→chart_of_accounts.id|FK:igst_receivable_account_id→chart_of_accounts.id|FK:interest_expense_account_id→chart_of_accounts.id|FK:interest_income_account_id→chart_of_accounts.id|FK:inventory_account_id→chart_of_accounts.id|FK:lwf_payable_employee_contribution_account_id→chart_of_accounts.id|FK:lwf_payable_employer_contribution_account_id→chart_of_accounts.id|FK:modified_by→users.id|FK:opening_balance_equity_account_id→chart_of_accounts.id|FK:organization_id→organizations.id|FK:penalty_receivable_account_id→chart_of_accounts.id|FK:pf_payable_employee_contribution_account_id→chart_of_accounts.id|FK:pf_payable_employer_contribution_account_id→chart_of_accounts.id|FK:purchase_tax_receivable_account_id→chart_of_accounts.id|FK:round_off_gain_account_id→chart_of_accounts.id|FK:round_off_loss_account_id→chart_of_accounts.id|FK:salary_expense_account_id→chart_of_accounts.id|FK:salary_payable_account_id→chart_of_accounts.id|FK:sales_revenue_account_id→chart_of_accounts.id|FK:sales_tax_payable_account_id→chart_of_accounts.id|FK:sgst_payable_account_id→chart_of_accounts.id|FK:sgst_receivable_account_id→chart_of_accounts.id|FK:tds_on_contractors_account_id→chart_of_accounts.id|FK:tds_on_salaries_account_id→chart_of_accounts.id|FK:tds_payable_account_id→chart_of_accounts.id|FK:tds_receivable_account_id→chart_of_accounts.id|FK:vendor_advance_account_id→chart_of_accounts.id|FK:vendor_security_deposit_account_id→chart_of_accounts.id|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:accounts_payable_account_id:uuid:False:::False|COL:accounts_receivable_account_id:uuid:False:::False|COL:bad_debt_expense_account_id:uuid:True:::False|COL:bank_charges_account_id:uuid:True:::False|COL:cgst_payable_account_id:uuid:False:::False|COL:cgst_receivable_account_id:uuid:False:::False|COL:cost_of_goods_sold_account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:depreciation_expense_account_id:uuid:True:::False|COL:discounts_given_account_id:uuid:True:::False|COL:discounts_received_account_id:uuid:True:::False|COL:foreign_exchange_gain_loss_account_id:uuid:True:::False|COL:id:bigint:False:::False|COL:igst_payable_account_id:uuid:False:::False|COL:igst_receivable_account_id:uuid:False:::False|COL:interest_expense_account_id:uuid:True:::False|COL:interest_income_account_id:uuid:True:::False|COL:inventory_account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:organization_id:uuid:False:::False|COL:penalty_receivable_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:purchase_tax_receivable_account_id:uuid:True:::False|COL:round_off_gain_account_id:uuid:False:::False|COL:round_off_loss_account_id:uuid:True:::False|COL:salary_expense_account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:salary_payable_account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:sales_revenue_account_id:uuid:False:::False|COL:sales_tax_payable_account_id:uuid:True:::False|COL:sgst_payable_account_id:uuid:False:::False|COL:sgst_receivable_account_id:uuid:False:::False|COL:tds_payable_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:tds_receivable_account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|PK:|FK:accounts_payable_account_id→chart_of_accounts.id|FK:accounts_receivable_account_id→chart_of_accounts.id|FK:bad_debt_expense_account_id→chart_of_accounts.id|FK:bank_charges_account_id→chart_of_accounts.id|FK:cgst_payable_account_id→chart_of_accounts.id|FK:cgst_receivable_account_id→chart_of_accounts.id|FK:created_by→users.id|FK:depreciation_expense_account_id→chart_of_accounts.id|FK:discounts_given_account_id→chart_of_accounts.id|FK:discounts_received_account_id→chart_of_accounts.id|FK:foreign_exchange_gain_loss_account_id→chart_of_accounts.id|FK:igst_payable_account_id→chart_of_accounts.id|FK:igst_receivable_account_id→chart_of_accounts.id|FK:interest_expense_account_id→chart_of_accounts.id|FK:interest_income_account_id→chart_of_accounts.id|FK:modified_by→users.id|FK:organization_id→organizations.id|FK:purchase_tax_receivable_account_id→chart_of_accounts.id|FK:round_off_gain_account_id→chart_of_accounts.id|FK:round_off_loss_account_id→chart_of_accounts.id|FK:sales_revenue_account_id→chart_of_accounts.id|FK:sales_tax_payable_account_id→chart_of_accounts.id|FK:sgst_payable_account_id→chart_of_accounts.id|FK:sgst_receivable_account_id→chart_of_accounts.id|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "organization_accounts" ("id" bigint NOT NULL, "organization_id" uuid NOT NULL, "accounts_receivable_account_id" uuid NOT NULL, "accounts_payable_account_id" uuid NOT NULL, "sales_revenue_account_id" uuid NOT NULL, "cgst_receivable_account_id" uuid NOT NULL, "sgst_receivable_account_id" uuid NOT NULL, "igst_receivable_account_id" uuid NOT NULL, "cgst_payable_account_id" uuid NOT NULL, "sgst_payable_account_id" uuid NOT NULL, "igst_payable_account_id" uuid NOT NULL, "round_off_gain_account_id" uuid NOT NULL, "round_off_loss_account_id" uuid, "sales_tax_payable_account_id" uuid, "purchase_tax_receivable_account_id" uuid, "discounts_given_account_id" uuid, "discounts_received_account_id" uuid, "interest_income_account_id" uuid, "interest_expense_account_id" uuid, "depreciation_expense_account_id" uuid, "bad_debt_expense_account_id" uuid, "bank_charges_account_id" uuid, "foreign_exchange_gain_loss_account_id" uuid, "created_on_utc" timestamp without time zone, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "cost_of_goods_sold_account_id" uuid, "inventory_account_id" uuid, "salary_expense_account_id" uuid, "salary_payable_account_id" uuid, "tds_receivable_account_id" uuid, "penalty_receivable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "tds_payable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "customer_advance_account_id" uuid, "customer_security_deposit_account_id" uuid, "employee_advance_account_id" uuid, "employee_loan_account_id" uuid, "vendor_advance_account_id" uuid, "vendor_security_deposit_account_id" uuid, "esi_payable_employee_contribution_account_id" uuid, "esi_payable_employer_contribution_account_id" uuid, "lwf_payable_employee_contribution_account_id" uuid, "lwf_payable_employer_contribution_account_id" uuid, "pf_payable_employee_contribution_account_id" uuid, "pf_payable_employer_contribution_account_id" uuid, "tds_on_contractors_account_id" uuid, "opening_balance_equity_account_id" uuid, "tds_on_salaries_account_id" uuid, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "organization_accounts" ("id" bigint NOT NULL, "organization_id" uuid NOT NULL, "accounts_receivable_account_id" uuid NOT NULL, "accounts_payable_account_id" uuid NOT NULL, "sales_revenue_account_id" uuid NOT NULL, "cgst_receivable_account_id" uuid NOT NULL, "sgst_receivable_account_id" uuid NOT NULL, "igst_receivable_account_id" uuid NOT NULL, "cgst_payable_account_id" uuid NOT NULL, "sgst_payable_account_id" uuid NOT NULL, "igst_payable_account_id" uuid NOT NULL, "round_off_gain_account_id" uuid NOT NULL, "round_off_loss_account_id" uuid, "sales_tax_payable_account_id" uuid, "purchase_tax_receivable_account_id" uuid, "discounts_given_account_id" uuid, "discounts_received_account_id" uuid, "interest_income_account_id" uuid, "interest_expense_account_id" uuid, "depreciation_expense_account_id" uuid, "bad_debt_expense_account_id" uuid, "bank_charges_account_id" uuid, "foreign_exchange_gain_loss_account_id" uuid, "created_on_utc" timestamp without time zone, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "cost_of_goods_sold_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "inventory_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "salary_expense_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "salary_payable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "tds_receivable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "penalty_receivable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "tds_payable_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "customer_advance_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "customer_security_deposit_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "employee_advance_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "employee_loan_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "vendor_advance_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "vendor_security_deposit_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "esi_payable_employee_contribution_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "esi_payable_employer_contribution_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "lwf_payable_employee_contribution_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "lwf_payable_employer_contribution_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "pf_payable_employee_contribution_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "pf_payable_employer_contribution_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "tds_on_contractors_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "opening_balance_equity_account_id" uuid ;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "tds_on_salaries_account_id" uuid ;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_customer_security_deposit_account_id" FOREIGN KEY (customer_security_deposit_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_employee_advance_account_id" FOREIGN KEY (employee_advance_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_employee_loan_account_id" FOREIGN KEY (employee_loan_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_esi_payable_employee_contribution_account_id" FOREIGN KEY (esi_payable_employee_contribution_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_esi_payable_employer_contribution_account_id" FOREIGN KEY (esi_payable_employer_contribution_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_inventory_account_id" FOREIGN KEY (inventory_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_lwf_payable_employee_contribution_account_id" FOREIGN KEY (lwf_payable_employee_contribution_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_lwf_payable_employer_contribution_account_id" FOREIGN KEY (lwf_payable_employer_contribution_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_opening_balance_equity_account_id" FOREIGN KEY (opening_balance_equity_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_penalty_receivable_account_id" FOREIGN KEY (penalty_receivable_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_pf_payable_employee_contribution_account_id" FOREIGN KEY (pf_payable_employee_contribution_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_pf_payable_employer_contribution_account_id" FOREIGN KEY (pf_payable_employer_contribution_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_salary_expense_account_id" FOREIGN KEY (salary_expense_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_salary_payable_account_id" FOREIGN KEY (salary_payable_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_tds_on_contractors_account_id" FOREIGN KEY (tds_on_contractors_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_tds_on_salaries_account_id" FOREIGN KEY (tds_on_salaries_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_tds_payable_account_id" FOREIGN KEY (tds_payable_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_tds_receivable_account_id" FOREIGN KEY (tds_receivable_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_vendor_advance_account_id" FOREIGN KEY (vendor_advance_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_org_accounts_vendor_security_deposit_account_id" FOREIGN KEY (vendor_security_deposit_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_organization_accounts_cogs_account_id" FOREIGN KEY (cost_of_goods_sold_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "organization_accounts" ADD CONSTRAINT "fk_organization_accounts_customer_advance_account_id" FOREIGN KEY (customer_advance_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- Table: opening_balances
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:account_id:uuid:False:::False|COL:balance:numeric:False::0:False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:employee_id:uuid:True:::False|COL:entry_type:character:False:1:'d'::bpchar:False|COL:finyear_id:integer:False:::False|COL:id:uuid:False::gen_random_uuid():False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:organization_id:uuid:False:::False|COL:vendor_id:uuid:True:::False|PK:|FK:account_id→chart_of_accounts.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:employee_id→employees.id|FK:finyear_id→finance_year.id|FK:modified_by→users.id|FK:organization_id→organizations.id|FK:vendor_id→vendors.id|IDX:btree:nonunique:finyear_id,account_id:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:account_id:uuid:False:::False|COL:balance:numeric:False::0:False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:employee_id:uuid:True:::False|COL:entry_type:character:False:1:'d'::bpchar:False|COL:finyear_id:integer:False:::False|COL:id:uuid:False::gen_random_uuid():False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:organization_id:uuid:False:::False|COL:vendor_id:uuid:True:::False|PK:|FK:account_id→chart_of_accounts.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:finyear_id→finance_year.id|FK:modified_by→users.id|FK:organization_id→organizations.id|FK:vendor_id→vendors.id|IDX:btree:nonunique:finyear_id,account_id:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "opening_balances" ("id" uuid DEFAULT gen_random_uuid() NOT NULL, "organization_id" uuid NOT NULL, "finyear_id" integer NOT NULL, "account_id" uuid NOT NULL, "customer_id" uuid, "vendor_id" uuid, "employee_id" uuid, "balance" numeric(15,2) DEFAULT 0 NOT NULL, "entry_type" character DEFAULT 'D'::bpchar NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "created_by" uuid NOT NULL, "modified_by" uuid, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "opening_balances" ("id" uuid DEFAULT gen_random_uuid() NOT NULL, "organization_id" uuid NOT NULL, "finyear_id" integer NOT NULL, "account_id" uuid NOT NULL, "customer_id" uuid, "vendor_id" uuid, "employee_id" uuid, "balance" numeric(15,2) DEFAULT 0 NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "created_by" uuid NOT NULL, "modified_by" uuid, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "entry_type" character DEFAULT 'D'::bpchar NOT NULL, PRIMARY KEY ("id"));
-- ForeignKeys: MissingInTarget
ALTER TABLE "opening_balances" ADD CONSTRAINT "fk_employee_id_opening_balances" FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE RESTRICT NOT VALID;
-- Table: audit_queries
Exists in source, missing in target
-- Table: audit_query_statuses
Exists in source, missing in target
-- Table: bank_statements
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:balance:numeric:True:::False|COL:bank_id:uuid:False:::False|COL:branch_code:character varying:True:20::False|COL:cheque_number:character varying:True:50::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:credit_amount:numeric:True:::False|COL:debit_amount:numeric:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:has_reconciled:boolean:False::false:False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:organization_id:uuid:False:::False|COL:txn_date:timestamp without time zone:False:::False|COL:value_date:timestamp without time zone:False:::False|PK:|FK:organization_id→organizations.id|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:balance:numeric:True:::False|COL:bank_id:integer:False:::False|COL:branch_code:character varying:True:20::False|COL:cheque_number:character varying:True:50::False|COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:credit_amount:numeric:True:::False|COL:debit_amount:numeric:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:has_reconciled:boolean:False::false:False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:txn_date:date:False:::False|COL:value_date:date:False:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "bank_statements" ("id" integer NOT NULL, "organization_id" uuid NOT NULL, "bank_id" uuid NOT NULL, "txn_date" timestamp without time zone NOT NULL, "cheque_number" varchar(50), "description" text NOT NULL, "value_date" timestamp without time zone NOT NULL, "branch_code" varchar(20), "debit_amount" numeric(14,2), "credit_amount" numeric(14,2), "balance" numeric(14,2), "has_reconciled" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_by" uuid, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "bank_statements" ("id" integer NOT NULL, "company_id" uuid NOT NULL, "bank_id" integer NOT NULL, "txn_date" date NOT NULL, "cheque_number" varchar(50), "description" text NOT NULL, "value_date" date NOT NULL, "branch_code" varchar(20), "debit_amount" numeric(14,2), "credit_amount" numeric(14,2), "balance" numeric(14,2), "has_reconciled" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_by" uuid, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "bank_statements" ADD COLUMN "organization_id" uuid NOT NULL;
-- ForeignKeys: MissingInTarget
ALTER TABLE "bank_statements" ADD CONSTRAINT "fk_bank_statements_organization_id" FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE NOT VALID;
-- Table: transaction_header_2025_2026
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:is_reversed:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "transaction_header_2025_2026" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "transaction_header_2025_2026" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "transaction_header_2025_2026" ADD COLUMN "is_reversed" boolean NOT NULL;
-- Table: transaction_headers
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:is_reversed:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- TARGET SIGNATURE
COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:document_id:uuid:True:::False|COL:document_number:text:False:::False|COL:employee_id:uuid:True:::False|COL:id:bigint:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:status_id:integer:False:::False|COL:transaction_date:timestamp without time zone:False:::False|COL:transaction_source_type:integer:False:::False|COL:vendor_id:uuid:True:::False|PK:|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:customer_id→customers.id|FK:modified_by→users.id|FK:transaction_source_type→transaction_source_types.id|FK:vendor_id→vendors.id|IDX:btree:unique:id,transaction_date:
-- SOURCE SCRIPT
CREATE TABLE "transaction_headers" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, "transaction_source_type" integer NOT NULL, "is_reversed" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id", "transaction_date"));
-- TARGET SCRIPT
CREATE TABLE "transaction_headers" ("id" bigint NOT NULL, "company_id" uuid NOT NULL, "transaction_date" timestamp without time zone NOT NULL, "transaction_source_type" integer NOT NULL, "status_id" integer NOT NULL, "document_number" text NOT NULL, "description" text NOT NULL, "customer_id" uuid, "vendor_id" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "employee_id" uuid, "document_id" uuid, PRIMARY KEY ("id", "transaction_date"));
-- Columns: MissingInTarget
ALTER TABLE "transaction_headers" ADD COLUMN "is_reversed" boolean NOT NULL;
-- Table: messages
Exists in source, missing in target
-- Table: notifications
-- ForeignKeys: MissingInTarget
ALTER TABLE "notifications" ADD CONSTRAINT "fk_notifications_user_id" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE NOT VALID;
-- Table: reconciliation_statuses
Exists in source, missing in target
-- Table: organizations
-- ForeignKeys: MissingInTarget
ALTER TABLE "organizations" ADD CONSTRAINT "fk_organization_type_id_organizations" FOREIGN KEY (organization_type_id) REFERENCES organization_types(id) ON DELETE RESTRICT;
-- Table: transaction_headers_19_05_25
Exists in source, missing in target
-- Table: transaction_headers_bk_20_5_25
Exists in source, missing in target
-- Table: transaction_source_types
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:name:text:False:::False|COL:sort_order:integer:False::100:False|PK:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:name:text:False:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "transaction_source_types" ("id" integer NOT NULL, "name" text NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "sort_order" integer DEFAULT 100 NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "transaction_source_types" ("id" integer NOT NULL, "name" text NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "transaction_source_types" ADD COLUMN "sort_order" integer NOT NULL;
-- Table: permissions
-- ForeignKeys: MissingInTarget
ALTER TABLE "permissions" ADD CONSTRAINT "fk_permissions_parent_permission_id" FOREIGN KEY (parent_permission_id) REFERENCES permissions(id) ON DELETE RESTRICT NOT VALID;
-- Table: user_deletion_requests
-- ForeignKeys: MissingInTarget
ALTER TABLE "user_deletion_requests" ADD CONSTRAINT "fk_user_deletion_requests_status_id" FOREIGN KEY (status_id) REFERENCES user_deletion_request_statuses(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "user_deletion_requests" ADD CONSTRAINT "fk_user_deletion_requests_user_id" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE NOT VALID;
-- Table: email_templates
-- ForeignKeys: MissingInTarget
ALTER TABLE "email_templates" ADD CONSTRAINT "fk_email_templates_organization_id" FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE NOT VALID;
-- Table: general_ledgers
-- ForeignKeys: MissingInTarget
ALTER TABLE "general_ledgers" ADD CONSTRAINT "fk_general_ledgers_credit_account_id" FOREIGN KEY (credit_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- ForeignKeys: MissingInTarget
ALTER TABLE "general_ledgers" ADD CONSTRAINT "fk_general_ledgers_debit_account_id" FOREIGN KEY (debit_account_id) REFERENCES chart_of_accounts(id) ON DELETE RESTRICT NOT VALID;
-- Table: fixed_deposits
-- ForeignKeys: MissingInTarget
ALTER TABLE "fixed_deposits" ADD CONSTRAINT "fk_fixed_deposits_company_id" FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE NOT VALID;
-- Table: email_notification_rules
Exists in source, missing in target
-- Table: user_permission_sync_deltas
Exists in source, missing in target
-- Table: email_audit_logs
Exists in source, missing in target
-- Table: transaction_header_2026_2027
Exists in source, missing in target
-- Table: transaction_header_2027_2028
Exists in source, missing in target
-- Table: transaction_header_2028_2029
Exists in source, missing in target
-- Table: journal_entries_2026_2027
Exists in source, missing in target
-- Table: transaction_header_2029_2030
Exists in source, missing in target
-- Table: journal_entries_2027_2028
Exists in source, missing in target
-- Table: journal_entries_2028_2029
Exists in source, missing in target
-- Table: journal_entries_2029_2030
Exists in source, missing in target
-- Table: journal_entries_2021_2022
Exists in source, missing in target
-- Table: d_permission_details
-- CreateScript: MissingInSource
CREATE TABLE "d_permission_details" ("id" uuid NOT NULL, "name" text NOT NULL, "tree_name" text NOT NULL, PRIMARY KEY ("id"));
-- PrimaryKeys: MissingInSource
ALTER TABLE "d_permission_details" ADD CONSTRAINT "pk_d_permission_details" PRIMARY KEY (id);
-- Columns: MissingInSource
ALTER TABLE "d_permission_details" ADD COLUMN "id" uuid NOT NULL;
-- Columns: MissingInSource
ALTER TABLE "d_permission_details" ADD COLUMN "name" text NOT NULL;
-- Columns: MissingInSource
ALTER TABLE "d_permission_details" ADD COLUMN "tree_name" text NOT NULL;
-- Indexes: MissingInSource
CREATE UNIQUE INDEX pk_d_permission_details ON public.d_permission_details USING btree (id)
-- Table: organizations_to_be_cleaned
-- CreateScript: MissingInSource
CREATE TABLE "organizations_to_be_cleaned" ("id" uuid, "name" text);
-- Columns: MissingInSource
ALTER TABLE "organizations_to_be_cleaned" ADD COLUMN "id" uuid ;
-- Columns: MissingInSource
ALTER TABLE "organizations_to_be_cleaned" ADD COLUMN "name" text ;
-- Table: permission_templates
-- CreateScript: MissingInSource
CREATE TABLE "permission_templates" ("id" integer NOT NULL, "name" varchar(100) NOT NULL, PRIMARY KEY ("id"));
-- PrimaryKeys: MissingInSource
ALTER TABLE "permission_templates" ADD CONSTRAINT "pk_permission_templates" PRIMARY KEY (id);
-- Columns: MissingInSource
ALTER TABLE "permission_templates" ADD COLUMN "id" integer NOT NULL;
-- Columns: MissingInSource
ALTER TABLE "permission_templates" ADD COLUMN "name" character varying NOT NULL;
-- Indexes: MissingInSource
CREATE UNIQUE INDEX pk_permission_templates ON public.permission_templates USING btree (id)
-- Table: permission_template_mappings
-- CreateScript: MissingInSource
CREATE TABLE "permission_template_mappings" ("id" integer NOT NULL, "permission_template_id" integer NOT NULL, "permission_id" uuid NOT NULL, PRIMARY KEY ("id"));
-- PrimaryKeys: MissingInSource
ALTER TABLE "permission_template_mappings" ADD CONSTRAINT "pk_permission_template_mappings" PRIMARY KEY (id);
-- Columns: MissingInSource
ALTER TABLE "permission_template_mappings" ADD COLUMN "id" integer NOT NULL;
-- Columns: MissingInSource
ALTER TABLE "permission_template_mappings" ADD COLUMN "permission_template_id" integer NOT NULL;
-- Columns: MissingInSource
ALTER TABLE "permission_template_mappings" ADD COLUMN "permission_id" uuid NOT NULL;
-- ForeignKeys: MissingInSource
ALTER TABLE "permission_template_mappings" ADD CONSTRAINT "fk_permission_template_mappings_permission_templates_permissio" FOREIGN KEY (permission_template_id) REFERENCES permission_templates(id) ON DELETE CASCADE;
-- Indexes: MissingInSource
CREATE INDEX ix_permission_template_mappings_permission_template_id ON public.permission_template_mappings USING btree (permission_template_id)
-- Indexes: MissingInSource
CREATE UNIQUE INDEX pk_permission_template_mappings ON public.permission_template_mappings USING btree (id)
-- Function: pgp_sym_decrypt_bytea
CREATE OR REPLACE FUNCTION public.pgp_sym_decrypt_bytea(bytea, text, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_sym_decrypt_bytea$function$
-- Function: pgp_pub_encrypt
CREATE OR REPLACE FUNCTION public.pgp_pub_encrypt(text, bytea)
RETURNS bytea
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_encrypt_text$function$
-- Function: pgp_pub_encrypt_bytea
CREATE OR REPLACE FUNCTION public.pgp_pub_encrypt_bytea(bytea, bytea)
RETURNS bytea
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_encrypt_bytea$function$
-- Function: pgp_pub_encrypt
CREATE OR REPLACE FUNCTION public.pgp_pub_encrypt(text, bytea, text)
RETURNS bytea
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_encrypt_text$function$
-- Function: pgp_pub_decrypt
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt(bytea, bytea, text)
RETURNS text
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_text$function$
-- Function: pgp_pub_decrypt_bytea
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt_bytea(bytea, bytea, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_bytea$function$
-- Function: pgp_pub_decrypt
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt(bytea, bytea, text, text)
RETURNS text
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_text$function$
-- Function: pgp_pub_decrypt_bytea
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt_bytea(bytea, bytea, text, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_bytea$function$
-- Function: pgp_key_id
CREATE OR REPLACE FUNCTION public.pgp_key_id(bytea)
RETURNS text
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_key_id_w$function$
-- Function: armor
CREATE OR REPLACE FUNCTION public.armor(bytea)
RETURNS text
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_armor$function$
-- Function: armor
CREATE OR REPLACE FUNCTION public.armor(bytea, text[], text[])
RETURNS text
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_armor$function$
-- Function: dearmor
CREATE OR REPLACE FUNCTION public.dearmor(text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_dearmor$function$
-- Function: pgp_armor_headers
CREATE OR REPLACE FUNCTION public.pgp_armor_headers(text, OUT key text, OUT value text)
RETURNS SETOF record
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_armor_headers$function$
-- Function: fips_mode
CREATE OR REPLACE FUNCTION public.fips_mode()
RETURNS boolean
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_check_fipsmode$function$
-- Function: uuid_nil
CREATE OR REPLACE FUNCTION public.uuid_nil()
RETURNS uuid
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_nil$function$
-- Function: uuid_ns_dns
CREATE OR REPLACE FUNCTION public.uuid_ns_dns()
RETURNS uuid
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_ns_dns$function$
-- Function: uuid_ns_url
CREATE OR REPLACE FUNCTION public.uuid_ns_url()
RETURNS uuid
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_ns_url$function$
-- Function: uuid_ns_oid
CREATE OR REPLACE FUNCTION public.uuid_ns_oid()
RETURNS uuid
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_ns_oid$function$
-- Function: uuid_ns_x500
CREATE OR REPLACE FUNCTION public.uuid_ns_x500()
RETURNS uuid
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_ns_x500$function$
-- Function: uuid_generate_v1
CREATE OR REPLACE FUNCTION public.uuid_generate_v1()
RETURNS uuid
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_generate_v1$function$
-- Function: uuid_generate_v1mc
CREATE OR REPLACE FUNCTION public.uuid_generate_v1mc()
RETURNS uuid
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_generate_v1mc$function$
-- Function: uuid_generate_v3
CREATE OR REPLACE FUNCTION public.uuid_generate_v3(namespace uuid, name text)
RETURNS uuid
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_generate_v3$function$
-- Function: uuid_generate_v4
CREATE OR REPLACE FUNCTION public.uuid_generate_v4()
RETURNS uuid
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_generate_v4$function$
-- Function: uuid_generate_v5
CREATE OR REPLACE FUNCTION public.uuid_generate_v5(namespace uuid, name text)
RETURNS uuid
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/uuid-ossp', $function$uuid_generate_v5$function$
-- Function: generate_permission_description
CREATE OR REPLACE FUNCTION public.generate_permission_description(p_name text)
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
parts text[];
module text;
tail text[];
action text;
object_tokens text[];
object_raw text;
object_phrase text;
is_myspace boolean := false;
-- scratch vars for “humanize” and “pluralize”
t text;
obj_lc text;
BEGIN
-- Expect Dhanman.<Module>...
parts := regexp_split_to_array(p_name, '\.');
IF array_length(parts, 1) IS NULL OR parts[1] <> 'Dhanman' THEN
RETURN NULL; -- not our namespace
END IF;
module := parts[2];
is_myspace := (module = 'MySpace');
IF array_length(parts, 1) < 3 THEN
-- e.g., Dhanman.Read / Dhanman.Write / Dhanman.Delete
RETURN trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g')) || ' permission';
END IF;
tail := parts[3:array_length(parts,1)]; -- everything after module
-- detect multi-word actions first
IF tail[array_length(tail,1)] = 'SendForApproval' THEN
action := 'SendForApproval';
IF array_length(tail,1) > 1 THEN
object_tokens := tail[1:array_length(tail,1)-1];
ELSE
object_tokens := ARRAY[]::text[];
END IF;
ELSIF tail[array_length(tail,1)] IN ('IdRead','TokenRead') THEN
action := 'Read';
IF array_length(tail,1) > 1 THEN
object_tokens := tail[1:array_length(tail,1)-1];
ELSE
object_tokens := ARRAY[]::text[];
END IF;
ELSE
-- simple actions (last token)
IF tail[array_length(tail,1)] IN ('Read','Write','Delete','Approve','Cancel','Reject','Resolve','Assign','Copy','Pay','Import') THEN
action := tail[array_length(tail,1)];
IF array_length(tail,1) > 1 THEN
object_tokens := tail[1:array_length(tail,1)-1];
ELSE
object_tokens := ARRAY[]::text[];
END IF;
ELSE
-- No explicit action => treat the last token as (maybe) object and assume base Read on module
action := NULL;
object_tokens := tail;
END IF;
END IF;
-- join tokens with '.' to get a raw object handle
object_raw := NULL;
IF object_tokens IS NOT NULL AND array_length(object_tokens,1) IS NOT NULL THEN
SELECT string_agg(x, '.' ORDER BY ord)
INTO object_raw
FROM (
SELECT object_tokens[i] AS x, i AS ord
FROM generate_subscripts(object_tokens,1) AS i
) s;
END IF;
-- If action is NULL, try to interpret tail as a “Read/Write/Delete” on module (rare fallback)
IF action IS NULL THEN
IF object_raw ILIKE 'Read' THEN
t := trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'));
RETURN 'Read ' || lower(t) || ' data';
ELSIF object_raw ILIKE 'Write' THEN
t := trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'));
RETURN 'Add/Update ' || lower(t) || ' data';
ELSIF object_raw ILIKE 'Delete' THEN
RETURN 'Delete general data';
END IF;
-- else continue with generic handling below
END IF;
-- ===== normalize object to a nicer phrase =====
object_phrase := NULL;
IF object_raw IS NOT NULL AND object_raw <> '' THEN
obj_lc := lower(object_raw);
-- specific aliases
IF obj_lc ~* '^basic(\.read|\.write|\.delete)?$' THEN
object_phrase := 'Basic Info';
ELSIF obj_lc LIKE 'ticketstatus%' THEN
object_phrase := 'Ticket Status';
ELSIF obj_lc = 'invoicetemplate' THEN
object_phrase := 'Invoice Template';
ELSIF obj_lc = 'groupedinvoice' THEN
object_phrase := 'Grouped Invoice';
ELSIF obj_lc = 'recurringinvoice' THEN
object_phrase := 'Recurring Invoice';
ELSIF obj_lc = 'customeraccountconfig' THEN
object_phrase := 'Customer Account Configuration';
ELSIF obj_lc = 'vendoraccountconfig' THEN
object_phrase := 'Vendor Account Configuration';
ELSIF obj_lc = 'bankstatement' THEN
object_phrase := 'Bank Statement';
ELSIF obj_lc = 'banktransfer' THEN
object_phrase := 'Bank Transfer';
ELSIF obj_lc = 'fixeddeposit' THEN
object_phrase := 'Fixed Deposit';
ELSE
-- generic humanize: add spaces before capitals
object_phrase := trim(both ' ' FROM regexp_replace(object_raw, '([a-z])([A-Z])', '\1 \2', 'g'));
END IF;
END IF;
-- quick helpers inlined:
-- humanize(module)
t := trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'));
-- module phrase
t := t || ' module';
-- MySpace special-casing (root objects)
IF is_myspace AND (object_phrase IS NULL OR object_phrase = '') THEN
IF action = 'Read' THEN
RETURN 'View own My Space data. (myspace)';
ELSIF action = 'Write' THEN
RETURN 'Create or update own data. (myspace)';
ELSIF action = 'Delete' THEN
RETURN 'Delete own entries. (myspace)';
END IF;
END IF;
-- pluralize (very naive) if we need a plural in messages
-- compute plural into obj_lc (reuse var)
IF object_phrase IS NOT NULL AND object_phrase <> '' THEN
obj_lc := lower(object_phrase);
-- don’t pluralize these:
IF obj_lc NOT IN ('info','data','ledger','cash flow','journal','basicinfo','basic info') THEN
IF obj_lc !~ '(s|x|z|ch|sh)$' THEN
object_phrase := object_phrase || 's';
END IF;
END IF;
END IF;
-- Render phrases by action
IF is_myspace THEN
-- MySpace + specific objects
IF action = 'Read' AND object_phrase IS NOT NULL THEN
IF lower(object_phrase) IN ('user', 'users') THEN
RETURN 'View user (own) details. (myspace)';
ELSIF lower(object_phrase) IN ('due','dues') THEN
RETURN 'View dues summary. (myspace)';
ELSE
RETURN 'View own ' || lower(object_phrase) || '. (myspace)';
END IF;
END IF;
-- For other MySpace actions on specific objects, fall through to generic wording below
END IF;
-- rebuild a fresh humanized module text for generic lines
t := trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'));
t := t || ' module';
CASE action
WHEN 'Read' THEN
IF object_phrase IS NULL OR object_phrase = '' THEN
RETURN 'Read ' || lower(trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'))) || ' data';
ELSE
RETURN 'Read ' || lower(object_phrase) || ' in ' || t;
END IF;
WHEN 'Write' THEN
IF object_phrase IS NULL OR object_phrase = '' THEN
RETURN 'Add/Update ' || lower(trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'))) || ' data';
ELSE
RETURN 'Add/Update ' || lower(object_phrase) || ' in ' || t;
END IF;
WHEN 'Delete' THEN
IF object_phrase IS NULL OR object_phrase = '' THEN
RETURN 'Delete general data';
ELSE
RETURN 'Delete ' || lower(object_phrase) || ' in ' || t;
END IF;
WHEN 'Approve' THEN
IF object_phrase IS NULL OR object_phrase = '' THEN
RETURN 'Approve ' || lower(trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'))) || ' actions';
ELSE
RETURN 'Approve ' || lower(object_phrase) || ' in the ' || t;
END IF;
WHEN 'Cancel' THEN
RETURN 'Cancel ' || lower(COALESCE(object_phrase,'items')) || ' in ' || t;
WHEN 'Reject' THEN
RETURN 'Reject ' || lower(COALESCE(object_phrase,'items')) || ' in ' || t;
WHEN 'Resolve' THEN
RETURN 'Mark ' || lower(COALESCE(object_phrase,'items')) || ' as resolved in the ' || t;
WHEN 'Assign' THEN
RETURN 'Assign ' || lower(COALESCE(object_phrase,'items')) || ' in the ' || t;
WHEN 'Copy' THEN
RETURN 'Copy ' || lower(COALESCE(object_phrase,'items')) || ' in ' || t;
WHEN 'Pay' THEN
RETURN 'Make payments for ' || lower(COALESCE(object_phrase,'items'));
WHEN 'SendForApproval' THEN
RETURN 'Send ' || lower(COALESCE(object_phrase,'items')) || ' for approval';
WHEN 'Import' THEN
RETURN 'Import ' || lower(COALESCE(object_phrase,'items')) || ' data';
ELSE
-- Fallback
IF object_phrase IS NULL OR object_phrase = '' THEN
RETURN trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g')) || ' permission';
ELSE
RETURN trim(both ' ' FROM regexp_replace(module, '([a-z])([A-Z])', '\1 \2', 'g'))
|| ' ' || trim(both ' ' FROM regexp_replace(object_phrase, '([a-z])([A-Z])', '\1 \2', 'g'))
|| ' permission';
END IF;
END CASE;
END;
$function$
-- Function: get_account_expense_monthly_breakdown
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_account_expense_monthly_breakdown(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(account_id uuid, account_name text, year integer, apr numeric, apr_diff numeric, may numeric, may_diff numeric, jun numeric, jun_diff numeric, jul numeric, jul_diff numeric, aug numeric, aug_diff numeric, sep numeric, sep_diff numeric, oct numeric, oct_diff numeric, nov numeric, nov_diff numeric, "dec" numeric, dec_diff numeric, jan numeric, jan_diff numeric, feb numeric, feb_diff numeric, mar numeric, mar_diff numeric, total numeric, difference numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_organization_id UUID;
v_start_date DATE;
v_end_date DATE;
v_prev_start_date DATE;
v_prev_end_date DATE;
BEGIN
-- Get organization and financial year details
SELECT c.organization_id INTO v_organization_id FROM public.companies c WHERE c.id = p_company_id;
SELECT fy.start_date, fy.end_date INTO v_start_date, v_end_date FROM public.finance_year fy WHERE fy.id = p_finance_year_id;
SELECT pfy.start_date, pfy.end_date INTO v_prev_start_date, v_prev_end_date FROM public.finance_year pfy WHERE pfy.id = (p_finance_year_id - 1);
RETURN QUERY
WITH expense_accounts AS (
SELECT DISTINCT coa.id AS account_id, coa.name
FROM chart_of_accounts coa
JOIN account_types at ON coa.account_type_id = at.id
WHERE at.id IN (SELECT id FROM get_account_type_hierarchy(5)) AND coa.organization_id = v_organization_id
),
years AS (
SELECT p_finance_year_id AS year UNION ALL SELECT p_finance_year_id - 1
),
all_accounts AS (
SELECT ea.account_id, ea.name, y.year
FROM expense_accounts ea CROSS JOIN years y
),
monthly_expenses AS (
-- Get monthly totals, excluding opening balance entries
SELECT
je.account_id,
CASE
WHEN je.transaction_date BETWEEN v_start_date AND v_end_date THEN p_finance_year_id
WHEN je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date THEN (p_finance_year_id - 1)
END AS year,
EXTRACT(MONTH FROM je.transaction_date) AS month,
SUM(je.amount) AS amount
FROM journal_entries je
JOIN transaction_headers th ON je.transaction_id = th.id
WHERE th.company_id = p_company_id
AND je.transaction_date BETWEEN v_prev_start_date AND v_end_date
AND th.transaction_source_type != 18
AND th.is_deleted = FALSE
AND je.is_deleted = FALSE
GROUP BY je.account_id, year, EXTRACT(MONTH FROM je.transaction_date)
),
pivoted_expenses AS (
-- Pivot data to month-wise columns
SELECT
aa.account_id, aa.name AS account_name, aa.year,
COALESCE(SUM(CASE WHEN me.month = 4 THEN me.amount ELSE 0 END), 0) AS apr,
COALESCE(SUM(CASE WHEN me.month = 5 THEN me.amount ELSE 0 END), 0) AS may,
COALESCE(SUM(CASE WHEN me.month = 6 THEN me.amount ELSE 0 END), 0) AS jun,
COALESCE(SUM(CASE WHEN me.month = 7 THEN me.amount ELSE 0 END), 0) AS jul,
COALESCE(SUM(CASE WHEN me.month = 8 THEN me.amount ELSE 0 END), 0) AS aug,
COALESCE(SUM(CASE WHEN me.month = 9 THEN me.amount ELSE 0 END), 0) AS sep,
COALESCE(SUM(CASE WHEN me.month = 10 THEN me.amount ELSE 0 END), 0) AS oct,
COALESCE(SUM(CASE WHEN me.month = 11 THEN me.amount ELSE 0 END), 0) AS nov,
COALESCE(SUM(CASE WHEN me.month = 12 THEN me.amount ELSE 0 END), 0) AS "dec",
COALESCE(SUM(CASE WHEN me.month = 1 THEN me.amount ELSE 0 END), 0) AS jan,
COALESCE(SUM(CASE WHEN me.month = 2 THEN me.amount ELSE 0 END), 0) AS feb,
COALESCE(SUM(CASE WHEN me.month = 3 THEN me.amount ELSE 0 END), 0) AS mar,
COALESCE(SUM(me.amount), 0) AS total
FROM all_accounts aa
LEFT JOIN monthly_expenses me ON aa.account_id = me.account_id AND aa.year = me.year
GROUP BY aa.account_id, aa.name, aa.year
)
-- Final table with difference calculation
SELECT
pe.account_id, pe.account_name, pe.year,
pe.apr, COALESCE(pe.apr - LAG(pe.apr) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS apr_diff,
pe.may, COALESCE(pe.may - LAG(pe.may) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS may_diff,
pe.jun, COALESCE(pe.jun - LAG(pe.jun) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jun_diff,
pe.jul, COALESCE(pe.jul - LAG(pe.jul) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jul_diff,
pe.aug, COALESCE(pe.aug - LAG(pe.aug) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS aug_diff,
pe.sep, COALESCE(pe.sep - LAG(pe.sep) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS sep_diff,
pe.oct, COALESCE(pe.oct - LAG(pe.oct) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS oct_diff,
pe.nov, COALESCE(pe.nov - LAG(pe.nov) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS nov_diff,
pe.dec, COALESCE(pe.dec - LAG(pe.dec) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS dec_diff,
pe.jan, COALESCE(pe.jan - LAG(pe.jan) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jan_diff,
pe.feb, COALESCE(pe.feb - LAG(pe.feb) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS feb_diff,
pe.mar, COALESCE(pe.mar - LAG(pe.mar) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS mar_diff,
pe.total,
COALESCE(pe.total - LAG(pe.total) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS difference
FROM pivoted_expenses pe
ORDER BY pe.account_name, pe.year;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_account_expense_monthly_breakdown(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(account_name text, year integer, apr numeric, apr_diff numeric, may numeric, may_diff numeric, jun numeric, jun_diff numeric, jul numeric, jul_diff numeric, aug numeric, aug_diff numeric, sep numeric, sep_diff numeric, oct numeric, oct_diff numeric, nov numeric, nov_diff numeric, "dec" numeric, dec_diff numeric, jan numeric, jan_diff numeric, feb numeric, feb_diff numeric, mar numeric, mar_diff numeric, total numeric, difference numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_organization_id UUID;
v_start_date DATE;
v_end_date DATE;
v_prev_start_date DATE;
v_prev_end_date DATE;
BEGIN
-- Get the organization_id for the given company_id
SELECT c.organization_id INTO v_organization_id
FROM public.companies c
WHERE c.id = p_company_id;
-- Get the start and end dates for the selected financial year
SELECT fy.start_date, fy.end_date INTO v_start_date, v_end_date
FROM public.finance_year fy
WHERE fy.id = p_finance_year_id;
-- Get the start and end dates for the previous financial year
SELECT pfy.start_date, pfy.end_date INTO v_prev_start_date, v_prev_end_date
FROM public.finance_year pfy
WHERE pfy.id = (p_finance_year_id - 1);
RETURN QUERY
WITH expense_accounts AS (
-- Fetch all expense accounts
SELECT DISTINCT coa.id AS account_id, coa.name
FROM chart_of_accounts coa
JOIN account_types at ON coa.account_type_id = at.id
WHERE at.id IN (SELECT id FROM get_account_type_hierarchy(5))
AND coa.organization_id = v_organization_id
),
years AS (
-- Ensure two years are always present
SELECT p_finance_year_id AS year
UNION ALL
SELECT p_finance_year_id - 1
),
all_accounts AS (
-- Ensure all accounts have both years
SELECT ea.account_id, ea.name, y.year
FROM expense_accounts ea
CROSS JOIN years y
),
monthly_expenses AS (
-- Get monthly totals for the selected and previous financial years
SELECT
je.account_id,
CASE
WHEN je.transaction_date BETWEEN v_start_date AND v_end_date THEN p_finance_year_id
WHEN je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date THEN (p_finance_year_id - 1)
END AS year,
EXTRACT(MONTH FROM je.transaction_date) AS month,
SUM(je.amount) AS amount
FROM journal_entries je
JOIN transaction_headers th ON je.transaction_id = th.id
WHERE th.company_id = p_company_id
AND je.transaction_date BETWEEN v_prev_start_date AND v_end_date
GROUP BY je.account_id, year, EXTRACT(MONTH FROM je.transaction_date)
),
pivoted_expenses AS (
-- Pivoting data to month-wise columns
SELECT
aa.name AS account_name,
aa.year,
COALESCE(SUM(CASE WHEN me.month = 4 THEN me.amount ELSE 0 END), 0) AS apr,
COALESCE(SUM(CASE WHEN me.month = 5 THEN me.amount ELSE 0 END), 0) AS may,
COALESCE(SUM(CASE WHEN me.month = 6 THEN me.amount ELSE 0 END), 0) AS jun,
COALESCE(SUM(CASE WHEN me.month = 7 THEN me.amount ELSE 0 END), 0) AS jul,
COALESCE(SUM(CASE WHEN me.month = 8 THEN me.amount ELSE 0 END), 0) AS aug,
COALESCE(SUM(CASE WHEN me.month = 9 THEN me.amount ELSE 0 END), 0) AS sep,
COALESCE(SUM(CASE WHEN me.month = 10 THEN me.amount ELSE 0 END), 0) AS oct,
COALESCE(SUM(CASE WHEN me.month = 11 THEN me.amount ELSE 0 END), 0) AS nov,
COALESCE(SUM(CASE WHEN me.month = 12 THEN me.amount ELSE 0 END), 0) AS "dec",
COALESCE(SUM(CASE WHEN me.month = 1 THEN me.amount ELSE 0 END), 0) AS jan,
COALESCE(SUM(CASE WHEN me.month = 2 THEN me.amount ELSE 0 END), 0) AS feb,
COALESCE(SUM(CASE WHEN me.month = 3 THEN me.amount ELSE 0 END), 0) AS mar,
COALESCE(SUM(me.amount), 0) AS total
FROM all_accounts aa
LEFT JOIN monthly_expenses me ON aa.account_id = me.account_id AND aa.year = me.year
GROUP BY aa.account_id, aa.name, aa.year
)
-- Final table with the difference calculation
SELECT
pe.account_name,
pe.year,
pe.apr, COALESCE(pe.apr - LAG(pe.apr) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS apr_diff,
pe.may, COALESCE(pe.may - LAG(pe.may) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS may_diff,
pe.jun, COALESCE(pe.jun - LAG(pe.jun) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jun_diff,
pe.jul, COALESCE(pe.jul - LAG(pe.jul) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jul_diff,
pe.aug, COALESCE(pe.aug - LAG(pe.aug) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS aug_diff,
pe.sep, COALESCE(pe.sep - LAG(pe.sep) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS sep_diff,
pe.oct, COALESCE(pe.oct - LAG(pe.oct) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS oct_diff,
pe.nov, COALESCE(pe.nov - LAG(pe.nov) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS nov_diff,
pe.dec, COALESCE(pe.dec - LAG(pe.dec) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS dec_diff,
pe.jan, COALESCE(pe.jan - LAG(pe.jan) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS jan_diff,
pe.feb, COALESCE(pe.feb - LAG(pe.feb) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS feb_diff,
pe.mar, COALESCE(pe.mar - LAG(pe.mar) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS mar_diff,
pe.total,
COALESCE(pe.total - LAG(pe.total) OVER (PARTITION BY pe.account_name ORDER BY pe.year), 0) AS difference
FROM pivoted_expenses pe
ORDER BY pe.account_name, pe.year;
END;
$function$
-- Function: get_income_expense_overview
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_income_expense_overview(p_company_id uuid, p_period_type integer, p_finance_id integer)
RETURNS TABLE(period text, year numeric, total_income numeric, total_expense numeric, actual_income numeric, actual_expense numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_financial_year_start timestamp;
v_financial_year_end timestamp;
CONST_MONTHLY CONSTANT INTEGER := 1;
CONST_QUARTERLY CONSTANT INTEGER := 2;
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
CONST_YEARLY CONSTANT INTEGER := 4;
CONST_YTD CONSTANT INTEGER := 5;
BEGIN
------------------------------------------------------------------
-- Rolling 12-month window for dashboard behavior
-- Keeps API contract same, handles period internally
------------------------------------------------------------------
IF p_period_type IN (CONST_MONTHLY, CONST_QUARTERLY, CONST_HALF_YEARLY, CONST_YEARLY) THEN
v_financial_year_start := date_trunc('month', CURRENT_DATE) - interval '11 months';
v_financial_year_end := date_trunc('month', CURRENT_DATE) + interval '1 month';
END IF;
------------------------------------------------------------------
-- YTD (actual data using selected finance year)
------------------------------------------------------------------
IF p_period_type = CONST_YTD THEN
RETURN QUERY
WITH recent_years AS (
SELECT
fy.id,
fy.start_date,
fy.end_date,
EXTRACT(YEAR FROM fy.start_date) AS fiscal_year
FROM public.finance_year fy
WHERE EXTRACT(YEAR FROM fy.start_date) >= EXTRACT(YEAR FROM CURRENT_DATE) - 4
AND EXTRACT(YEAR FROM fy.start_date) <= EXTRACT(YEAR FROM CURRENT_DATE)
ORDER BY fy.start_date DESC
LIMIT 5
),
quarters AS (
SELECT
id AS finance_id,
fiscal_year,
'Q1' AS period,
make_date(fiscal_year::int, 4, 1) AS period_start,
make_date(fiscal_year::int, 6, 30) AS period_end
FROM recent_years
UNION ALL
SELECT
id,
fiscal_year,
'Q2',
make_date(fiscal_year::int, 7, 1),
make_date(fiscal_year::int, 9, 30)
FROM recent_years
UNION ALL
SELECT
id,
fiscal_year,
'Q3',
make_date(fiscal_year::int, 10, 1),
make_date(fiscal_year::int, 12, 31)
FROM recent_years
UNION ALL
SELECT
id,
fiscal_year,
'Q4',
make_date((fiscal_year::int) + 1, 1, 1),
make_date((fiscal_year::int) + 1, 3, 31)
FROM recent_years
),
filtered_je AS (
SELECT
je.amount,
je.entry_type,
tr.company_id,
je.transaction_date,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr
ON je.transaction_id = tr.id
JOIN chart_of_accounts coa
ON je.account_id = coa.id
WHERE COALESCE(je.is_deleted, FALSE) = FALSE
AND tr.company_id = p_company_id
AND COALESCE(tr.is_reversed, FALSE) = FALSE
)
SELECT
CONCAT(q.period, ' ', q.fiscal_year)::text AS period,
q.fiscal_year::numeric AS year,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (4,14,15,19,20)
AND je.transaction_date BETWEEN q.period_start AND q.period_end
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (5,16,17,18,21,22)
AND je.transaction_date BETWEEN q.period_start AND q.period_end
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_expense,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN q.period_start AND q.period_end
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN q.period_start AND q.period_end
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_expense
FROM quarters q
LEFT JOIN filtered_je je
ON je.transaction_date BETWEEN q.period_start AND q.period_end
GROUP BY q.period, q.fiscal_year
HAVING
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (4,14,15,19,20)
AND je.transaction_date BETWEEN q.period_start AND q.period_end
THEN je.amount
ELSE 0
END
), 0) > 0
OR COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (5,16,17,18,21,22)
AND je.transaction_date BETWEEN q.period_start AND q.period_end
THEN je.amount
ELSE 0
END
), 0) > 0
OR COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN q.period_start AND q.period_end
THEN je.amount
ELSE 0
END
), 0) > 0
OR COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN q.period_start AND q.period_end
THEN je.amount
ELSE 0
END
), 0) > 0
ORDER BY q.fiscal_year, q.period;
------------------------------------------------------------------
-- MONTHLY (Rolling last 12 months)
------------------------------------------------------------------
ELSIF p_period_type = CONST_MONTHLY THEN
RETURN QUERY
WITH months AS (
SELECT
gs::timestamp AS month_start,
(gs + interval '1 month')::timestamp AS month_end
FROM generate_series(
v_financial_year_start,
v_financial_year_end - interval '1 month',
interval '1 month'
) gs
),
filtered_je AS (
SELECT
je.amount,
je.entry_type,
je.transaction_date,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr
ON je.transaction_id = tr.id
JOIN chart_of_accounts coa
ON je.account_id = coa.id
WHERE tr.company_id = p_company_id
AND COALESCE(tr.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND je.transaction_date >= v_financial_year_start
AND je.transaction_date < v_financial_year_end
)
SELECT
to_char(m.month_start, 'Mon YYYY')::text AS period,
EXTRACT(YEAR FROM m.month_start)::numeric AS year,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (4, 14, 15, 19, 20)
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (5, 16, 17, 18, 21, 22)
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_expense,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (8, 9)
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (8, 9)
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_expense
FROM months m
LEFT JOIN filtered_je je
ON je.transaction_date >= m.month_start
AND je.transaction_date < m.month_end
GROUP BY m.month_start
ORDER BY m.month_start;
------------------------------------------------------------------
-- QUARTERLY (Rolling)
------------------------------------------------------------------
ELSIF p_period_type = CONST_QUARTERLY THEN
RETURN QUERY
WITH months AS (
SELECT
gs::timestamp AS month_start,
(gs + interval '1 month')::timestamp AS month_end,
date_trunc('quarter', gs)::timestamp AS quarter_start
FROM generate_series(
v_financial_year_start,
v_financial_year_end - interval '1 month',
interval '1 month'
) gs
),
filtered_je AS (
SELECT
je.amount,
je.entry_type,
je.transaction_date,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr
ON je.transaction_id = tr.id
JOIN chart_of_accounts coa
ON je.account_id = coa.id
WHERE tr.company_id = p_company_id
AND COALESCE(tr.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND je.transaction_date >= v_financial_year_start
AND je.transaction_date < v_financial_year_end
)
SELECT
to_char(m.quarter_start, '"Q"Q YYYY')::text AS period,
EXTRACT(YEAR FROM m.quarter_start)::numeric AS year,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (4, 14, 15, 19, 20)
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (5, 16, 17, 18, 21, 22)
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_expense,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (8, 9)
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (8, 9)
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_expense
FROM months m
LEFT JOIN filtered_je je
ON je.transaction_date >= m.month_start
AND je.transaction_date < m.month_end
GROUP BY m.quarter_start
ORDER BY m.quarter_start;
------------------------------------------------------------------
-- HALF YEARLY (Rolling)
------------------------------------------------------------------
ELSIF p_period_type = CONST_HALF_YEARLY THEN
RETURN QUERY
WITH months AS (
SELECT
gs::timestamp AS month_start,
(gs + interval '1 month')::timestamp AS month_end,
CASE
WHEN EXTRACT(MONTH FROM gs) <= 6
THEN date_trunc('year', gs)::timestamp
ELSE (date_trunc('year', gs) + interval '6 months')::timestamp
END AS half_start
FROM generate_series(
v_financial_year_start,
v_financial_year_end - interval '1 month',
interval '1 month'
) gs
),
filtered_je AS (
SELECT
je.amount,
je.entry_type,
je.transaction_date,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr
ON je.transaction_id = tr.id
JOIN chart_of_accounts coa
ON je.account_id = coa.id
WHERE tr.company_id = p_company_id
AND COALESCE(tr.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND je.transaction_date >= v_financial_year_start
AND je.transaction_date < v_financial_year_end
)
SELECT
(
CASE
WHEN EXTRACT(MONTH FROM m.half_start) = 1 THEN 'H1 '
ELSE 'H2 '
END || EXTRACT(YEAR FROM m.half_start)::int
)::text AS period,
EXTRACT(YEAR FROM m.half_start)::numeric AS year,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (4, 14, 15, 19, 20)
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (5, 16, 17, 18, 21, 22)
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_expense,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (8, 9)
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (8, 9)
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_expense
FROM months m
LEFT JOIN filtered_je je
ON je.transaction_date >= m.month_start
AND je.transaction_date < m.month_end
GROUP BY m.half_start
ORDER BY m.half_start;
------------------------------------------------------------------
-- YEARLY (Rolling)
------------------------------------------------------------------
ELSIF p_period_type = CONST_YEARLY THEN
RETURN QUERY
WITH months AS (
SELECT
gs::timestamp AS month_start,
(gs + interval '1 month')::timestamp AS month_end,
date_trunc('year', gs)::timestamp AS year_start
FROM generate_series(
v_financial_year_start,
v_financial_year_end - interval '1 month',
interval '1 month'
) gs
),
filtered_je AS (
SELECT
je.amount,
je.entry_type,
je.transaction_date,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr
ON je.transaction_id = tr.id
JOIN chart_of_accounts coa
ON je.account_id = coa.id
WHERE tr.company_id = p_company_id
AND COALESCE(tr.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND je.transaction_date >= v_financial_year_start
AND je.transaction_date < v_financial_year_end
)
SELECT
to_char(m.year_start, 'YYYY')::text AS period,
EXTRACT(YEAR FROM m.year_start)::numeric AS year,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (4, 14, 15, 19, 20)
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (5, 16, 17, 18, 21, 22)
THEN je.amount
ELSE 0
END
), 0)::numeric AS total_expense,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D'
AND je.account_type_id IN (8, 9)
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_income,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C'
AND je.account_type_id IN (8, 9)
THEN je.amount
ELSE 0
END
), 0)::numeric AS actual_expense
FROM months m
LEFT JOIN filtered_je je
ON je.transaction_date >= m.month_start
AND je.transaction_date < m.month_end
GROUP BY m.year_start
ORDER BY m.year_start;
ELSE
RAISE EXCEPTION 'Unsupported period type %', p_period_type;
END IF;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_income_expense_overview(p_company_id uuid, p_period_type integer, p_finance_id integer)
RETURNS TABLE(period text, year numeric, total_income numeric, total_expense numeric, actual_income numeric, actual_expense numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_financial_year_start date;
v_financial_year_end date;
v_finance_year_start numeric;
v_finance_year_end numeric;
CONST_MONTHLY CONSTANT INTEGER := 1;
CONST_QUARTERLY CONSTANT INTEGER := 2;
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
CONST_YEARLY CONSTANT INTEGER := 4;
CONST_YTD CONSTANT INTEGER := 5;
BEGIN
IF p_period_type != CONST_YTD THEN
SELECT start_date, end_date,
EXTRACT(YEAR FROM start_date),
EXTRACT(YEAR FROM end_date)
INTO v_financial_year_start, v_financial_year_end,
v_finance_year_start, v_finance_year_end
FROM public.finance_year
WHERE id = p_finance_id;
END IF;
IF p_period_type = CONST_YTD THEN
RETURN QUERY
WITH recent_years AS (
SELECT fy.id, fy.start_date, fy.end_date,
EXTRACT(YEAR FROM fy.start_date) AS year_start,
EXTRACT(YEAR FROM fy.end_date) AS year_end
FROM finance_year fy
WHERE EXTRACT(YEAR FROM fy.start_date) >= EXTRACT(YEAR FROM CURRENT_DATE) - 4
AND EXTRACT(YEAR FROM fy.start_date) <= EXTRACT(YEAR FROM CURRENT_DATE)
ORDER BY fy.start_date DESC
LIMIT 5
),
ytd_periods AS (
SELECT id AS finance_id, 'YTD H1' AS period,
(start_date + INTERVAL '5 months 29 days')::date AS period_end,
EXTRACT(YEAR FROM start_date) AS year,
start_date AS fy_start
FROM recent_years
UNION ALL
SELECT id, 'YTD H2', end_date,
EXTRACT(YEAR FROM end_date) AS year,
start_date
FROM recent_years
WHERE end_date <= CURRENT_DATE
AND CURRENT_DATE >= (start_date + INTERVAL '6 months') -- skip if it's a future YTD H2
),
filtered_je AS (
SELECT
je.amount,
je.entry_type,
tr.company_id,
tr.transaction_date,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr ON je.transaction_id = tr.id
JOIN chart_of_accounts coa ON je.account_id = coa.id
WHERE je.is_deleted = FALSE
)
SELECT
CONCAT(yp.period, ' ', yp.year) AS period,
yp.year,
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0)
FROM ytd_periods yp
LEFT JOIN filtered_je je ON je.company_id = p_company_id
GROUP BY yp.period, yp.year, yp.period_end
HAVING
SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
OR SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
OR SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
OR SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
ORDER BY yp.year, yp.period;
ELSIF p_period_type = CONST_MONTHLY THEN
RETURN QUERY
WITH months AS (
SELECT
generate_series::date AS month_start,
(generate_series + interval '1 month')::date AS month_end
FROM generate_series(
v_financial_year_start,
v_financial_year_end,
interval '1 month'
)
),
filtered_je AS (
SELECT
je.*,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr ON je.transaction_id = tr.id
JOIN chart_of_accounts coa ON je.account_id = coa.id
WHERE tr.company_id = p_company_id
AND je.is_deleted = FALSE
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
)
SELECT
to_char(m.month_start, 'Mon YYYY') AS period,
EXTRACT(YEAR FROM m.month_start) AS year,
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
FROM months m
LEFT JOIN filtered_je je ON je.transaction_date >= m.month_start AND je.transaction_date < m.month_end
GROUP BY m.month_start
ORDER BY m.month_start;
ELSIF p_period_type = CONST_QUARTERLY THEN
RETURN QUERY
WITH quarters AS (
SELECT 'Q1' AS period, 4 AS start_month, 6 AS end_month, v_finance_year_start AS year
UNION ALL SELECT 'Q2', 7, 9, v_finance_year_start
UNION ALL SELECT 'Q3', 10, 12, v_finance_year_start
UNION ALL SELECT 'Q4', 1, 3, v_finance_year_end
),
filtered_je AS (
SELECT
je.*,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr ON je.transaction_id = tr.id
JOIN chart_of_accounts coa ON je.account_id = coa.id
WHERE tr.company_id = p_company_id
AND je.is_deleted = FALSE
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
)
SELECT
CONCAT(q.period, ' ', q.year),
q.year,
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
FROM quarters q
JOIN filtered_je je ON EXTRACT(MONTH FROM je.transaction_date) BETWEEN q.start_month AND q.end_month
AND EXTRACT(YEAR FROM je.transaction_date) = q.year
GROUP BY q.period, q.year
ORDER BY q.year, q.period;
ELSIF p_period_type = CONST_HALF_YEARLY THEN
RETURN QUERY
WITH half_years AS (
SELECT 'H1' AS period, 4 AS start_month, 9 AS end_month, v_finance_year_start AS year
UNION ALL SELECT 'H2', 10, 12, v_finance_year_start
UNION ALL SELECT 'H2', 1, 3, v_finance_year_end
),
filtered_je AS (
SELECT
je.*,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr ON je.transaction_id = tr.id
JOIN chart_of_accounts coa ON je.account_id = coa.id
WHERE tr.company_id = p_company_id
AND je.is_deleted = FALSE
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
)
SELECT
CONCAT(h.period, ' ', h.year),
h.year,
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
FROM half_years h
JOIN filtered_je je ON EXTRACT(MONTH FROM je.transaction_date) BETWEEN h.start_month AND h.end_month
AND EXTRACT(YEAR FROM je.transaction_date) = h.year
GROUP BY h.period, h.year
ORDER BY h.year, h.period;
ELSIF p_period_type = CONST_YEARLY THEN
RETURN QUERY
WITH filtered_je AS (
SELECT
je.*,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers tr ON je.transaction_id = tr.id
JOIN chart_of_accounts coa ON je.account_id = coa.id
WHERE tr.company_id = p_company_id
AND je.is_deleted = FALSE
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
)
SELECT
CONCAT('Year ', EXTRACT(YEAR FROM je.transaction_date)),
EXTRACT(YEAR FROM je.transaction_date),
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
FROM filtered_je je
GROUP BY EXTRACT(YEAR FROM je.transaction_date)
ORDER BY EXTRACT(YEAR FROM je.transaction_date);
ELSE
RAISE EXCEPTION 'Unsupported period type %', p_period_type;
END IF;
END;
$function$
-- Function: get_income_expense_overview_test
CREATE OR REPLACE FUNCTION public.get_income_expense_overview_test(p_company_id uuid, p_period_type integer, p_finance_id integer)
RETURNS TABLE(period text, year numeric, total_income numeric, total_expense numeric, actual_income numeric, actual_expense numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date timestamp;
v_end_date timestamp;
CONST_MONTHLY CONSTANT INTEGER := 1;
BEGIN
------------------------------------------------------------------
-- STEP 1: DEFINE DATE RANGE (TEMP - will be removed later)
------------------------------------------------------------------
v_start_date := date_trunc('month', CURRENT_DATE) - interval '11 months';
v_end_date := date_trunc('month', CURRENT_DATE) + interval '1 month';
------------------------------------------------------------------
-- STEP 2: BASE DATASET (SINGLE SOURCE INSIDE FUNCTION)
------------------------------------------------------------------
RETURN QUERY
WITH base_data AS (
SELECT
th.transaction_date,
je.amount,
je.entry_type,
coa.account_type_id
FROM journal_entries je
JOIN transaction_headers th ON th.id = je.transaction_id
JOIN chart_of_accounts coa ON coa.id = je.account_id
WHERE
th.company_id = p_company_id
AND COALESCE(th.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND th.transaction_date >= v_start_date
AND th.transaction_date < v_end_date
),
------------------------------------------------------------------
-- STEP 3: MONTH BUCKETS
------------------------------------------------------------------
months AS (
SELECT
gs AS month_start,
(gs + interval '1 month') AS month_end
FROM generate_series(
v_start_date,
v_end_date - interval '1 month',
interval '1 month'
) gs
)
------------------------------------------------------------------
-- STEP 4: FINAL AGGREGATION
------------------------------------------------------------------
SELECT
to_char(m.month_start, 'Mon YYYY') AS period,
EXTRACT(YEAR FROM m.month_start)::numeric AS year,
ROUND(SUM(
CASE WHEN b.entry_type = 'C'
AND b.account_type_id IN (4,14,15,19,20)
THEN b.amount ELSE 0 END
), 2) AS total_income,
ROUND(SUM(
CASE WHEN b.entry_type = 'D'
AND b.account_type_id IN (5,16,17,18,21,22)
THEN b.amount ELSE 0 END
), 2) AS total_expense,
ROUND(SUM(
CASE WHEN b.entry_type = 'D'
AND b.account_type_id IN (8,9)
THEN b.amount ELSE 0 END
), 2) AS actual_income,
ROUND(SUM(
CASE WHEN b.entry_type = 'C'
AND b.account_type_id IN (8,9)
THEN b.amount ELSE 0 END
), 2) AS actual_expense
FROM months m
LEFT JOIN base_data b
ON b.transaction_date >= m.month_start
AND b.transaction_date < m.month_end
GROUP BY m.month_start
ORDER BY m.month_start;
END;
$function$
-- Function: get_expense_categorization
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_expense_categorization(p_company_id uuid, p_period_type integer, p_finance_id integer)
RETURNS TABLE(period text, year numeric, account_id uuid, account_name text, total_expense numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_financial_year_start DATE;
v_financial_year_end DATE;
v_organization_id uuid;
CONST_MONTHLY CONSTANT INTEGER := 1;
CONST_QUARTERLY CONSTANT INTEGER := 2;
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
CONST_YEARLY CONSTANT INTEGER := 4;
CONST_YTD CONSTANT INTEGER := 5;
BEGIN
-- 🔥 Rolling 12 months (for dashboard types)
IF p_period_type IN (CONST_MONTHLY, CONST_QUARTERLY, CONST_HALF_YEARLY, CONST_YEARLY) THEN
v_financial_year_start := date_trunc('month', CURRENT_DATE) - INTERVAL '11 months';
v_financial_year_end := date_trunc('month', CURRENT_DATE) + INTERVAL '1 month';
ELSE
-- YTD fallback
SELECT start_date, end_date
INTO v_financial_year_start, v_financial_year_end
FROM public.finance_year
WHERE id = p_finance_id;
END IF;
-- Org fetch
SELECT organization_id INTO v_organization_id
FROM public.companies
WHERE id = p_company_id;
IF v_organization_id IS NULL THEN
RAISE EXCEPTION 'Company % not found or missing organization_id', p_company_id;
END IF;
------------------------------------------------------------------
-- 🔹 MONTHLY (ROLLING 12 MONTHS)
------------------------------------------------------------------
IF p_period_type = CONST_MONTHLY THEN
RETURN QUERY
WITH months AS (
SELECT
gs AS month_start,
gs + interval '1 month' AS month_end,
to_char(gs, 'Mon') AS period,
EXTRACT(YEAR FROM gs) AS year
FROM generate_series(
v_financial_year_start,
v_financial_year_end - interval '1 month',
interval '1 month'
) gs
)
SELECT
m.period,
m.year,
coa.id,
coa.name,
COALESCE(SUM(je.amount), 0)
FROM months m
LEFT JOIN journal_entries je
ON je.transaction_date >= m.month_start
AND je.transaction_date < m.month_end
AND je.is_deleted = FALSE
INNER JOIN transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
AND th.is_reversed = FALSE
INNER JOIN chart_of_accounts coa
ON je.account_id = coa.id
AND coa.organization_id = v_organization_id
AND coa.is_deleted = FALSE
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.entry_type = 'D'
GROUP BY m.period, m.year, m.month_start, coa.id, coa.name
ORDER BY m.month_start, coa.name;
------------------------------------------------------------------
-- 🔹 QUARTERLY (ROLLING)
------------------------------------------------------------------
ELSIF p_period_type = CONST_QUARTERLY THEN
RETURN QUERY
WITH months AS (
SELECT
gs AS month_start,
gs + interval '1 month' AS month_end,
date_trunc('quarter', gs) AS quarter_start
FROM generate_series(
v_financial_year_start,
v_financial_year_end - interval '1 month',
interval '1 month'
) gs
)
SELECT
to_char(m.quarter_start, '"Q"Q') AS period,
EXTRACT(YEAR FROM m.quarter_start),
coa.id,
coa.name,
COALESCE(SUM(je.amount), 0)
FROM months m
LEFT JOIN journal_entries je
ON je.transaction_date >= m.month_start
AND je.transaction_date < m.month_end
AND je.is_deleted = FALSE
INNER JOIN transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
AND th.is_reversed = FALSE
INNER JOIN chart_of_accounts coa
ON je.account_id = coa.id
AND coa.organization_id = v_organization_id
AND coa.is_deleted = FALSE
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.entry_type = 'D'
GROUP BY m.quarter_start, coa.id, coa.name
ORDER BY m.quarter_start, coa.name;
------------------------------------------------------------------
-- 🔹 HALF YEARLY (ROLLING)
------------------------------------------------------------------
ELSIF p_period_type = CONST_HALF_YEARLY THEN
RETURN QUERY
WITH months AS (
SELECT
gs AS month_start,
gs + interval '1 month' AS month_end,
CASE
WHEN EXTRACT(MONTH FROM gs) <= 6
THEN date_trunc('year', gs)
ELSE date_trunc('year', gs) + interval '6 months'
END AS half_start
FROM generate_series(
v_financial_year_start,
v_financial_year_end - interval '1 month',
interval '1 month'
) gs
)
SELECT
to_char(m.half_start, '"H"1') AS period,
EXTRACT(YEAR FROM m.half_start),
coa.id,
coa.name,
COALESCE(SUM(je.amount), 0)
FROM months m
LEFT JOIN journal_entries je
ON je.transaction_date >= m.month_start
AND je.transaction_date < m.month_end
AND je.is_deleted = FALSE
INNER JOIN transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
AND th.is_reversed = FALSE
INNER JOIN chart_of_accounts coa
ON je.account_id = coa.id
AND coa.organization_id = v_organization_id
AND coa.is_deleted = FALSE
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.entry_type = 'D'
GROUP BY m.half_start, coa.id, coa.name
ORDER BY m.half_start, coa.name;
------------------------------------------------------------------
-- 🔹 YEARLY (ROLLING)
------------------------------------------------------------------
ELSIF p_period_type = CONST_YEARLY THEN
RETURN QUERY
WITH months AS (
SELECT
gs AS month_start,
gs + interval '1 month' AS month_end,
date_trunc('year', gs) AS year_start
FROM generate_series(
v_financial_year_start,
v_financial_year_end - interval '1 month',
interval '1 month'
) gs
)
SELECT
to_char(m.year_start, 'YYYY') AS period,
EXTRACT(YEAR FROM m.year_start),
coa.id,
coa.name,
COALESCE(SUM(je.amount), 0)
FROM months m
LEFT JOIN journal_entries je
ON je.transaction_date >= m.month_start
AND je.transaction_date < m.month_end
AND je.is_deleted = FALSE
INNER JOIN transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
AND th.is_reversed = FALSE
INNER JOIN chart_of_accounts coa
ON je.account_id = coa.id
AND coa.organization_id = v_organization_id
AND coa.is_deleted = FALSE
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.entry_type = 'D'
GROUP BY m.year_start, coa.id, coa.name
ORDER BY m.year_start, coa.name;
------------------------------------------------------------------
-- 🔹 YTD (UNCHANGED)
------------------------------------------------------------------
ELSIF p_period_type = CONST_YTD THEN
RETURN QUERY
SELECT
'YTD',
EXTRACT(YEAR FROM CURRENT_DATE),
coa.id,
coa.name,
COALESCE(SUM(je.amount), 0)
FROM journal_entries je
JOIN transaction_headers th ON je.transaction_id = th.id
JOIN chart_of_accounts coa ON je.account_id = coa.id
WHERE th.company_id = p_company_id
AND coa.organization_id = v_organization_id
AND je.entry_type = 'D'
AND je.is_deleted = FALSE
AND th.is_reversed = FALSE
GROUP BY coa.id, coa.name
ORDER BY total_expense DESC;
ELSE
RAISE EXCEPTION 'Unsupported period type %', p_period_type;
END IF;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_expense_categorization(p_company_id uuid, p_period_type integer, p_finance_id integer)
RETURNS TABLE(period text, year numeric, account_id uuid, account_name text, total_expense numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_financial_year_start DATE;
v_financial_year_end DATE;
BEGIN
-- Step 1: Get financial year boundaries
SELECT start_date, end_date INTO v_financial_year_start, v_financial_year_end
FROM public.finance_year
WHERE id = p_finance_id;
-- Raise an exception if financial year is not found
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
RAISE EXCEPTION 'Financial year with ID % not found', p_finance_id;
END IF;
-- Step 2: Categorize expenses based on period type
IF p_period_type = 1 THEN -- Monthly Categorization
RETURN QUERY
WITH months AS (
SELECT
TO_CHAR(m, 'Mon') AS period,
EXTRACT(YEAR FROM m) AS year,
EXTRACT(MONTH FROM m) AS month_num
FROM generate_series(v_financial_year_start, v_financial_year_end, '1 month'::interval) AS m
)
SELECT
months.period,
months.year,
coa.id AS account_id, -- 🔹 Added missing account_id
coa.name AS account_name,
COALESCE(SUM(je.amount), 0) AS total_expense
FROM months
LEFT JOIN public.journal_entries je
ON EXTRACT(MONTH FROM je.transaction_date) = months.month_num
AND EXTRACT(YEAR FROM je.transaction_date) = months.year
INNER JOIN public.transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
INNER JOIN public.chart_of_accounts coa
ON je.account_id = coa.id
WHERE
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.entry_type = 'D'
AND je.is_deleted = FALSE
GROUP BY months.year, months.month_num, months.period, coa.id, coa.name -- 🔹 Added `coa.id` to GROUP BY
ORDER BY months.year, months.month_num, coa.name;
ELSIF p_period_type = 2 THEN -- Quarterly Categorization
RETURN QUERY
WITH quarters AS (
SELECT 'Q1' AS period, v_financial_year_start AS start_date, v_financial_year_start + interval '2 month' AS end_date
UNION ALL
SELECT 'Q2', v_financial_year_start + interval '3 month', v_financial_year_start + interval '5 month'
UNION ALL
SELECT 'Q3', v_financial_year_start + interval '6 month', v_financial_year_start + interval '8 month'
UNION ALL
SELECT 'Q4', v_financial_year_start + interval '9 month', v_financial_year_end
)
SELECT
quarters.period,
EXTRACT(YEAR FROM quarters.start_date) AS year,
coa.id AS account_id, -- Added account_id
coa.name AS account_name,
COALESCE(SUM(je.amount), 0) AS total_expense
FROM quarters
LEFT JOIN public.journal_entries je
ON je.transaction_date BETWEEN quarters.start_date AND quarters.end_date
INNER JOIN public.transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
INNER JOIN public.chart_of_accounts coa
ON je.account_id = coa.id
WHERE
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.entry_type = 'D'
AND je.is_deleted = FALSE
GROUP BY quarters.period, year, coa.id, coa.name
ORDER BY year, quarters.period, coa.name;
ELSIF p_period_type = 3 THEN -- Half-Yearly Categorization
RETURN QUERY
WITH half_years AS (
SELECT 'H1' AS period, v_financial_year_start AS start_date, v_financial_year_start + interval '5 month' AS end_date
UNION ALL
SELECT 'H2', v_financial_year_start + interval '6 month', v_financial_year_end
)
SELECT
half_years.period,
EXTRACT(YEAR FROM half_years.start_date) AS year,
coa.id AS account_id, -- Added account_id
coa.name AS account_name,
COALESCE(SUM(je.amount), 0) AS total_expense
FROM half_years
LEFT JOIN public.journal_entries je
ON je.transaction_date BETWEEN half_years.start_date AND half_years.end_date
INNER JOIN public.transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
INNER JOIN public.chart_of_accounts coa
ON je.account_id = coa.id
WHERE
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.entry_type = 'D'
AND je.is_deleted = FALSE
GROUP BY half_years.period, year, coa.id, coa.name
ORDER BY year, half_years.period, coa.name;
ELSIF p_period_type = 4 THEN -- Yearly Categorization
RETURN QUERY
SELECT
'Year' AS period,
EXTRACT(YEAR FROM je.transaction_date) AS year,
coa.id AS account_id, -- Added account_id
coa.name AS account_name,
COALESCE(SUM(je.amount), 0) AS total_expense
FROM public.journal_entries je
INNER JOIN public.transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
INNER JOIN public.chart_of_accounts coa
ON je.account_id = coa.id
WHERE
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.entry_type = 'D'
AND je.is_deleted = FALSE
GROUP BY year, coa.id, coa.name
ORDER BY year, coa.name;
END IF;
END;
$function$
-- Function: get_top_expense_categories
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_top_expense_categories(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(account_id uuid, account_name text, account_number text, total_expense numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
-- 🔥 Rolling window instead of financial year
v_start_date DATE := date_trunc('month', CURRENT_DATE) - INTERVAL '11 months';
v_end_date DATE := date_trunc('month', CURRENT_DATE) + INTERVAL '1 month';
BEGIN
-- 🔥 Top 10 Expense Accounts (Rolling 12 Months)
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense
FROM journal_entries je
INNER JOIN chart_of_accounts coa
ON je.account_id = coa.id
INNER JOIN account_types at
ON coa.account_type_id = at.id
INNER JOIN transaction_headers th
ON je.transaction_id = th.id
WHERE at.id IN (SELECT id FROM get_account_type_hierarchy(5))
AND th.company_id = p_company_id
AND je.transaction_date >= v_start_date
AND je.transaction_date < v_end_date
AND je.is_deleted = FALSE
AND th.is_reversed = FALSE
AND je.entry_type = 'D'
GROUP BY coa.id, coa.name, coa.account_number
ORDER BY total_expense DESC
LIMIT 10;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_top_expense_categories(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(account_id uuid, account_name text, account_number text, total_expense numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_financial_year_start DATE;
v_financial_year_end DATE;
BEGIN
-- Fetch financial year start and end dates
SELECT start_date, end_date
INTO v_financial_year_start, v_financial_year_end
FROM public.finance_year
WHERE id = p_finance_year_id;
-- Raise an exception if financial year is not found
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
RAISE EXCEPTION 'Financial year with ID % not found', p_finance_year_id;
END IF;
-- Calculate and return top 10 Expense accounts using `get_account_type_hierarchy(5)`
RETURN QUERY
SELECT
coa.id As account_id,
coa.name AS account_name,
coa.account_number AS account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense
FROM public.journal_entries je
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
INNER JOIN public.account_types at ON coa.account_type_id = at.id
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
WHERE at.id IN (SELECT id FROM get_account_type_hierarchy(5)) -- Dynamically fetch all expense types
AND th.company_id = p_company_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
GROUP BY coa.id, coa.name, coa.account_number
ORDER BY total_expense DESC
LIMIT 10;
END;
$function$
-- Function: get_account_expense_breakdown
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_account_expense_breakdown(p_company_id uuid, p_finance_id integer, p_period_type integer, p_period integer)
RETURNS TABLE(account_id uuid, account_name text, account_number text, total_expense numeric, financial_year text)
LANGUAGE plpgsql
STABLE PARALLEL SAFE
AS $function$
DECLARE
v_rolling_start DATE;
v_rolling_end DATE; -- exclusive
v_finance_year TEXT;
v_period_start DATE;
v_period_end DATE;
CONST_MONTHLY CONSTANT INTEGER := 1;
CONST_QUARTERLY CONSTANT INTEGER := 2;
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
CONST_YEARLY CONSTANT INTEGER := 4;
CONST_YTD CONSTANT INTEGER := 5;
BEGIN
-- Rolling 12-month window
v_rolling_start := date_trunc('month', CURRENT_DATE) - interval '11 months';
v_rolling_end := date_trunc('month', CURRENT_DATE) + interval '1 month';
-- Show visible inclusive month range (e.g. May 2025 - Apr 2026)
v_finance_year := to_char(v_rolling_start, 'Mon YYYY')
|| ' - ' ||
to_char(v_rolling_end - interval '1 month', 'Mon YYYY');
-- Validate period ranges by type
IF p_period_type = CONST_MONTHLY AND (p_period < 1 OR p_period > 12) THEN
RAISE EXCEPTION 'Invalid period for monthly: %. Expected 1..12', p_period;
ELSIF p_period_type = CONST_QUARTERLY AND (p_period < 1 OR p_period > 4) THEN
RAISE EXCEPTION 'Invalid period for quarterly: %. Expected 1..4', p_period;
ELSIF p_period_type = CONST_HALF_YEARLY AND (p_period < 1 OR p_period > 2) THEN
RAISE EXCEPTION 'Invalid period for half-yearly: %. Expected 1..2', p_period;
END IF;
IF p_period_type = CONST_MONTHLY THEN
v_period_start := v_rolling_start + ((p_period - 1) * interval '1 month');
v_period_end := v_period_start + interval '1 month';
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name::text AS account_name,
coa.account_number::text AS account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
v_finance_year::text AS financial_year
FROM public.journal_entries je
JOIN public.transaction_headers th ON je.transaction_id = th.id
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE th.company_id = p_company_id
AND COALESCE(th.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND je.entry_type = 'D'
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
AND coa.name NOT IN ('Rounding Gain')
AND je.transaction_date >= v_period_start
AND je.transaction_date < v_period_end
GROUP BY coa.id, coa.name, coa.account_number
ORDER BY total_expense DESC;
ELSIF p_period_type = CONST_QUARTERLY THEN
v_period_start := v_rolling_start + ((p_period - 1) * interval '3 months');
v_period_end := v_period_start + interval '3 months';
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name::text AS account_name,
coa.account_number::text AS account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
v_finance_year::text AS financial_year
FROM public.journal_entries je
JOIN public.transaction_headers th ON je.transaction_id = th.id
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE th.company_id = p_company_id
AND COALESCE(th.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND je.entry_type = 'D'
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
AND coa.name NOT IN ('Rounding Gain')
AND je.transaction_date >= v_period_start
AND je.transaction_date < v_period_end
GROUP BY coa.id, coa.name, coa.account_number
ORDER BY total_expense DESC;
ELSIF p_period_type = CONST_HALF_YEARLY THEN
v_period_start := v_rolling_start + ((p_period - 1) * interval '6 months');
v_period_end := v_period_start + interval '6 months';
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name::text AS account_name,
coa.account_number::text AS account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
v_finance_year::text AS financial_year
FROM public.journal_entries je
JOIN public.transaction_headers th ON je.transaction_id = th.id
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE th.company_id = p_company_id
AND COALESCE(th.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND je.entry_type = 'D'
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
AND coa.name NOT IN ('Rounding Gain')
AND je.transaction_date >= v_period_start
AND je.transaction_date < v_period_end
GROUP BY coa.id, coa.name, coa.account_number
ORDER BY total_expense DESC;
ELSIF p_period_type = CONST_YEARLY THEN
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name::text AS account_name,
coa.account_number::text AS account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
v_finance_year::text AS financial_year
FROM public.journal_entries je
JOIN public.transaction_headers th ON je.transaction_id = th.id
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE th.company_id = p_company_id
AND COALESCE(th.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND je.entry_type = 'D'
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
AND coa.name NOT IN ('Rounding Gain')
AND je.transaction_date >= v_rolling_start
AND je.transaction_date < v_rolling_end
GROUP BY coa.id, coa.name, coa.account_number
ORDER BY total_expense DESC;
ELSIF p_period_type = CONST_YTD THEN
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name::text AS account_name,
coa.account_number::text AS account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2)::numeric AS total_expense,
v_finance_year::text AS financial_year
FROM public.journal_entries je
JOIN public.transaction_headers th ON je.transaction_id = th.id
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE th.company_id = p_company_id
AND COALESCE(th.is_reversed, FALSE) = FALSE
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND je.entry_type = 'D'
AND coa.account_type_id IN (5, 16, 17, 18, 21, 22)
AND coa.name NOT IN ('Rounding Gain')
AND je.transaction_date >= v_rolling_start
AND je.transaction_date <= CURRENT_DATE
GROUP BY coa.id, coa.name, coa.account_number
ORDER BY total_expense DESC;
ELSE
RAISE EXCEPTION 'Invalid period type: %', p_period_type;
END IF;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_account_expense_breakdown(p_company_id uuid, p_finance_id integer, p_period_type integer, p_period integer)
RETURNS TABLE(account_id uuid, account_name text, account_number text, total_expense numeric, financial_year text)
LANGUAGE plpgsql
STABLE PARALLEL SAFE
AS $function$
DECLARE
v_financial_year_start DATE;
v_financial_year_end DATE;
v_finance_year TEXT;
-- Period type constants
CONST_MONTHLY CONSTANT INTEGER := 1;
CONST_QUARTERLY CONSTANT INTEGER := 2;
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
CONST_YEARLY CONSTANT INTEGER := 4;
BEGIN
-- Fetch financial year details
SELECT fy.start_date, fy.end_date, fy.year
INTO v_financial_year_start, v_financial_year_end, v_finance_year
FROM public.finance_year fy
WHERE fy.id = p_finance_id;
-- Validate financial year
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
RAISE EXCEPTION 'Invalid financial year ID: %', p_finance_id;
END IF;
-- Period-based filtering
IF p_period_type = CONST_MONTHLY THEN
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
v_finance_year AS financial_year
FROM
public.journal_entries je
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.company_id = p_company_id
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND EXTRACT(MONTH FROM je.transaction_date) = (p_period + 3) % 12 + 1
AND je.is_deleted = FALSE
AND coa.name NOT IN ('Rounding Gain')
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
ORDER BY total_expense DESC;
ELSIF p_period_type = CONST_QUARTERLY THEN
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
v_finance_year AS financial_year
FROM
public.journal_entries je
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.company_id = p_company_id
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND (
(p_period = 1 AND EXTRACT(MONTH FROM je.transaction_date) IN (4, 5, 6)) OR
(p_period = 2 AND EXTRACT(MONTH FROM je.transaction_date) IN (7, 8, 9)) OR
(p_period = 3 AND EXTRACT(MONTH FROM je.transaction_date) IN (10, 11, 12)) OR
(p_period = 4 AND EXTRACT(MONTH FROM je.transaction_date) IN (1, 2, 3))
)
AND je.is_deleted = FALSE
AND coa.name NOT IN ('Rounding Gain')
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
ORDER BY total_expense DESC;
ELSIF p_period_type = CONST_HALF_YEARLY THEN
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
v_finance_year AS financial_year
FROM
public.journal_entries je
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.company_id = p_company_id
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND (
(p_period = 1 AND EXTRACT(MONTH FROM je.transaction_date) BETWEEN 4 AND 9) OR
(p_period = 2 AND (EXTRACT(MONTH FROM je.transaction_date) BETWEEN 10 AND 12 OR EXTRACT(MONTH FROM je.transaction_date) BETWEEN 1 AND 3))
)
AND je.is_deleted = FALSE
AND coa.name NOT IN ('Rounding Gain')
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
ORDER BY total_expense DESC;
ELSIF p_period_type = CONST_YEARLY THEN
RETURN QUERY
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
v_finance_year AS financial_year
FROM
public.journal_entries je
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.company_id = p_company_id
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
AND coa.name NOT IN ('Rounding Gain')
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
ORDER BY total_expense DESC;
ELSE
RAISE EXCEPTION 'Invalid period type: %', p_period_type;
END IF;
END;
$function$
-- Function: get_five_years_expenses
CREATE OR REPLACE FUNCTION public.get_five_years_expenses(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(id uuid, financial_year_range text, account_name text, y1_apr numeric, y1_may numeric, y1_jun numeric, y1_jul numeric, y1_aug numeric, y1_sep numeric, y1_oct numeric, y1_nov numeric, y1_dec numeric, y1_jan numeric, y1_feb numeric, y1_mar numeric, y2_apr numeric, y2_may numeric, y2_jun numeric, y2_jul numeric, y2_aug numeric, y2_sep numeric, y2_oct numeric, y2_nov numeric, y2_dec numeric, y2_jan numeric, y2_feb numeric, y2_mar numeric, y3_apr numeric, y3_may numeric, y3_jun numeric, y3_jul numeric, y3_aug numeric, y3_sep numeric, y3_oct numeric, y3_nov numeric, y3_dec numeric, y3_jan numeric, y3_feb numeric, y3_mar numeric, y4_apr numeric, y4_may numeric, y4_jun numeric, y4_jul numeric, y4_aug numeric, y4_sep numeric, y4_oct numeric, y4_nov numeric, y4_dec numeric, y4_jan numeric, y4_feb numeric, y4_mar numeric, y5_apr numeric, y5_may numeric, y5_jun numeric, y5_jul numeric, y5_aug numeric, y5_sep numeric, y5_oct numeric, y5_nov numeric, y5_dec numeric, y5_jan numeric, y5_feb numeric, y5_mar numeric)
LANGUAGE plpgsql
STABLE PARALLEL SAFE
AS $function$
DECLARE
current_year_start DATE;
current_year_end DATE;
previous_year1_start DATE;
previous_year1_end DATE;
previous_year2_start DATE;
previous_year2_end DATE;
previous_year3_start DATE;
previous_year3_end DATE;
previous_year4_start DATE;
previous_year4_end DATE;
CONST_EXPENSE_TYPE_ID INTEGER := 5;
BEGIN
-- Get start/end of current year
SELECT fy.start_date, fy.end_date
INTO current_year_start, current_year_end
FROM public.finance_year fy
WHERE fy.id = p_finance_year_id;
IF current_year_start IS NULL OR current_year_end IS NULL THEN
RAISE EXCEPTION 'Financial year with ID % not found', p_finance_year_id;
END IF;
-- Previous year 1
SELECT fy.start_date, fy.end_date
INTO previous_year1_start, previous_year1_end
FROM public.finance_year fy
WHERE fy.end_date < current_year_start
ORDER BY fy.end_date DESC
LIMIT 1;
-- Previous year 2
SELECT fy.start_date, fy.end_date
INTO previous_year2_start, previous_year2_end
FROM public.finance_year fy
WHERE fy.end_date < previous_year1_start
ORDER BY fy.end_date DESC
LIMIT 1;
-- Previous year 3
SELECT fy.start_date, fy.end_date
INTO previous_year3_start, previous_year3_end
FROM public.finance_year fy
WHERE fy.end_date < previous_year2_start
ORDER BY fy.end_date DESC
LIMIT 1;
-- Previous year 4
SELECT fy.start_date, fy.end_date
INTO previous_year4_start, previous_year4_end
FROM public.finance_year fy
WHERE fy.end_date < previous_year3_start
ORDER BY fy.end_date DESC
LIMIT 1;
RETURN QUERY
WITH RECURSIVE AccountHierarchy AS (
SELECT coa.id AS account_id, coa.name, coa.parent_account_id
FROM public.chart_of_accounts coa
WHERE coa.account_type_id = CONST_EXPENSE_TYPE_ID
UNION ALL
SELECT coa.id, coa.name, coa.parent_account_id
FROM public.chart_of_accounts coa
INNER JOIN AccountHierarchy ah ON coa.parent_account_id = ah.account_id
),
MonthlyExpenses AS (
SELECT
ah.name AS account_name,
TO_CHAR(je.transaction_date, 'MM') AS transaction_month,
CASE
WHEN je.transaction_date BETWEEN previous_year4_start AND previous_year3_start - INTERVAL '1 day' THEN 'y1'
WHEN je.transaction_date BETWEEN previous_year3_start AND previous_year2_start - INTERVAL '1 day' THEN 'y2'
WHEN je.transaction_date BETWEEN previous_year2_start AND previous_year1_start - INTERVAL '1 day' THEN 'y3'
WHEN je.transaction_date BETWEEN previous_year1_start AND current_year_start - INTERVAL '1 day' THEN 'y4'
WHEN je.transaction_date BETWEEN current_year_start AND current_year_end THEN 'y5'
END AS year_group,
SUM(je.amount) AS monthly_amount
FROM AccountHierarchy ah
LEFT JOIN public.journal_entries je ON je.account_id = ah.account_id
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
WHERE th.company_id = p_company_id
AND je.transaction_date BETWEEN previous_year4_start AND current_year_end
AND je.is_deleted = FALSE
GROUP BY ah.name, year_group, TO_CHAR(je.transaction_date, 'MM')
),
AggregatedExpenses AS (
SELECT
me.account_name,
-- y1
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y1_apr,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y1_may,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y1_jun,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y1_jul,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y1_aug,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y1_sep,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y1_oct,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y1_nov,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y1_dec,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y1_jan,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y1_feb,
SUM(CASE WHEN me.year_group = 'y1' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y1_mar,
-- y2
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y2_apr,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y2_may,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y2_jun,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y2_jul,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y2_aug,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y2_sep,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y2_oct,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y2_nov,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y2_dec,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y2_jan,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y2_feb,
SUM(CASE WHEN me.year_group = 'y2' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y2_mar,
-- y3
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y3_apr,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y3_may,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y3_jun,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y3_jul,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y3_aug,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y3_sep,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y3_oct,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y3_nov,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y3_dec,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y3_jan,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y3_feb,
SUM(CASE WHEN me.year_group = 'y3' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y3_mar,
-- y4
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y4_apr,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y4_may,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y4_jun,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y4_jul,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y4_aug,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y4_sep,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y4_oct,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y4_nov,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y4_dec,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y4_jan,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y4_feb,
SUM(CASE WHEN me.year_group = 'y4' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y4_mar,
-- y5
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '04' THEN me.monthly_amount ELSE 0 END) AS y5_apr,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '05' THEN me.monthly_amount ELSE 0 END) AS y5_may,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '06' THEN me.monthly_amount ELSE 0 END) AS y5_jun,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '07' THEN me.monthly_amount ELSE 0 END) AS y5_jul,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '08' THEN me.monthly_amount ELSE 0 END) AS y5_aug,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '09' THEN me.monthly_amount ELSE 0 END) AS y5_sep,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '10' THEN me.monthly_amount ELSE 0 END) AS y5_oct,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '11' THEN me.monthly_amount ELSE 0 END) AS y5_nov,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '12' THEN me.monthly_amount ELSE 0 END) AS y5_dec,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '01' THEN me.monthly_amount ELSE 0 END) AS y5_jan,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '02' THEN me.monthly_amount ELSE 0 END) AS y5_feb,
SUM(CASE WHEN me.year_group = 'y5' AND me.transaction_month = '03' THEN me.monthly_amount ELSE 0 END) AS y5_mar
FROM MonthlyExpenses me
GROUP BY me.account_name
)
SELECT
gen_random_uuid() AS id,
CONCAT(EXTRACT(YEAR FROM previous_year4_start), '-', EXTRACT(YEAR FROM current_year_end)) AS financial_year_range,
ae.account_name,
-- y1 months
ae.y1_apr, ae.y1_may, ae.y1_jun, ae.y1_jul, ae.y1_aug, ae.y1_sep, ae.y1_oct, ae.y1_nov, ae.y1_dec, ae.y1_jan, ae.y1_feb, ae.y1_mar,
-- y2 months
ae.y2_apr, ae.y2_may, ae.y2_jun, ae.y2_jul, ae.y2_aug, ae.y2_sep, ae.y2_oct, ae.y2_nov, ae.y2_dec, ae.y2_jan, ae.y2_feb, ae.y2_mar,
-- y3 months
ae.y3_apr, ae.y3_may, ae.y3_jun, ae.y3_jul, ae.y3_aug, ae.y3_sep, ae.y3_oct, ae.y3_nov, ae.y3_dec, ae.y3_jan, ae.y3_feb, ae.y3_mar,
-- y4 months
ae.y4_apr, ae.y4_may, ae.y4_jun, ae.y4_jul, ae.y4_aug, ae.y4_sep, ae.y4_oct, ae.y4_nov, ae.y4_dec, ae.y4_jan, ae.y4_feb, ae.y4_mar,
-- y5 months
ae.y5_apr, ae.y5_may, ae.y5_jun, ae.y5_jul, ae.y5_aug, ae.y5_sep, ae.y5_oct, ae.y5_nov, ae.y5_dec, ae.y5_jan, ae.y5_feb, ae.y5_mar
FROM AggregatedExpenses ae
ORDER BY
COALESCE(ae.y1_apr,0)+COALESCE(ae.y1_may,0)+COALESCE(ae.y1_jun,0)+COALESCE(ae.y1_jul,0)+COALESCE(ae.y1_aug,0)+COALESCE(ae.y1_sep,0)+COALESCE(ae.y1_oct,0)+COALESCE(ae.y1_nov,0)+COALESCE(ae.y1_dec,0)+COALESCE(ae.y1_jan,0)+COALESCE(ae.y1_feb,0)+COALESCE(ae.y1_mar,0)
+COALESCE(ae.y2_apr,0)+COALESCE(ae.y2_may,0)+COALESCE(ae.y2_jun,0)+COALESCE(ae.y2_jul,0)+COALESCE(ae.y2_aug,0)+COALESCE(ae.y2_sep,0)+COALESCE(ae.y2_oct,0)+COALESCE(ae.y2_nov,0)+COALESCE(ae.y2_dec,0)+COALESCE(ae.y2_jan,0)+COALESCE(ae.y2_feb,0)+COALESCE(ae.y2_mar,0)
+COALESCE(ae.y3_apr,0)+COALESCE(ae.y3_may,0)+COALESCE(ae.y3_jun,0)+COALESCE(ae.y3_jul,0)+COALESCE(ae.y3_aug,0)+COALESCE(ae.y3_sep,0)+COALESCE(ae.y3_oct,0)+COALESCE(ae.y3_nov,0)+COALESCE(ae.y3_dec,0)+COALESCE(ae.y3_jan,0)+COALESCE(ae.y3_feb,0)+COALESCE(ae.y3_mar,0)
+COALESCE(ae.y4_apr,0)+COALESCE(ae.y4_may,0)+COALESCE(ae.y4_jun,0)+COALESCE(ae.y4_jul,0)+COALESCE(ae.y4_aug,0)+COALESCE(ae.y4_sep,0)+COALESCE(ae.y4_oct,0)+COALESCE(ae.y4_nov,0)+COALESCE(ae.y4_dec,0)+COALESCE(ae.y4_jan,0)+COALESCE(ae.y4_feb,0)+COALESCE(ae.y4_mar,0)
+COALESCE(ae.y5_apr,0)+COALESCE(ae.y5_may,0)+COALESCE(ae.y5_jun,0)+COALESCE(ae.y5_jul,0)+COALESCE(ae.y5_aug,0)+COALESCE(ae.y5_sep,0)+COALESCE(ae.y5_oct,0)+COALESCE(ae.y5_nov,0)+COALESCE(ae.y5_dec,0)+COALESCE(ae.y5_jan,0)+COALESCE(ae.y5_feb,0)+COALESCE(ae.y5_mar,0)
DESC
LIMIT 12;
END;
$function$
-- Function: mark_original_transaction_reversed
CREATE OR REPLACE FUNCTION public.mark_original_transaction_reversed()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
-- Only act when inserted row is a reversal
IF NEW.is_reversed = true THEN
UPDATE public.transaction_headers th
SET
is_reversed = true,
modified_on_utc = NOW()
WHERE th.company_id = NEW.company_id
AND th.document_number = NEW.document_number
AND th.id <> NEW.id
AND th.is_reversed = false;
END IF;
RETURN NEW;
END;
$function$
-- Function: get_comparative_accounts_overview
CREATE OR REPLACE FUNCTION public.get_comparative_accounts_overview(p_company_id uuid, p_fin_year_id integer)
RETURNS TABLE(account_id uuid, account_name text, parent_account_id uuid, hierarchy_path text[], order_sequence text, financial_year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_sum numeric, difference numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_current_year_id INTEGER := p_fin_year_id;
v_previous_year_id INTEGER;
v_organization_id UUID;
BEGIN
-- Get organization
SELECT c.organization_id INTO v_organization_id
FROM public.companies c
WHERE c.id = p_company_id;
-- Find previous financial year (based on start_date)
SELECT fy.id
INTO v_previous_year_id
FROM finance_year fy
WHERE fy.start_date < (
SELECT start_date FROM finance_year WHERE id = v_current_year_id
)
ORDER BY fy.start_date DESC
LIMIT 1;
RETURN QUERY
WITH RECURSIVE account_with_path AS (
-- Root accounts
SELECT
coa.id,
coa.name,
coa.parent_account_id,
ARRAY[coa.name]::text[] AS path,
coa.account_number::text AS order_sequence
FROM chart_of_accounts coa
WHERE coa.organization_id = v_organization_id
AND (coa.parent_account_id IS NULL OR coa.parent_account_id = '00000000-0000-0000-0000-000000000000')
AND coa.is_deleted = FALSE
UNION ALL
-- Children
SELECT
c.id,
c.name,
c.parent_account_id,
awp.path || c.name,
awp.order_sequence || '.' || c.account_number::text
FROM chart_of_accounts c
JOIN account_with_path awp ON c.parent_account_id = awp.id
WHERE c.organization_id = v_organization_id
AND c.is_deleted = FALSE
),
leaf_accounts AS (
SELECT a.id
FROM account_with_path a
WHERE NOT EXISTS (
SELECT 1 FROM chart_of_accounts c
WHERE c.parent_account_id = a.id
AND c.organization_id = v_organization_id
AND c.is_deleted = FALSE
)
),
financial_years AS (
SELECT id AS fin_year_id, start_date, end_date
FROM finance_year
WHERE id IN (v_current_year_id, v_previous_year_id)
),
aggregated_data AS (
SELECT
awp.id,
awp.name,
awp.parent_account_id,
awp.path AS hierarchy_path,
awp.order_sequence,
fy.fin_year_id,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN je.amount END), 0) AS apr,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN je.amount END), 0) AS may,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN je.amount END), 0) AS jun,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN je.amount END), 0) AS jul,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN je.amount END), 0) AS aug,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN je.amount END), 0) AS sep,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN je.amount END), 0) AS oct,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN je.amount END), 0) AS nov,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN je.amount END), 0) AS "dec",
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN je.amount END), 0) AS jan,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN je.amount END), 0) AS feb,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN je.amount END), 0) AS mar
FROM account_with_path awp
JOIN leaf_accounts la ON la.id = awp.id -- ✅ only leaf accounts get financial years
CROSS JOIN financial_years fy
LEFT JOIN journal_entries je
ON je.account_id = awp.id
AND je.transaction_date BETWEEN fy.start_date AND fy.end_date
LEFT JOIN transaction_headers th
ON th.id = je.transaction_id
AND th.company_id = p_company_id
WHERE (th.transaction_source_type IS NULL OR th.transaction_source_type != 18)
GROUP BY awp.id, awp.name, awp.parent_account_id, awp.path, awp.order_sequence, fy.fin_year_id
)
SELECT
a1.id AS account_id,
a1.name AS account_name,
a1.parent_account_id,
a1.hierarchy_path,
a1.order_sequence,
a1.fin_year_id AS financial_year,
a1.apr, a1.may, a1.jun, a1.jul, a1.aug, a1.sep, a1.oct, a1.nov, a1."dec",
a1.jan, a1.feb, a1.mar,
(COALESCE(a1.apr,0) + COALESCE(a1.may,0) + COALESCE(a1.jun,0) +
COALESCE(a1.jul,0) + COALESCE(a1.aug,0) + COALESCE(a1.sep,0) +
COALESCE(a1.oct,0) + COALESCE(a1.nov,0) + COALESCE(a1."dec",0) +
COALESCE(a1.jan,0) + COALESCE(a1.feb,0) + COALESCE(a1.mar,0)) AS total_sum,
((COALESCE(a1.apr,0) + COALESCE(a1.may,0) + COALESCE(a1.jun,0) +
COALESCE(a1.jul,0) + COALESCE(a1.aug,0) + COALESCE(a1.sep,0) +
COALESCE(a1.oct,0) + COALESCE(a1.nov,0) + COALESCE(a1."dec",0) +
COALESCE(a1.jan,0) + COALESCE(a1.feb,0) + COALESCE(a1.mar,0))
-
(COALESCE(a2.apr,0) + COALESCE(a2.may,0) + COALESCE(a2.jun,0) +
COALESCE(a2.jul,0) + COALESCE(a2.aug,0) + COALESCE(a2.sep,0) +
COALESCE(a2.oct,0) + COALESCE(a2.nov,0) + COALESCE(a2."dec",0) +
COALESCE(a2.jan,0) + COALESCE(a2.feb,0) + COALESCE(a2.mar,0))
) AS difference
FROM aggregated_data a1
LEFT JOIN aggregated_data a2
ON a1.id = a2.id
AND a1.fin_year_id = v_current_year_id
AND a2.fin_year_id = v_previous_year_id
ORDER BY a1.order_sequence, a1.fin_year_id;
END;
$function$
-- Function: approve_visitor_for_unit
CREATE OR REPLACE FUNCTION public.approve_visitor_for_unit(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid, p_resident_id integer)
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, final_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
local_total_visits INT;
local_approved_count INT;
local_rejected_count INT;
local_new_visitor_status INT;
BEGIN
-- Step 1: Approve for this unit only
UPDATE public.multi_unit_visits
SET visitor_statuses = 2, -- Approved
modified_on_utc = NOW(),
modified_by = p_resident_user
WHERE visitor_log_id = p_visitor_log_id
AND unit_id = p_unit_id
AND is_deleted = FALSE;
IF NOT FOUND THEN
-- No update happened (invalid visitor_log_id or unit_id)
RETURN;
END IF;
-- Step 2: Recalculate parent visitor log status
SELECT
COUNT(*),
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2), -- Approved
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6) -- Rejected
INTO
local_total_visits,
local_approved_count,
local_rejected_count
FROM public.multi_unit_visits muv
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.is_deleted = FALSE;
-- Step 3: Decide new parent status
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
local_new_visitor_status := 2; -- All Approved
ELSIF local_approved_count > 0 THEN
local_new_visitor_status := 7; -- Partial Approved
ELSIF local_rejected_count > 0 THEN
local_new_visitor_status := 6; -- Rejected
ELSE
local_new_visitor_status := 1; -- Pending
END IF;
UPDATE public.visitor_logs
SET visitor_status_id = local_new_visitor_status,
modified_on_utc = NOW(),
modified_by = p_resident_user
WHERE id = p_visitor_log_id;
-- Step 4: Return minimal information for the service layer
RETURN QUERY
SELECT
p_visitor_log_id,
p_unit_id,
p_resident_id, -- approver resident
local_new_visitor_status;
END;
$function$
-- Function: get_trial_balance_of_company_fy
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_trial_balance_of_company_fy(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(account_id uuid, account_type text, account_category text, account_number text, account_name text, total_debits numeric, total_credits numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH RECURSIVE coa_hierarchy AS (
SELECT
coa.id,
act.name AS account_type,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
0 AS level,
coa.account_number::text AS order_sequence
FROM public.chart_of_accounts coa
INNER JOIN public.account_types act
ON coa.account_type_id = act.id
WHERE
coa.is_deleted = FALSE
AND coa.parent_account_id = '00000000-0000-0000-0000-000000000000'
UNION ALL
SELECT
coa.id,
act.name AS account_type,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
ch.level + 1,
ch.order_sequence || '.' || coa.account_number::text
FROM public.chart_of_accounts coa
INNER JOIN public.account_types act
ON coa.account_type_id = act.id
INNER JOIN coa_hierarchy ch
ON ch.id = coa.parent_account_id
WHERE coa.is_deleted = FALSE
),
coa_with_balances AS (
SELECT
fy.start_date::DATE,
fy.end_date::DATE,
fy.year
FROM public.finance_year fy
WHERE fy.id = p_finance_year_id
LIMIT 1
),
journal_entries_filtered AS (
SELECT
je.account_id,
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) AS total_debits,
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) AS total_credits
FROM public.journal_entries je
JOIN public.transaction_headers tr
ON je.transaction_id = tr.id
JOIN coa_with_balances fy
ON tr.transaction_date BETWEEN fy.start_date AND fy.end_date
WHERE
tr.company_id = p_company_id
AND je.is_deleted = FALSE
AND tr.is_deleted = FALSE
-- 🔥 DOCUMENT-LEVEL REVERSAL SAFE LOGIC
AND tr.is_reversed = FALSE
AND NOT EXISTS (
SELECT 1
FROM public.transaction_headers tr_rev
WHERE tr_rev.company_id = tr.company_id
AND tr_rev.document_number = tr.document_number
AND tr_rev.is_reversed = TRUE
)
GROUP BY je.account_id
HAVING
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) > 0 OR
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) > 0
),
relevant_accounts AS (
SELECT
ch.id,
ch.account_type,
ch.account_number,
ch.name,
ch.level,
ch.order_sequence,
ch.parent_account_id,
wb.total_debits,
wb.total_credits
FROM coa_hierarchy ch
LEFT JOIN journal_entries_filtered wb
ON ch.id = wb.account_id
WHERE wb.account_id IS NOT NULL
UNION ALL
SELECT
ch.id,
ch.account_type,
ch.account_number,
ch.name,
ch.level,
ch.order_sequence,
ch.parent_account_id,
NULL,
NULL
FROM coa_hierarchy ch
INNER JOIN relevant_accounts ra
ON ch.id = ra.parent_account_id
)
SELECT DISTINCT ON (ra.order_sequence)
ra.id::uuid AS account_id,
ra.account_type::TEXT AS account_type,
ac.name::TEXT AS account_category,
ra.account_number::TEXT AS account_number,
LPAD('', ra.level * 4, ' ') || ra.name::TEXT AS account_name,
COALESCE(ra.total_debits, 0) AS total_debits,
COALESCE(ra.total_credits, 0) AS total_credits
FROM relevant_accounts ra
LEFT JOIN public.chart_of_accounts coa
ON ra.id = coa.id
LEFT JOIN public.account_types at
ON coa.account_type_id = at.id
LEFT JOIN public.account_categories ac
ON at.account_category_id = ac.id
CROSS JOIN coa_with_balances fy
ORDER BY ra.order_sequence;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_trial_balance_of_company_fy(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(account_id uuid, account_type text, account_category text, account_number text, account_name text, total_debits numeric, total_credits numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH RECURSIVE coa_hierarchy AS (
-- Base case: Fetch the top-level (root) accounts with no parent (e.g., Assets, Liabilities)
SELECT
coa.id,
act.name AS account_type,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
0 AS level,
coa.account_number::text AS order_sequence
FROM
public.chart_of_accounts coa
INNER JOIN
public.account_types act ON coa.account_type_id = act.id
WHERE
coa.is_deleted = FALSE
AND coa.parent_account_id = '00000000-0000-0000-0000-000000000000' -- Root-level accounts
UNION ALL
-- Recursive case: Fetch all child accounts for each parent
SELECT
coa.id,
act.name AS account_type,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
ch.level + 1 AS level,
ch.order_sequence || '.' || coa.account_number::text AS order_sequence
FROM
public.chart_of_accounts coa
INNER JOIN
public.account_types act ON coa.account_type_id = act.id
INNER JOIN
coa_hierarchy ch ON ch.id = coa.parent_account_id
WHERE
coa.is_deleted = FALSE
),
coa_with_balances AS (
-- Fetch the current financial year start and end date from the finance_year table
SELECT
fy.start_date::DATE, -- Cast start_date to DATE
fy.end_date::DATE, -- Cast end_date to DATE
fy.year
FROM
public.finance_year fy
WHERE
fy.id = p_finance_year_id
LIMIT 1
),
journal_entries_filtered AS (
-- Fetch accounts that have non-zero balances within the financial year range
SELECT
je.account_id,
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) AS total_debits,
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) AS total_credits
FROM
public.journal_entries je
JOIN
public.transaction_headers th ON je.transaction_id = th.id
JOIN
coa_with_balances fy ON th.transaction_date BETWEEN fy.start_date AND fy.end_date -- Filter by financial year
WHERE
th.company_id = p_company_id
AND je.is_deleted = FALSE
GROUP BY
je.account_id
HAVING
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) > 0 OR
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) > 0
),
relevant_accounts AS (
-- Select accounts that have balances directly
SELECT
ch.id,
ch.account_type,
ch.account_number,
ch.name,
ch.level,
ch.order_sequence,
ch.parent_account_id,
wb.total_debits,
wb.total_credits
FROM
coa_hierarchy ch
LEFT JOIN
journal_entries_filtered wb ON ch.id = wb.account_id
WHERE
wb.account_id IS NOT NULL -- Only accounts with actual balances
UNION ALL
-- Recursively select parent accounts, without recalculating balances
SELECT
ch.id,
ch.account_type,
ch.account_number,
ch.name,
ch.level,
ch.order_sequence,
ch.parent_account_id,
NULL AS total_debits,
NULL AS total_credits
FROM
coa_hierarchy ch
INNER JOIN
relevant_accounts ra ON ch.id = ra.parent_account_id -- Propagate balance up to parent accounts
)
-- Final output
SELECT DISTINCT ON (ra.order_sequence)
ra.id::uuid AS account_id, -- Chart of Accounts ID
ra.account_type::TEXT AS account_type, -- Account type
ac.name::TEXT AS account_category, -- Account category
ra.account_number::TEXT AS account_number, -- Account number
LPAD('', ra.level * 4, ' ') || ra.name::TEXT AS account_name, -- Indented account name
COALESCE(ra.total_debits, 0) AS total_debits, -- Sum of debits
COALESCE(ra.total_credits, 0) AS total_credits -- Sum of credits
-- Cast end date of the financial year to DATE
FROM
relevant_accounts ra
LEFT JOIN
public.chart_of_accounts coa ON ra.id = coa.id -- Join chart of accounts
LEFT JOIN
public.account_types at ON coa.account_type_id = at.id -- Join account types
LEFT JOIN
public.account_categories ac ON at.account_category_id = ac.id -- Join account categories
CROSS JOIN
coa_with_balances fy -- Include the financial year details
ORDER BY
ra.order_sequence; -- Maintain hierarchy in the output
END;
$function$
-- Function: pgp_pub_encrypt_bytea
CREATE OR REPLACE FUNCTION public.pgp_pub_encrypt_bytea(bytea, bytea, text)
RETURNS bytea
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_encrypt_bytea$function$
-- Function: pgp_pub_decrypt
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt(bytea, bytea)
RETURNS text
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_text$function$
-- Function: pgp_pub_decrypt_bytea
CREATE OR REPLACE FUNCTION public.pgp_pub_decrypt_bytea(bytea, bytea)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_pub_decrypt_bytea$function$
-- Function: get_balance_sheet
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_balance_sheet(p_company_id uuid, p_finance_year_id integer, p_as_on_date date, p_is_get_all_for_organizations boolean)
RETURNS TABLE(account_id uuid, account_type text, account_name text, amount numeric, category text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date DATE;
v_end_date DATE;
v_company_ids UUID[];
v_organization_id UUID;
asset_account_ids integer[];
liability_account_ids integer[];
equity_account_ids integer[];
BEGIN
-- Fetch financial year start and end dates
SELECT fy.start_date, fy.end_date
INTO v_start_date, v_end_date
FROM public.finance_year fy
WHERE fy.id = p_finance_year_id;
-- Cap the end date by the given as_on_date
v_end_date := LEAST(v_end_date, p_as_on_date);
-- Get organization id of company
SELECT c.organization_id INTO v_organization_id
FROM public.companies c
WHERE c.id = p_company_id;
-- Get account type hierarchies
asset_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(1)); -- Assets
liability_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(2)); -- Liabilities
equity_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(3)); -- Equity
-- Company IDs based on flag
IF p_is_get_all_for_organizations THEN
SELECT ARRAY_AGG(id)
INTO v_company_ids
FROM public.companies
WHERE organization_id = v_organization_id;
ELSE
v_company_ids := ARRAY[p_company_id];
END IF;
-- Assets
RETURN QUERY
SELECT
coa.id,
at.name,
coa.name,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'D' THEN je.amount
WHEN je.entry_type = 'C' THEN -je.amount
ELSE 0
END
),0) AS amount,
'Assets'
FROM public.chart_of_accounts coa
JOIN public.account_types at ON coa.account_type_id = at.id
JOIN public.journal_entries je ON je.account_id = coa.id
JOIN public.transaction_headers th ON th.id = je.transaction_id
WHERE th.company_id = ANY(v_company_ids)
AND th.transaction_date BETWEEN v_start_date AND v_end_date
AND NOT th.is_deleted
AND NOT je.is_deleted
AND coa.account_type_id = ANY(asset_account_ids)
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
ORDER BY coa.account_number, at.id;
-- Liabilities
RETURN QUERY
SELECT
coa.id,
at.name,
coa.name,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C' THEN je.amount
WHEN je.entry_type = 'D' THEN -je.amount
ELSE 0
END
),0) AS amount,
'Liabilities'
FROM public.chart_of_accounts coa
JOIN public.account_types at ON coa.account_type_id = at.id
JOIN public.journal_entries je ON je.account_id = coa.id
JOIN public.transaction_headers th ON th.id = je.transaction_id
WHERE th.company_id = ANY(v_company_ids)
AND th.transaction_date BETWEEN v_start_date AND v_end_date
AND NOT th.is_deleted
AND NOT je.is_deleted
AND coa.account_type_id = ANY(liability_account_ids)
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
ORDER BY coa.account_number, at.id;
-- Equity
RETURN QUERY
SELECT
coa.id,
at.name,
coa.name,
COALESCE(SUM(
CASE
WHEN je.entry_type = 'C' THEN je.amount
WHEN je.entry_type = 'D' THEN -je.amount
ELSE 0
END
),0) AS amount,
'Equity'
FROM public.chart_of_accounts coa
JOIN public.account_types at ON coa.account_type_id = at.id
JOIN public.journal_entries je ON je.account_id = coa.id
JOIN public.transaction_headers th ON th.id = je.transaction_id
WHERE th.company_id = ANY(v_company_ids)
AND th.transaction_date BETWEEN v_start_date AND v_end_date
AND NOT th.is_deleted
AND NOT je.is_deleted
AND coa.account_type_id = ANY(equity_account_ids)
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
ORDER BY coa.account_number, at.id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_balance_sheet(p_company_id uuid, p_finance_year_id integer, p_as_on_date date, p_is_get_all_for_organizations boolean)
RETURNS TABLE(account_type text, account_name text, amount numeric, category text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date DATE;
v_end_date DATE;
v_company_ids UUID[];
v_organization_id UUID;
asset_account_ids integer[];
liability_account_ids integer[];
equity_account_ids integer[];
BEGIN
-- Fetch financial year start and end dates
SELECT fy.start_date, fy.end_date
INTO v_start_date, v_end_date
FROM public.finance_year fy
WHERE fy.id = p_finance_year_id;
-- Cap the end date by the given as_on_date
v_end_date := LEAST(v_end_date, p_as_on_date);
-- Get organization id of company
SELECT c.organization_id INTO v_organization_id
FROM public.companies c
WHERE c.id = p_company_id;
-- Get account type hierarchies for assets, liabilities, and equity
asset_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(1)); -- Assets
liability_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(2)); -- Liabilities
equity_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(3)); -- Equity
-- Determine company IDs based on flag
IF p_is_get_all_for_organizations THEN
SELECT ARRAY_AGG(id)
INTO v_company_ids
FROM public.companies
WHERE organization_id = v_organization_id;
ELSE
v_company_ids := ARRAY[p_company_id];
END IF;
-- Return Assets
RETURN QUERY
SELECT
at.name AS account_type,
coa.name AS account_name,
COALESCE(
SUM(
CASE
WHEN ob.entry_type = 'D' THEN ob.balance
ELSE -ob.balance
END
), 0) +
COALESCE(
SUM(CASE
WHEN je.entry_type = 'D' THEN je.amount -- Debit increases asset balance
WHEN je.entry_type = 'C' THEN -je.amount -- Credit decreases asset balance
ELSE 0
END), 0
) AS amount,
'Assets' AS category
FROM public.chart_of_accounts coa
LEFT JOIN public.journal_entries je ON je.account_id = coa.id
LEFT JOIN public.transaction_headers th ON je.transaction_id = th.id
LEFT JOIN public.opening_balances ob ON ob.account_id = coa.id
AND ob.organization_id = v_organization_id
AND ob.finyear_id = p_finance_year_id
AND ob.is_deleted = FALSE
INNER JOIN public.account_types at ON coa.account_type_id = at.id
WHERE th.company_id = ANY(v_company_ids)
AND th.transaction_date BETWEEN v_start_date AND v_end_date
AND NOT th.is_deleted
AND NOT je.is_deleted
AND coa.account_type_id = ANY(asset_account_ids)
GROUP BY at.name, coa.name, coa.account_number, at.id
ORDER BY coa.account_number, at.id;
-- Return Liabilities
RETURN QUERY
SELECT
at.name AS account_type,
coa.name AS account_name,
COALESCE(
SUM(
CASE
WHEN ob.entry_type = 'C' THEN ob.balance
ELSE -ob.balance
END
), 0) +
COALESCE(
SUM(CASE
WHEN je.entry_type = 'C' THEN je.amount
WHEN je.entry_type = 'D' THEN -je.amount
ELSE 0
END), 0
) AS amount,
'Liabilities' AS category
FROM public.chart_of_accounts coa
LEFT JOIN public.journal_entries je ON je.account_id = coa.id
LEFT JOIN public.transaction_headers th ON je.transaction_id = th.id
LEFT JOIN public.opening_balances ob ON ob.account_id = coa.id
AND ob.organization_id = v_organization_id
AND ob.finyear_id = p_finance_year_id
AND ob.is_deleted = FALSE
INNER JOIN public.account_types at ON coa.account_type_id = at.id
WHERE th.company_id = ANY(v_company_ids)
AND th.transaction_date BETWEEN v_start_date AND v_end_date
AND NOT th.is_deleted
AND NOT je.is_deleted
AND coa.account_type_id = ANY(liability_account_ids)
GROUP BY at.name, coa.name, coa.account_number, at.id
ORDER BY coa.account_number, at.id;
-- Return Equity
RETURN QUERY
SELECT
at.name AS account_type,
coa.name AS account_name,
COALESCE(
SUM(
CASE
WHEN ob.entry_type = 'C' THEN ob.balance
ELSE -ob.balance
END
), 0) +
COALESCE(
SUM(CASE
WHEN je.entry_type = 'C' THEN je.amount
WHEN je.entry_type = 'D' THEN -je.amount
ELSE 0
END), 0
) AS amount,
'Equity' AS category
FROM public.chart_of_accounts coa
LEFT JOIN public.journal_entries je ON je.account_id = coa.id
LEFT JOIN public.transaction_headers th ON je.transaction_id = th.id
LEFT JOIN public.opening_balances ob ON ob.account_id = coa.id
AND ob.organization_id = v_organization_id
AND ob.finyear_id = p_finance_year_id
AND ob.is_deleted = FALSE
INNER JOIN public.account_types at ON coa.account_type_id = at.id
WHERE th.company_id = ANY(v_company_ids)
AND th.transaction_date BETWEEN v_start_date AND v_end_date
AND NOT th.is_deleted
AND NOT je.is_deleted
AND coa.account_type_id = ANY(equity_account_ids)
GROUP BY at.name, coa.name, coa.account_number, at.id
ORDER BY coa.account_number, at.id;
END;
$function$
-- Function: grant_full_schema_access
CREATE OR REPLACE FUNCTION public.grant_full_schema_access(p_schema text, p_user text)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
obj RECORD;
BEGIN
-- Grant on tables
FOR obj IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = p_schema
AND table_type = 'BASE TABLE'
LOOP
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.%I TO %I;', p_schema, obj.table_name, p_user);
RAISE NOTICE 'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %.% TO %;', p_schema, obj.table_name, p_user;
END LOOP;
-- Grant on sequences (USAGE + SELECT + UPDATE: full coverage)
FOR obj IN
SELECT c.relname AS sequence_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'S'
AND n.nspname = p_schema
LOOP
EXECUTE format('GRANT USAGE, SELECT, UPDATE ON SEQUENCE %I.%I TO %I;', p_schema, obj.sequence_name, p_user);
RAISE NOTICE 'GRANT USAGE, SELECT, UPDATE ON SEQUENCE %.% TO %;', p_schema, obj.sequence_name, p_user;
END LOOP;
-- Grant on all functions (handles all argument types)
FOR obj IN
SELECT
p.proname AS function_name,
pg_get_function_identity_arguments(p.oid) AS args
FROM
pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE
n.nspname = p_schema
AND p.prokind = 'f' -- f = function
LOOP
EXECUTE format(
'GRANT EXECUTE ON FUNCTION %I.%I(%s) TO %I;',
p_schema, obj.function_name, obj.args, p_user
);
RAISE NOTICE 'GRANT EXECUTE ON FUNCTION %.%(%) TO %;', p_schema, obj.function_name, obj.args, p_user;
END LOOP;
-- Grant on all procedures (Postgres 11+)
FOR obj IN
SELECT
p.proname AS procedure_name,
pg_get_function_identity_arguments(p.oid) AS args
FROM
pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE
n.nspname = p_schema
AND p.prokind = 'p' -- p = procedure
LOOP
EXECUTE format(
'GRANT EXECUTE ON PROCEDURE %I.%I(%s) TO %I;',
p_schema, obj.procedure_name, obj.args, p_user
);
RAISE NOTICE 'GRANT EXECUTE ON PROCEDURE %.%(%) TO %;', p_schema, obj.procedure_name, obj.args, p_user;
END LOOP;
END;
$function$
-- Function: dummy_hello_function
CREATE OR REPLACE FUNCTION public.dummy_hello_function(name text)
RETURNS text
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN 'Hello, ' || name || '!';
END;
$function$
-- Function: get_all_users
CREATE OR REPLACE FUNCTION public.get_all_users()
RETURNS TABLE(id uuid, first_name character varying, last_name character varying, email character varying, phone_number character varying, user_name text)
LANGUAGE sql
AS $function$
SELECT id, first_name, last_name, email, phone_number, user_name
FROM public.users;
$function$
-- Function: get_vendor_opening_balance
CREATE OR REPLACE FUNCTION public.get_vendor_opening_balance(p_vendor_id uuid, p_finyear_id integer)
RETURNS TABLE(transaction_date timestamp without time zone, vendor_id uuid, vendor_name text, description text, credit numeric, debit numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_year_text text;
BEGIN
-- Fetch the year text for the given finance year id
SELECT year INTO v_year_text FROM public.finance_year WHERE id = p_finyear_id;
IF v_year_text IS NULL THEN
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
END IF;
RETURN QUERY
SELECT
th.transaction_date,
th.vendor_id,
v.name::text AS vendor_name, -- Explicit cast to match function signature
th.description,
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) AS credit,
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) AS debit
FROM public.transaction_headers th
JOIN public.vendors v ON th.vendor_id = v.id
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.chart_of_accounts sc ON sc.name ILIKE '%Sundry Creditors%'
WHERE th.vendor_id = p_vendor_id
AND je.account_id = sc.id
AND th.transaction_source_type = 13
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
ORDER BY th.transaction_date;
END;
$function$
-- Function: get_customer_ledger
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_customer_ledger(p_company_id uuid, p_organization_id uuid, p_customer_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_id uuid, account_name text, debit numeric, credit numeric, balance numeric, document_number text, document_id uuid, transaction_source_type integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_opening_equity_id UUID;
v_sundary_detors_account_id UUID;
v_opening_balance_equity_account_id UUID;
BEGIN
SELECT accounts_receivable_account_id, opening_balance_equity_account_id
INTO v_sundary_detors_account_id, v_opening_balance_equity_account_id
FROM public.organization_accounts
WHERE organization_id = p_organization_id
LIMIT 1;
RETURN QUERY
WITH all_transactions AS (
SELECT
th.transaction_date::date,
coa.name AS account_name,
coa.id AS account_id, -- ✅ Include account_id
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
th.document_number,
th.document_id,
th.transaction_source_type,
tst.sort_order,
th.id AS transaction_id,
je.id AS journal_entry_id
FROM
public.transaction_headers th
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.transaction_source_types tst ON th.transaction_source_type = tst.id
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.company_id = p_company_id
AND th.customer_id = p_customer_id
AND je.account_id = v_sundary_detors_account_id
AND th.transaction_date BETWEEN p_start_date AND p_end_date
AND th.is_deleted = false
AND je.is_deleted = false
)
SELECT
at.transaction_date,
at.account_id, -- ✅ Added here
at.account_name,
at.debit,
at.credit,
SUM(at.debit - at.credit) OVER (
ORDER BY
at.transaction_date,
at.sort_order,
at.transaction_id,
at.journal_entry_id
) AS balance,
at.document_number,
at.document_id,
at.transaction_source_type
FROM all_transactions AS at
ORDER BY
at.transaction_date,
at.sort_order,
at.transaction_id,
at.journal_entry_id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_customer_ledger(p_company_id uuid, p_organization_id uuid, p_customer_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, document_number text, document_id uuid, transaction_source_type integer, sort_order integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_opening_balance NUMERIC;
BEGIN
SELECT COALESCE(
SUM(
CASE
WHEN ob.entry_type = 'D' THEN ob.balance -- Debit increases balance
ELSE -ob.balance -- Credit reduces balance
END
), 0)
INTO v_opening_balance
FROM public.opening_balances ob
WHERE ob.organization_id = p_organization_id
AND ob.customer_id = p_customer_id
AND ob.is_deleted = FALSE;
RETURN QUERY
WITH customer_transactions AS (
SELECT
th.transaction_date::date AS transaction_date,
coa.name AS account_name,
CASE
WHEN je.entry_type = 'D' THEN je.amount
ELSE 0
END AS debit,
CASE
WHEN je.entry_type = 'C' THEN je.amount
ELSE 0
END AS credit,
th.company_id,
th.customer_id,
je.account_id,
th.document_number,
th.document_id,
th.transaction_source_type,
2 AS sort_order
FROM
public.transaction_headers th
INNER JOIN
public.journal_entries je ON th.id = je.transaction_id
INNER JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.company_id = p_company_id
AND coa.organization_id = p_organization_id
AND th.customer_id = p_customer_id
AND th.is_deleted = false
AND je.is_deleted = false
AND th.transaction_date >= p_start_date
AND th.transaction_date <= p_end_date
)
SELECT
p_start_date AS transaction_date,
'Opening Balance' AS account_name,
0 AS debit,
0 AS credit,
v_opening_balance AS balance,
NULL AS document_number,
NULL AS document_id,
NULL AS transaction_source_type,
1 AS sort_order
UNION ALL
SELECT
ct.transaction_date,
ct.account_name,
ct.debit,
ct.credit,
v_opening_balance
+ SUM(ct.debit - ct.credit)
OVER (ORDER BY ct.transaction_date, ct.document_number ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS balance,
ct.document_number,
ct.document_id,
ct.transaction_source_type,
ct.sort_order
FROM
customer_transactions ct
WHERE
NOT (ct.debit = 0 AND ct.credit = 0)
ORDER BY
transaction_date,
sort_order ASC; -- Then order by transaction date
END;
$function$
-- Function: get_general_ledger
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_general_ledger(p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid, customer_id uuid, customer_name text, employee_id uuid, employee_name text, vendor_id uuid, vendor_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH general_transactions AS (
SELECT
th.transaction_date::date AS transaction_date,
coa.name AS account_name,
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
th.company_id,
je.account_id,
th.transaction_source_type,
tst.sort_order,
th.document_number,
th.document_id,
th.customer_id,
th.employee_id,
th.vendor_id,
th.id AS transaction_id,
je.id AS journal_entry_id
FROM
public.transaction_headers th
INNER JOIN
public.journal_entries je ON th.id = je.transaction_id
INNER JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
INNER JOIN
public.transaction_source_types tst ON th.transaction_source_type = tst.id
WHERE
th.company_id = p_company_id
AND coa.organization_id = p_organization_id
AND th.is_deleted = false
AND je.is_deleted = false
AND th.transaction_date BETWEEN p_start_date AND p_end_date
)
SELECT
gt.transaction_date,
gt.account_name,
gt.debit,
gt.credit,
SUM(gt.debit - gt.credit) OVER (
ORDER BY
gt.transaction_date,
gt.sort_order,
gt.transaction_id,
gt.journal_entry_id
) AS balance,
gt.transaction_source_type,
gt.document_number,
gt.document_id,
c.id AS customer_id,
c.name::text AS customer_name,
e.id AS employee_id,
CASE
WHEN e.id IS NOT NULL THEN (e.first_name || ' ' || e.last_name)::text
ELSE NULL
END AS employee_name,
v.id AS vendor_id,
v.name::text AS vendor_name
FROM
general_transactions gt
LEFT JOIN public.customers c ON gt.customer_id = c.id AND c.company_id = p_company_id
LEFT JOIN public.employees e ON gt.employee_id = e.id AND e.company_id = p_company_id
LEFT JOIN public.vendors v ON gt.vendor_id = v.id AND v.company_id = p_company_id
WHERE
NOT (gt.debit = 0 AND gt.credit = 0)
ORDER BY
gt.transaction_date,
gt.sort_order,
gt.transaction_id,
gt.journal_entry_id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_general_ledger(p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH general_transactions AS (
SELECT
th.transaction_date::date AS transaction_date, -- Cast to date
coa.name AS account_name,
CASE
WHEN je.entry_type = 'D' THEN je.amount
ELSE 0
END AS debit,
CASE
WHEN je.entry_type = 'C' THEN je.amount
ELSE 0
END AS credit,
th.company_id,
je.account_id,
th.transaction_source_type,
th.document_number,
th.document_id
FROM
public.transaction_headers th
INNER JOIN
public.journal_entries je ON th.id = je.transaction_id
INNER JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.company_id = p_company_id
AND coa.organization_id = p_organization_id
AND th.is_deleted = false
AND je.is_deleted = false
AND th.transaction_date >= p_start_date
AND th.transaction_date <= p_end_date
)
SELECT
gt.transaction_date,
gt.account_name,
gt.debit,
gt.credit,
-- Recalculate balance where debit and credit are settled
SUM(gt.debit - gt.credit) OVER (PARTITION BY gt.account_id ORDER BY gt.transaction_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance,
gt.transaction_source_type,
gt.document_number,
gt.document_id
FROM
general_transactions gt
WHERE
-- Exclude rows where both debit and credit are 0
NOT (gt.debit = 0 AND gt.credit = 0)
ORDER BY
gt.transaction_date;
END;
$function$
-- Function: get_vendor_ledger
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_vendor_ledger(p_vendor_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_id uuid, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
LANGUAGE plpgsql
AS $function$
DECLARE
v_sundary_creditors_account_id UUID;
v_vendor_advance_account_id UUID;
BEGIN
SELECT
accounts_payable_account_id,
vendor_advance_account_id
INTO
v_sundary_creditors_account_id,
v_vendor_advance_account_id
FROM public.organization_accounts
WHERE organization_id = p_organization_id
LIMIT 1;
RETURN QUERY
WITH all_transactions AS (
SELECT
th.transaction_date::date,
coa.id AS account_id,
coa.name AS account_name,
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
th.document_number,
th.document_id,
th.transaction_source_type,
tst.sort_order,
th.id AS transaction_id,
je.id AS journal_entry_id
FROM public.transaction_headers th
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.transaction_source_types tst ON th.transaction_source_type = tst.id
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.company_id = p_company_id
AND th.vendor_id = p_vendor_id
AND je.account_id IN (v_sundary_creditors_account_id, v_vendor_advance_account_id)
AND th.transaction_date BETWEEN p_start_date AND p_end_date
AND th.is_deleted = false
AND je.is_deleted = false
)
SELECT
at.transaction_date,
at.account_id,
at.account_name,
at.debit,
at.credit,
SUM(at.debit - at.credit) OVER (
ORDER BY
at.transaction_date,
at.sort_order,
at.transaction_id,
at.journal_entry_id
) AS balance,
at.transaction_source_type,
at.document_number,
at.document_id
FROM all_transactions AS at
ORDER BY
at.transaction_date,
at.sort_order,
at.transaction_id,
at.journal_entry_id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_vendor_ledger(p_vendor_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH vendor_transactions AS (
SELECT
th.transaction_date::date AS transaction_date,
coa.name AS account_name,
CASE
WHEN je.entry_type = 'D' THEN je.amount
ELSE 0
END AS debit,
CASE
WHEN je.entry_type = 'C' THEN je.amount
ELSE 0
END AS credit,
th.company_id,
th.vendor_id,
th.transaction_source_type,
th.document_number,
th.document_id
FROM
public.transaction_headers th
INNER JOIN
public.journal_entries je ON th.id = je.transaction_id
INNER JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.vendor_id = p_vendor_id
AND th.company_id = p_company_id
AND coa.organization_id = p_organization_id
AND th.is_deleted = false
AND je.is_deleted = false
AND th.transaction_date >= p_start_date
AND th.transaction_date <= p_end_date
)
SELECT
vt.transaction_date,
vt.account_name,
vt.debit,
vt.credit,
SUM(vt.debit - vt.credit) OVER (PARTITION BY vt.vendor_id ORDER BY vt.transaction_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance,
vt.transaction_source_type,
vt.document_number,
vt.document_id
FROM
vendor_transactions vt
ORDER BY
vt.transaction_date;
END;
$function$
-- Function: get_party_totals
CREATE OR REPLACE FUNCTION public.get_party_totals(p_organization_id uuid, p_company_id uuid, p_start_date date, p_end_date date, p_party_type integer)
RETURNS TABLE(id uuid, party_name text, company_id uuid, company_name text, total_invoiced numeric, total_received numeric, total_billed numeric, total_paid numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
CUSTOMER CONSTANT int := 1;
VENDOR CONSTANT int := 2;
BEGIN
-- Customers
IF p_party_type = CUSTOMER THEN
RETURN QUERY
SELECT
th.customer_id AS id,
MAX(cust.name) AS party_name,
th.company_id,
MAX(comp.name) AS company_name,
COALESCE(SUM(CASE WHEN th.transaction_source_type = 1 AND je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) AS total_invoiced,
COALESCE(SUM(CASE WHEN th.transaction_source_type = 3 AND je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) AS total_received,
0::numeric AS total_billed,
0::numeric AS total_paid
FROM public.transaction_headers th
JOIN public.journal_entries je ON je.transaction_id = th.id
JOIN public.companies comp ON comp.id = th.company_id
LEFT JOIN public.customers cust ON cust.id = th.customer_id
WHERE comp.organization_id = p_organization_id
AND (p_company_id IS NULL OR th.company_id = p_company_id)
AND (p_start_date IS NULL OR th.transaction_date >= p_start_date)
AND (p_end_date IS NULL OR th.transaction_date <= p_end_date)
AND th.customer_id IS NOT NULL
GROUP BY th.customer_id, th.company_id;
-- Vendors
ELSIF p_party_type = VENDOR THEN
RETURN QUERY
SELECT
th.vendor_id AS id,
MAX(vend.name) AS party_name,
th.company_id,
MAX(comp.name) AS company_name,
0::numeric AS total_invoiced,
0::numeric AS total_received,
COALESCE(SUM(CASE WHEN th.transaction_source_type = 2 AND je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) AS total_billed,
COALESCE(SUM(CASE WHEN th.transaction_source_type = 4 AND je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) AS total_paid
FROM public.transaction_headers th
JOIN public.journal_entries je ON je.transaction_id = th.id
JOIN public.companies comp ON comp.id = th.company_id
LEFT JOIN public.vendors vend ON vend.id = th.vendor_id
WHERE comp.organization_id = p_organization_id
AND (p_company_id IS NULL OR th.company_id = p_company_id)
AND (p_start_date IS NULL OR th.transaction_date >= p_start_date)
AND (p_end_date IS NULL OR th.transaction_date <= p_end_date)
AND th.vendor_id IS NOT NULL
GROUP BY th.vendor_id, th.company_id;
END IF;
END;
$function$
-- Function: get_comparative_accounts_overview
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_comparative_accounts_overview(p_company_id uuid, p_fin_year_id integer)
RETURNS TABLE(account_id uuid, account_name text, parent_account_id uuid, hierarchy_path text[], order_sequence text, financial_year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_sum numeric, difference numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_current_year_id INTEGER := p_fin_year_id;
v_previous_year_id INTEGER;
v_organization_id UUID;
BEGIN
-- Get organization
SELECT c.organization_id INTO v_organization_id
FROM public.companies c
WHERE c.id = p_company_id;
-- Find previous financial year (based on start_date)
SELECT fy.id
INTO v_previous_year_id
FROM finance_year fy
WHERE fy.start_date < (
SELECT start_date FROM finance_year WHERE id = v_current_year_id
)
ORDER BY fy.start_date DESC
LIMIT 1;
RETURN QUERY
WITH RECURSIVE account_with_path AS (
-- Root accounts
SELECT
coa.id,
coa.name,
coa.parent_account_id,
ARRAY[coa.name]::text[] AS path,
coa.account_number::text AS order_sequence
FROM chart_of_accounts coa
WHERE coa.organization_id = v_organization_id
AND (coa.parent_account_id IS NULL OR coa.parent_account_id = '00000000-0000-0000-0000-000000000000')
AND coa.is_deleted = FALSE
UNION ALL
-- Children
SELECT
c.id,
c.name,
c.parent_account_id,
awp.path || c.name,
awp.order_sequence || '.' || c.account_number::text
FROM chart_of_accounts c
JOIN account_with_path awp ON c.parent_account_id = awp.id
WHERE c.organization_id = v_organization_id
AND c.is_deleted = FALSE
),
leaf_accounts AS (
SELECT a.id
FROM account_with_path a
WHERE NOT EXISTS (
SELECT 1 FROM chart_of_accounts c
WHERE c.parent_account_id = a.id
AND c.organization_id = v_organization_id
AND c.is_deleted = FALSE
)
),
financial_years AS (
SELECT id AS fin_year_id, start_date, end_date
FROM finance_year
WHERE id IN (v_current_year_id, v_previous_year_id)
),
aggregated_data AS (
SELECT
awp.id,
awp.name,
awp.parent_account_id,
awp.path AS hierarchy_path,
awp.order_sequence,
fy.fin_year_id,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN je.amount END), 0) AS apr,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN je.amount END), 0) AS may,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN je.amount END), 0) AS jun,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN je.amount END), 0) AS jul,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN je.amount END), 0) AS aug,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN je.amount END), 0) AS sep,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN je.amount END), 0) AS oct,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN je.amount END), 0) AS nov,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN je.amount END), 0) AS "dec",
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN je.amount END), 0) AS jan,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN je.amount END), 0) AS feb,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN je.amount END), 0) AS mar
FROM account_with_path awp
JOIN leaf_accounts la ON la.id = awp.id -- ✅ only leaf accounts get financial years
CROSS JOIN financial_years fy
LEFT JOIN journal_entries je
ON je.account_id = awp.id
AND je.transaction_date BETWEEN fy.start_date AND fy.end_date
LEFT JOIN transaction_headers th
ON th.id = je.transaction_id
AND th.company_id = p_company_id
WHERE (th.transaction_source_type IS NULL OR th.transaction_source_type != 18)
GROUP BY awp.id, awp.name, awp.parent_account_id, awp.path, awp.order_sequence, fy.fin_year_id
)
SELECT
a1.id AS account_id,
a1.name AS account_name,
a1.parent_account_id,
a1.hierarchy_path,
a1.order_sequence,
a1.fin_year_id AS financial_year,
a1.apr, a1.may, a1.jun, a1.jul, a1.aug, a1.sep, a1.oct, a1.nov, a1."dec",
a1.jan, a1.feb, a1.mar,
(COALESCE(a1.apr,0) + COALESCE(a1.may,0) + COALESCE(a1.jun,0) +
COALESCE(a1.jul,0) + COALESCE(a1.aug,0) + COALESCE(a1.sep,0) +
COALESCE(a1.oct,0) + COALESCE(a1.nov,0) + COALESCE(a1."dec",0) +
COALESCE(a1.jan,0) + COALESCE(a1.feb,0) + COALESCE(a1.mar,0)) AS total_sum,
((COALESCE(a1.apr,0) + COALESCE(a1.may,0) + COALESCE(a1.jun,0) +
COALESCE(a1.jul,0) + COALESCE(a1.aug,0) + COALESCE(a1.sep,0) +
COALESCE(a1.oct,0) + COALESCE(a1.nov,0) + COALESCE(a1."dec",0) +
COALESCE(a1.jan,0) + COALESCE(a1.feb,0) + COALESCE(a1.mar,0))
-
(COALESCE(a2.apr,0) + COALESCE(a2.may,0) + COALESCE(a2.jun,0) +
COALESCE(a2.jul,0) + COALESCE(a2.aug,0) + COALESCE(a2.sep,0) +
COALESCE(a2.oct,0) + COALESCE(a2.nov,0) + COALESCE(a2."dec",0) +
COALESCE(a2.jan,0) + COALESCE(a2.feb,0) + COALESCE(a2.mar,0))
) AS difference
FROM aggregated_data a1
LEFT JOIN aggregated_data a2
ON a1.id = a2.id
AND a1.fin_year_id = v_current_year_id
AND a2.fin_year_id = v_previous_year_id
ORDER BY a1.order_sequence, a1.fin_year_id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_comparative_accounts_overview(p_company_id uuid, p_fin_year_id integer, p_chart_of_account_id uuid)
RETURNS TABLE(account_id uuid, parent_account_name text, parent_account_id uuid, financial_year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_sum numeric, difference numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_current_year INTEGER := p_fin_year_id;
v_previous_year INTEGER := p_fin_year_id - 1;
v_organization_id UUID;
v_is_second_last_leaf BOOLEAN;
BEGIN
-- Get the organization_id for the given company_id
SELECT c.organization_id
INTO v_organization_id
FROM public.companies c
WHERE c.id = p_company_id;
-- Search for 2nd last leaf account
SELECT NOT EXISTS (
SELECT 1 FROM chart_of_accounts ca1
WHERE ca1.parent_account_id = p_chart_of_account_id
)
INTO v_is_second_last_leaf;
RAISE notice 'it is leaf account %', v_is_second_last_leaf;
-- If it is a LEAF account, fetch its transactions directly
IF v_is_second_last_leaf THEN
RETURN QUERY
WITH RECURSIVE child_accounts AS (
-- Get the given parent account and all its child accounts
SELECT id, parent_account_id, name
FROM chart_of_accounts
WHERE parent_account_id = p_chart_of_account_id
UNION ALL
-- Recursively get all child accounts
SELECT ca.id, ca.parent_account_id, ca.name
FROM chart_of_accounts ca
INNER JOIN child_accounts c ON ca.parent_account_id = c.id
),
financial_years AS (
-- Get previous and current financial years
SELECT fy.id AS fin_year_id, fy.start_date, fy.end_date
FROM finance_year fy
WHERE fy.id IN (v_previous_year, v_current_year)
)
SELECT
je.id AS transaction_id,
je.account_id,
ca.name AS account_name,
ca.parent_account_id,
fy.fin_year_id AS financial_year,
je.transaction_date,
EXTRACT(MONTH FROM je.transaction_date) AS calendar_month,
-- Convert Calendar Month to Financial Month (April - March Cycle)
CASE
WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN 'Apr'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN 'May'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN 'Jun'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN 'Jul'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN 'Aug'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN 'Sep'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN 'Oct'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN 'Nov'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN 'Dec'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN 'Jan'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN 'Feb'
WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN 'Mar'
END AS financial_month,
je.amount,
je.description
FROM journal_entries je
INNER JOIN child_accounts ca ON je.account_id = ca.id
INNER JOIN financial_years fy ON je.transaction_date BETWEEN fy.start_date AND fy.end_date
ORDER BY fy.fin_year_id, financial_month, ca.name, je.transaction_date;
ELSE
-- If it is NOT a leaf account, use the ORIGINAL FUNCTIONALITY (recursive aggregation)
RETURN QUERY
WITH direct_children AS (
-- Step 1: Get Direct Children of Given Parent
SELECT ca.id AS account_id, ca.parent_account_id, ca.name
FROM chart_of_accounts ca
WHERE ca.parent_account_id = p_chart_of_account_id
AND ca.organization_id = v_organization_id
),
descendant_accounts AS (
-- Step 2: Recursively Get All Descendants of Each Direct Child
WITH RECURSIVE hierarchy AS (
-- Start with direct children
SELECT dc.account_id, dc.parent_account_id, dc.name
FROM direct_children dc
UNION ALL
-- Then, recursively get all their descendants
SELECT coa.id AS account_id, coa.parent_account_id, coa.name
FROM chart_of_accounts coa
INNER JOIN hierarchy h ON coa.parent_account_id = h.account_id
)
SELECT * FROM hierarchy
),
financial_years AS (
-- Step 3: Get Start and End Dates for Given Financial Years
SELECT id AS fin_year_id, start_date, end_date
FROM finance_year
WHERE id IN (v_previous_year, v_current_year)
),
aggregated_data AS (
-- Step 4: Ensure Each Account Appears for Both Financial Years
SELECT
dc.account_id, -- Include Account ID
dc.name AS parent_account_name,
dc.parent_account_id, -- Ensure Parent Account ID is properly referenced
fy.fin_year_id AS financial_year,
-- Monthly Values (April to March)
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN je.amount ELSE 0 END), 0) AS apr,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN je.amount ELSE 0 END), 0) AS may,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN je.amount ELSE 0 END), 0) AS jun,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN je.amount ELSE 0 END), 0) AS jul,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN je.amount ELSE 0 END), 0) AS aug,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN je.amount ELSE 0 END), 0) AS sep,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN je.amount ELSE 0 END), 0) AS oct,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN je.amount ELSE 0 END), 0) AS nov,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN je.amount ELSE 0 END), 0) AS "dec",
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN je.amount ELSE 0 END), 0) AS jan,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN je.amount ELSE 0 END), 0) AS feb,
COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN je.amount ELSE 0 END), 0) AS mar,
-- Total Sum of All Months
COALESCE(SUM(je.amount), 0) AS total_sum
FROM direct_children dc
CROSS JOIN financial_years fy -- Ensures each account appears for both years
LEFT JOIN descendant_accounts da ON da.parent_account_id = dc.account_id
LEFT JOIN journal_entries je
ON je.account_id = da.account_id
AND je.transaction_date BETWEEN fy.start_date AND fy.end_date -- Ensure correct year filtering
-- AND je.company_id = p_company_id -- Filter by Company ID
GROUP BY dc.account_id, dc.parent_account_id, dc.name, fy.fin_year_id
)
-- Step 5: Compute Difference Between Two Financial Years
SELECT
a1.account_id,
a1.parent_account_name,
a1.parent_account_id,
a1.financial_year,
-- Monthly Values
a1.apr, a1.may, a1.jun, a1.jul, a1.aug, a1.sep, a1.oct, a1.nov, a1."dec",
a1.jan, a1.feb, a1.mar,
-- Total Sum for Financial Year
a1.total_sum,
-- Difference (Current Year - Previous Year)
(COALESCE(a1.total_sum, 0) - COALESCE(a2.total_sum, 0)) AS difference
FROM aggregated_data a1
LEFT JOIN aggregated_data a2
ON a1.account_id = a2.account_id
AND a1.financial_year = v_current_year -- Current Year
AND a2.financial_year = v_previous_year -- Previous Year
ORDER BY a1.parent_account_name, a1.financial_year;
END IF;
END;
$function$
-- Function: update_basic_company
CREATE OR REPLACE FUNCTION public.update_basic_company(p_company_id uuid, p_organization_id uuid, p_name text, p_gst_in text, p_short_name text, p_currency text, p_pan text, p_tan text, p_outstanding_limit numeric, p_is_apartment boolean, p_is_non_work boolean, p_interest_percentage numeric, p_has_gst_in boolean, p_email text, p_mobile_number text)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_contact_id UUID;
BEGIN
-- 1️⃣ Get contact_id from company_contacts
SELECT contact_id INTO v_contact_id
FROM company_contacts
WHERE company_id = p_company_id
LIMIT 1;
-- 2️⃣ Update company table
UPDATE companies
SET
organization_id = p_organization_id,
name = p_name,
gst_in = p_gst_in,
short_name = p_short_name,
currency = p_currency,
pan = p_pan,
tan = p_tan,
outstanding_limit = p_outstanding_limit,
is_apartment = p_is_apartment,
is_non_work = p_is_non_work,
interest_percentage = p_interest_percentage,
has_gst_in = p_has_gst_in,
modified_on_utc = NOW()
WHERE id = p_company_id;
-- 3️⃣ Update contact table if mapping exists
IF v_contact_id IS NOT NULL THEN
UPDATE contacts
SET
email = p_email,
mobile_number = p_mobile_number,
modified_on_utc = NOW()
WHERE id = v_contact_id;
END IF;
END;
$function$
-- Function: pgp_sym_decrypt
CREATE OR REPLACE FUNCTION public.pgp_sym_decrypt(bytea, text)
RETURNS text
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_sym_decrypt_text$function$
-- Function: pgp_sym_decrypt_bytea
CREATE OR REPLACE FUNCTION public.pgp_sym_decrypt_bytea(bytea, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_sym_decrypt_bytea$function$
-- Function: get_module_permissions
CREATE OR REPLACE FUNCTION public.get_module_permissions()
RETURNS TABLE(id integer, feature_name text, read_permission_id uuid, create_permission_id uuid, update_permission_id uuid, delete_permission_id uuid, full_access_permission_id uuid, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, deleted_on_utc timestamp without time zone, is_deleted boolean, created_by uuid, modified_by uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
mp.id,
mp.feature_name,
mp.read_permission_id,
mp.create_permission_id,
mp.update_permission_id,
mp.delete_permission_id,
mp.full_access_permission_id,
mp.created_on_utc,
mp.modified_on_utc,
mp.deleted_on_utc,
mp.is_deleted,
mp.created_by,
mp.modified_by
FROM module_permissions mp
WHERE mp.is_deleted = false
ORDER BY mp.feature_name;
END;
$function$
-- Function: get_profit_loss_statement
CREATE OR REPLACE FUNCTION public.get_profit_loss_statement(p_company_id uuid, p_fin_year_id integer, p_start_date date, p_end_date date, p_is_get_for_organization boolean)
RETURNS TABLE(account_id uuid, account_type text, account_name text, amount numeric, category text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date;
v_end_date date;
v_fy_start_date date;
v_fy_end_date date;
v_company_ids UUID[];
v_organization_id UUID;
income_account_ids integer[];
expense_account_ids integer[];
BEGIN
-- Get the financial year dates
SELECT start_date::date, end_date::date
INTO v_fy_start_date, v_fy_end_date
FROM public.finance_year
WHERE id = p_fin_year_id
LIMIT 1;
-- Apply filter logic for new date params
v_start_date := COALESCE(p_start_date, v_fy_start_date);
v_end_date := COALESCE(p_end_date, v_fy_end_date);
-- Get organization id
SELECT organization_id INTO v_organization_id
FROM public.companies
WHERE id = p_company_id;
-- Get account type hierarchies
income_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(4));
expense_account_ids := ARRAY(SELECT id FROM get_account_type_hierarchy(5));
-- Populate company IDs based on org flag
IF p_is_get_for_organization THEN
SELECT ARRAY(
SELECT id FROM companies
WHERE organization_id = v_organization_id
)
INTO v_company_ids;
ELSE
v_company_ids := ARRAY[p_company_id];
END IF;
-- Return Income
RETURN QUERY
SELECT
coa.id as account_id, -- GUID column
at.name AS account_type,
coa.name AS account_name,
COALESCE(
SUM(
CASE
WHEN ob.entry_type = 'C' THEN ob.balance
ELSE -ob.balance
END
),0
) +
COALESCE(
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE -je.amount END), 0
) AS amount,
'Income' AS category
FROM public.chart_of_accounts coa
INNER JOIN public.journal_entries je ON je.account_id = coa.id
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
INNER JOIN public.account_types at ON coa.account_type_id = at.id
LEFT JOIN public.opening_balances ob ON ob.account_id = coa.id
AND ob.organization_id = v_organization_id
AND ob.finyear_id = p_fin_year_id
AND ob.is_deleted = FALSE
WHERE th.company_id = ANY(v_company_ids)
AND th.transaction_date BETWEEN v_start_date AND v_end_date
AND NOT th.is_deleted
AND NOT je.is_deleted
AND coa.account_type_id = ANY(income_account_ids)
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
ORDER BY coa.account_number, at.id;
-- Return Expenses
RETURN QUERY
SELECT
coa.id as account_id, -- GUID column
at.name AS account_type,
coa.name AS account_name,
COALESCE(
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE -je.amount END), 0
) AS amount,
'Expenses' AS category
FROM public.chart_of_accounts coa
INNER JOIN public.journal_entries je ON je.account_id = coa.id
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
INNER JOIN public.account_types at ON coa.account_type_id = at.id
WHERE th.company_id = ANY(v_company_ids)
AND th.transaction_date BETWEEN v_start_date AND v_end_date
AND NOT th.is_deleted
AND NOT je.is_deleted
AND coa.account_type_id = ANY(expense_account_ids)
GROUP BY coa.id, at.name, coa.name, coa.account_number, at.id
ORDER BY coa.account_number, at.id;
END;
$function$
-- Function: pgp_sym_encrypt_bytea
CREATE OR REPLACE FUNCTION public.pgp_sym_encrypt_bytea(bytea, text, text)
RETURNS bytea
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_sym_encrypt_bytea$function$
-- Function: get_trial_balance_of_company_fin_year
CREATE OR REPLACE FUNCTION public.get_trial_balance_of_company_fin_year(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(account_type text, account_category text, account_number text, account_name text, total_debits numeric, total_credits numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH RECURSIVE coa_hierarchy AS (
-- Base case: Fetch the top-level (root) accounts
SELECT
coa.id,
act.name AS account_type,
act.classification AS account_classification,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
0 AS level,
coa.account_number::text AS order_sequence
FROM
public.chart_of_accounts coa
INNER JOIN
public.account_types act ON coa.account_type_id = act.id
WHERE
coa.is_deleted = FALSE
AND coa.parent_account_id = '00000000-0000-0000-0000-000000000000' -- Root-level accounts
UNION ALL
-- Recursive case: Fetch child accounts
SELECT
coa.id,
act.name AS account_type,
act.classification AS account_classification,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
ch.level + 1 AS level,
ch.order_sequence || '.' || coa.account_number::text AS order_sequence
FROM
public.chart_of_accounts coa
INNER JOIN
public.account_types act ON coa.account_type_id = act.id
INNER JOIN
coa_hierarchy ch ON ch.id = coa.parent_account_id
WHERE
coa.is_deleted = FALSE
),
coa_with_balances AS (
-- Fetch financial year start and end date
SELECT
fy.start_date::DATE,
fy.end_date::DATE
FROM
public.finance_year fy
WHERE
fy.id = p_finance_year_id
LIMIT 1
),
journal_entries_filtered AS (
-- Get total debits and credits per account
SELECT
je.account_id,
SUM(CASE
WHEN je.entry_type = 'D' THEN je.amount
ELSE 0
END) AS total_debits,
SUM(CASE
WHEN je.entry_type = 'C' THEN je.amount
ELSE 0
END) AS total_credits
FROM
public.journal_entries je
JOIN
public.transaction_headers th ON je.transaction_id = th.id
JOIN
coa_with_balances fy ON th.transaction_date BETWEEN fy.start_date AND fy.end_date
WHERE
th.company_id = p_company_id
AND je.is_deleted = FALSE
GROUP BY
je.account_id
HAVING
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) > 0 OR
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) > 0
),
relevant_accounts AS (
-- Select accounts that have balances
SELECT
ch.id,
ch.account_type,
ch.account_classification,
ch.account_number,
ch.name,
ch.level,
ch.order_sequence,
ch.parent_account_id,
wb.total_debits,
wb.total_credits
FROM
coa_hierarchy ch
LEFT JOIN
journal_entries_filtered wb ON ch.id = wb.account_id
WHERE
wb.account_id IS NOT NULL
UNION ALL
-- Recursively select parent accounts
SELECT
ch.id,
ch.account_type,
ch.account_classification,
ch.account_number,
ch.name,
ch.level,
ch.order_sequence,
ch.parent_account_id,
NULL AS total_debits,
NULL AS total_credits
FROM
coa_hierarchy ch
INNER JOIN
relevant_accounts ra ON ch.id = ra.parent_account_id
)
-- Final output
SELECT DISTINCT ON (ra.order_sequence)
ra.account_type::TEXT AS account_type,
ac.name::TEXT AS account_category,
ra.account_number::TEXT AS account_number,
LPAD('', ra.level * 4, ' ') || ra.name::TEXT AS account_name,
CASE
WHEN ra.account_classification = 'EXPENSES' THEN COALESCE(ra.total_credits, 0) -- Expenses should be on the debit side
ELSE COALESCE(ra.total_debits, 0)
END AS total_debits,
CASE
WHEN ra.account_classification = 'EXPENSES' THEN COALESCE(ra.total_debits, 0) -- Expenses should not appear on the credit side
ELSE COALESCE(ra.total_credits, 0)
END AS total_credits
FROM
relevant_accounts ra
LEFT JOIN
public.chart_of_accounts coa ON ra.id = coa.id
LEFT JOIN
public.account_types at ON coa.account_type_id = at.id
LEFT JOIN
public.account_categories ac ON at.account_category_id = ac.id
CROSS JOIN
coa_with_balances fy
ORDER BY
ra.order_sequence;
END;
$function$
-- Function: pgp_sym_decrypt
CREATE OR REPLACE FUNCTION public.pgp_sym_decrypt(bytea, text, text)
RETURNS text
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_sym_decrypt_text$function$
-- Function: digest
CREATE OR REPLACE FUNCTION public.digest(text, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_digest$function$
-- Function: digest
CREATE OR REPLACE FUNCTION public.digest(bytea, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_digest$function$
-- Function: hmac
CREATE OR REPLACE FUNCTION public.hmac(text, text, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_hmac$function$
-- Function: hmac
CREATE OR REPLACE FUNCTION public.hmac(bytea, bytea, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_hmac$function$
-- Function: crypt
CREATE OR REPLACE FUNCTION public.crypt(text, text)
RETURNS text
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_crypt$function$
-- Function: gen_salt
CREATE OR REPLACE FUNCTION public.gen_salt(text)
RETURNS text
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_gen_salt$function$
-- Function: gen_salt
CREATE OR REPLACE FUNCTION public.gen_salt(text, integer)
RETURNS text
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_gen_salt_rounds$function$
-- Function: encrypt
CREATE OR REPLACE FUNCTION public.encrypt(bytea, bytea, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_encrypt$function$
-- Function: decrypt
CREATE OR REPLACE FUNCTION public.decrypt(bytea, bytea, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_decrypt$function$
-- Function: encrypt_iv
CREATE OR REPLACE FUNCTION public.encrypt_iv(bytea, bytea, bytea, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_encrypt_iv$function$
-- Function: decrypt_iv
CREATE OR REPLACE FUNCTION public.decrypt_iv(bytea, bytea, bytea, text)
RETURNS bytea
LANGUAGE c
IMMUTABLE PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_decrypt_iv$function$
-- Function: gen_random_bytes
CREATE OR REPLACE FUNCTION public.gen_random_bytes(integer)
RETURNS bytea
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pg_random_bytes$function$
-- Function: gen_random_uuid
CREATE OR REPLACE FUNCTION public.gen_random_uuid()
RETURNS uuid
LANGUAGE c
PARALLEL SAFE
AS '$libdir/pgcrypto', $function$pg_random_uuid$function$
-- Function: pgp_sym_encrypt
CREATE OR REPLACE FUNCTION public.pgp_sym_encrypt(text, text)
RETURNS bytea
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_sym_encrypt_text$function$
-- Function: pgp_sym_encrypt_bytea
CREATE OR REPLACE FUNCTION public.pgp_sym_encrypt_bytea(bytea, text)
RETURNS bytea
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_sym_encrypt_bytea$function$
-- Function: pgp_sym_encrypt
CREATE OR REPLACE FUNCTION public.pgp_sym_encrypt(text, text, text)
RETURNS bytea
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/pgcrypto', $function$pgp_sym_encrypt_text$function$
-- Function: get_bank_statements_for_status
CREATE OR REPLACE FUNCTION public.get_bank_statements_for_status(p_company_id uuid, p_finyear_id integer, p_date_from timestamp without time zone DEFAULT NULL::timestamp without time zone, p_date_to timestamp without time zone DEFAULT NULL::timestamp without time zone)
RETURNS TABLE(id integer, company_id uuid, bank_id integer, bank_name text, txn_date timestamp without time zone, cheque_number character varying, description text, value_date timestamp without time zone, branch_code character varying, debit_amount numeric, credit_amount numeric, balance numeric, has_reconciled boolean, reconciliation_status_id integer, reconciliation_status text, reconciliation_description text, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, deleted_on_utc timestamp without time zone, is_deleted boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
bs.id,
bs.company_id,
bs.bank_id,
b.name AS bank_name,
bs.txn_date::timestamp,
bs.cheque_number,
bs.description,
bs.value_date::timestamp,
bs.branch_code,
bs.debit_amount,
bs.credit_amount,
bs.balance,
-- 🧠 Smart has_reconciled logic
CASE
WHEN br.id IS NOT NULL
AND rs.status IN ('partial', 'matched', 'audited') THEN TRUE
ELSE FALSE
END AS has_reconciled,
-- 🧩 New reconciliation info
rs.id AS reconciliation_status_id,
rs.status::text AS reconciliation_status,
rs.description::text AS reconciliation_description,
bs.created_by,
bs.created_on_utc,
bs.modified_by,
bs.modified_on_utc,
bs.deleted_on_utc,
bs.is_deleted
FROM public.bank_statements bs
INNER JOIN public.banks b
ON b.id = bs.bank_id
AND b.is_deleted = false
INNER JOIN public.finance_year fy
ON fy.id = p_finyear_id
LEFT JOIN LATERAL (
SELECT br.*
FROM public.bank_reconciliations br
WHERE br.bank_statement_id = bs.id
ORDER BY br.matched_on_utc DESC
LIMIT 1
) br ON TRUE
LEFT JOIN public.reconciliation_statuses rs
ON rs.id = br.reconciliation_status_id
WHERE bs.company_id = p_company_id
AND bs.txn_date BETWEEN fy.start_date AND fy.end_date
AND (p_date_from IS NULL OR bs.txn_date >= p_date_from)
AND (p_date_to IS NULL OR bs.txn_date <= p_date_to)
AND bs.is_deleted = false
ORDER BY bs.txn_date, bs.id;
END;
$function$
-- Function: verify_user_password
CREATE OR REPLACE FUNCTION public.verify_user_password(p_user_name text, p_password text)
RETURNS uuid
LANGUAGE plpgsql
AS $function$
DECLARE
v_user_id UUID;
BEGIN
SELECT id
INTO v_user_id
FROM users
WHERE user_name = p_user_name
AND is_deleted = false
AND password_hash = crypt(p_password, password_hash);
RETURN v_user_id;
END;
$function$
-- Function: get_account_ledger
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_account_ledger(p_account_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid, customer_id uuid, customer_name text, employee_id uuid, employee_name text, vendor_id uuid, vendor_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH RECURSIVE account_tree AS (
SELECT id
FROM public.chart_of_accounts
WHERE id = p_account_id
AND organization_id = p_organization_id
AND is_deleted = false
UNION ALL
SELECT coa.id
FROM public.chart_of_accounts coa
INNER JOIN account_tree at ON coa.parent_account_id = at.id
WHERE coa.organization_id = p_organization_id
AND coa.is_deleted = false
),
account_transactions AS (
SELECT
th.transaction_date::date AS transaction_date,
coa.name AS account_name,
coa.id AS account_id,
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
th.document_number,
th.document_id,
th.transaction_source_type,
tst.sort_order,
th.id AS transaction_id,
je.id AS journal_entry_id,
th.customer_id,
th.employee_id,
th.vendor_id
FROM
public.transaction_headers th
INNER JOIN
public.journal_entries je ON th.id = je.transaction_id
INNER JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
INNER JOIN
account_tree at ON je.account_id = at.id
INNER JOIN
public.transaction_source_types tst ON th.transaction_source_type = tst.id
WHERE
th.company_id = p_company_id
AND coa.organization_id = p_organization_id
AND th.is_deleted = false
AND je.is_deleted = false
AND th.transaction_date BETWEEN p_start_date AND p_end_date
)
SELECT
at.transaction_date,
at.account_name,
at.debit,
at.credit,
SUM(at.debit - at.credit) OVER (
ORDER BY
at.transaction_date,
at.sort_order,
at.transaction_id,
at.journal_entry_id
) AS balance,
at.transaction_source_type,
at.document_number,
at.document_id,
c.id AS customer_id,
c.name::text AS customer_name,
e.id AS employee_id,
CASE
WHEN e.id IS NOT NULL THEN (e.first_name || ' ' || e.last_name)::text
ELSE NULL
END AS employee_name,
v.id AS vendor_id,
v.name::text AS vendor_name
FROM
account_transactions at
LEFT JOIN public.customers c ON at.customer_id = c.id AND c.company_id = p_company_id
LEFT JOIN public.employees e ON at.employee_id = e.id AND e.company_id = p_company_id
LEFT JOIN public.vendors v ON at.vendor_id = v.id AND v.company_id = p_company_id
WHERE
NOT (at.debit = 0 AND at.credit = 0)
ORDER BY
at.transaction_date,
at.sort_order,
at.transaction_id,
at.journal_entry_id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_account_ledger(p_account_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH account_transactions AS (
SELECT
th.transaction_date::date AS transaction_date, -- Cast to date
coa.name AS account_name,
CASE
WHEN je.entry_type = 'D' THEN je.amount
ELSE 0
END AS debit,
CASE
WHEN je.entry_type = 'C' THEN je.amount
ELSE 0
END AS credit,
th.company_id,
je.account_id,
th.transaction_source_type,
th.document_number,
th.document_id
FROM
public.transaction_headers th
INNER JOIN
public.journal_entries je ON th.id = je.transaction_id
INNER JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
je.account_id = p_account_id
AND th.company_id = p_company_id
AND coa.organization_id = p_organization_id
AND th.is_deleted = false
AND je.is_deleted = false
AND th.transaction_date >= p_start_date
AND th.transaction_date <= p_end_date
)
SELECT
at.transaction_date,
at.account_name,
at.debit,
at.credit,
SUM(at.debit - at.credit) OVER (PARTITION BY at.account_id ORDER BY at.transaction_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance,
at.transaction_source_type,
at.document_number,
at.document_id
FROM
account_transactions at
WHERE
NOT (at.debit = 0 AND at.credit = 0)
ORDER BY
at.transaction_date;
END;
$function$
-- Function: get_account_transactions
CREATE OR REPLACE FUNCTION public.get_account_transactions(p_company_id uuid, p_fin_year_id integer, p_chart_of_account_id uuid)
RETURNS TABLE(account_id uuid, account_name text, parent_account_id uuid, account_type text, year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_current_year numeric, total_last_year numeric, difference numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date TIMESTAMP DEFAULT NULL;
v_end_date TIMESTAMP DEFAULT NULL;
v_prev_start_date TIMESTAMP DEFAULT NULL;
v_prev_end_date TIMESTAMP DEFAULT NULL;
BEGIN
-- Get start and end date for the given financial year(fn)
SELECT fn.start_date, fn.end_date
INTO v_start_date, v_end_date
FROM finance_year fn
WHERE fn.id = p_fin_year_id
LIMIT 1;
-- If no result, raise an error
IF v_start_date IS NULL OR v_end_date IS NULL THEN
RAISE EXCEPTION 'Financial year % not found in finance_year table', p_fin_year_id;
END IF;
-- Get the previous financial year's(pfn) start and end date
SELECT pfn.start_date, pfn.end_date
INTO v_prev_start_date, v_prev_end_date
FROM finance_year pfn
WHERE pfn.id = (p_fin_year_id - 1)
LIMIT 1;
-- Return the final query
RETURN QUERY
WITH direct_children AS (
SELECT coa.id AS child_id, coa.name AS child_name, coa.parent_account_id AS direct_parent_id, coa.account_type_id
FROM chart_of_accounts coa
WHERE coa.parent_account_id = p_chart_of_account_id
),
all_descendants AS (
WITH RECURSIVE hierarchy AS (
SELECT ca.id AS descendant_id, ca.parent_account_id AS ancestor_parent_id
FROM chart_of_accounts ca
WHERE ca.parent_account_id IN (SELECT child_id FROM direct_children)
UNION ALL
SELECT coa.id AS descendant_id, coa.parent_account_id AS ancestor_parent_id
FROM chart_of_accounts coa
INNER JOIN hierarchy h ON coa.parent_account_id = h.descendant_id
)
SELECT descendant_id, ancestor_parent_id FROM hierarchy
),
years AS (
SELECT p_fin_year_id AS year
UNION ALL
SELECT (p_fin_year_id - 1) AS year
),
all_accounts AS (
SELECT
dc.child_id AS account_id,
dc.child_name AS account_name,
dc.direct_parent_id AS parent_account_id,
dc.account_type_id,
y.year,
dc.child_id AS summation_base_id
FROM direct_children dc
CROSS JOIN years y
UNION ALL
SELECT
ad.descendant_id AS account_id,
dc.child_name AS account_name,
dc.direct_parent_id AS parent_account_id,
dc.account_type_id,
y.year,
dc.child_id AS summation_base_id
FROM direct_children dc
JOIN all_descendants ad ON dc.child_id = ad.ancestor_parent_id
CROSS JOIN years y
)
SELECT
aa.summation_base_id AS account_id,
aa.account_name,
aa.parent_account_id,
at.name AS account_type,
aa.year,
-- Monthly values, ensuring correct year filtering
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 4 THEN je.amount ELSE 0 END), 0) AS apr,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 5 THEN je.amount ELSE 0 END), 0) AS may,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 6 THEN je.amount ELSE 0 END), 0) AS jun,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 7 THEN je.amount ELSE 0 END), 0) AS jul,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 8 THEN je.amount ELSE 0 END), 0) AS aug,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 9 THEN je.amount ELSE 0 END), 0) AS sep,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 10 THEN je.amount ELSE 0 END), 0) AS oct,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 11 THEN je.amount ELSE 0 END), 0) AS nov,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 12 THEN je.amount ELSE 0 END), 0) AS "dec",
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 1 THEN je.amount ELSE 0 END), 0) AS jan,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 2 THEN je.amount ELSE 0 END), 0) AS feb,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date AND EXTRACT(MONTH FROM je.transaction_date) = 3 THEN je.amount ELSE 0 END), 0) AS mar,
-- Totals for each year
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date THEN je.amount ELSE 0 END), 0) AS total_current_year,
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date THEN je.amount ELSE 0 END), 0) AS total_last_year,
-- Difference calculation
(COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_start_date AND v_end_date THEN je.amount ELSE 0 END), 0) -
COALESCE(SUM(CASE WHEN je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date THEN je.amount ELSE 0 END), 0))
AS difference
FROM all_accounts aa
LEFT JOIN journal_entries je
ON je.account_id = aa.account_id
AND (je.transaction_date BETWEEN v_start_date AND v_end_date OR
je.transaction_date BETWEEN v_prev_start_date AND v_prev_end_date)
LEFT JOIN account_types at ON at.id = aa.account_type_id
GROUP BY aa.year, aa.summation_base_id, aa.account_name, aa.parent_account_id, at.name
ORDER BY aa.summation_base_id, aa.year;
END;
$function$
-- Function: get_all_coa
CREATE OR REPLACE FUNCTION public.get_all_coa(p_organization_id uuid, p_finyear_id integer)
RETURNS TABLE(id uuid, account_type text, account_number integer, name text, opening_balance numeric, level integer, order_sequence character varying, is_default_account boolean, schedule text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH RECURSIVE coa_view AS (
SELECT
coa.id,
act.name as account_type,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
0 AS level,
coa.account_number::text AS order_sequence,
coa.is_default_account,
NULL::text AS schedule
FROM public.chart_of_accounts coa
INNER JOIN public.account_types act ON coa.account_type_id = act.id
WHERE coa.organization_id = p_organization_id
AND (coa.parent_account_id = '00000000-0000-0000-0000-000000000000' OR coa.parent_account_id IS NULL)
AND coa.is_deleted = FALSE
UNION ALL
SELECT
coa.id,
act.name as account_type,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
view.level + 1 AS level,
view.order_sequence || '.' || coa.account_number::text AS order_sequence,
coa.is_default_account,
NULL::text AS schedule
FROM public.chart_of_accounts coa
INNER JOIN coa_view view ON view.id = coa.parent_account_id
INNER JOIN public.account_types act ON coa.account_type_id = act.id
WHERE coa.organization_id = p_organization_id
AND coa.is_deleted = FALSE
),
balances AS (
SELECT
je.account_id,
SUM(
CASE WHEN je.entry_type = 'D' THEN je.amount
ELSE -je.amount END
) AS balance
FROM public.journal_entries je
INNER JOIN public.transaction_headers th
ON th.id = je.transaction_id
INNER JOIN public.companies c
ON th.company_id = c.id
AND c.organization_id = p_organization_id
INNER JOIN public.finance_year fy
ON th.transaction_date BETWEEN fy.start_date AND fy.end_date
AND fy.id = p_finyear_id
WHERE th.is_deleted = FALSE
GROUP BY je.account_id
)
SELECT
view.id,
view.account_type,
view.account_number,
LPAD('', view.level * 4, ' ') || view.name AS name,
COALESCE(balances.balance, 0) AS opening_balance,
view.level,
view.order_sequence::character varying AS order_sequence,
view.is_default_account,
view.schedule
FROM coa_view view
LEFT JOIN balances ON balances.account_id = view.id
ORDER BY view.order_sequence;
END;
$function$
-- Function: get_all_coa_with_pi
CREATE OR REPLACE FUNCTION public.get_all_coa_with_pi(p_organization_id uuid)
RETURNS TABLE(id uuid, account_type text, account_number integer, name text, opening_balance numeric, level integer, order_sequence character varying, schedule text, parent_account_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN query
WITH RECURSIVE coa_view AS (
SELECT
coa.id,
act.name as account_type,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
0 AS level,
coa.account_number::text AS order_sequence,
NULL::text AS schedule
FROM
public.chart_of_accounts coa
INNER JOIN
public.account_types act ON coa.account_type_id = act.id
WHERE
coa.organization_id = p_organization_id
AND coa.parent_account_id = '00000000-0000-0000-0000-000000000000'
AND coa.is_deleted = FALSE
UNION ALL
SELECT
coa.id,
act.name as account_type,
coa.account_number::integer,
coa.name,
coa.parent_account_id,
view.level + 1 AS level,
view.order_sequence || '.' || coa.account_number::text AS order_sequence,
NULL::text AS schedule
FROM
public.chart_of_accounts coa
INNER JOIN
coa_view view ON view.id = coa.parent_account_id
INNER JOIN
public.account_types act ON coa.account_type_id = act.id
WHERE
coa.organization_id = p_organization_id
AND coa.is_deleted = FALSE
)
SELECT
view.id,
view.account_type,
view.account_number,
LPAD('', view.level * 4, ' ') || view.name AS name,
0::numeric AS opening_balance,
view.level,
view.order_sequence::character varying AS order_sequence,
view.schedule,
view.parent_account_id
FROM
coa_view view
ORDER BY
view.order_sequence;
END;
$function$
-- Function: change_user_password
CREATE OR REPLACE FUNCTION public.change_user_password(p_user_name text, p_current_password text, p_new_password text, p_modified_by uuid)
RETURNS boolean
LANGUAGE plpgsql
AS $function$
DECLARE
v_user_id UUID;
BEGIN
-- Step 1: verify current password
SELECT id
INTO v_user_id
FROM users
WHERE user_name = p_user_name
AND is_deleted = false
AND password_hash = crypt(p_current_password, password_hash);
IF v_user_id IS NULL THEN
RETURN FALSE;
END IF;
-- Step 2: update password
UPDATE users
SET password_hash = crypt(p_new_password, gen_salt('bf', 12)),
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE id = v_user_id;
RETURN TRUE;
END;
$function$
-- Function: get_coa_opening_balance
CREATE OR REPLACE FUNCTION public.get_coa_opening_balance(p_coa_id uuid, p_finyear_id integer)
RETURNS TABLE(transaction_date timestamp without time zone, coa_id uuid, coa_name text, description text, debit numeric, credit numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_year_text text;
BEGIN
-- Fetch the year text for the given finance year id
SELECT year INTO v_year_text FROM public.finance_year WHERE id = p_finyear_id;
IF v_year_text IS NULL THEN
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
END IF;
RETURN QUERY
SELECT
th.transaction_date,
th.document_id AS coa_id,
coa.name AS coa_name,
th.description,
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) AS debit,
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) AS credit
FROM public.transaction_headers th
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.chart_of_accounts coa ON th.document_id = coa.id
WHERE th.document_id = p_coa_id
AND je.account_id = th.document_id
AND th.transaction_source_type = 13
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
ORDER BY th.transaction_date;
END;
$function$
-- Function: get_cash_flow
CREATE OR REPLACE FUNCTION public.get_cash_flow()
RETURNS TABLE(transaction_date date, account_number text, name text, net_cash_flow numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
je.transaction_date,
coa.account_number,
coa.name,
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE -je.amount END) AS net_cash_flow
FROM
public.journal_entries je
JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
coa.account_type_id = 1 -- Current Assets, focusing on cash accounts
AND je.is_deleted = false
GROUP BY
je.transaction_date, coa.account_number, coa.name
ORDER BY
coa.account_number, je.transaction_date ;
END;
$function$
-- Function: get_cash_flow
CREATE OR REPLACE FUNCTION public.get_cash_flow(p_company_id uuid)
RETURNS TABLE(transaction_date date, account_number text, name text, net_cash_flow numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
je.transaction_date::date,
coa.account_number,
coa.name,
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE -je.amount END) AS net_cash_flow
FROM
public.journal_entries je
JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
JOIN
public.transaction_headers th ON je.transaction_id = th.id
WHERE
coa.account_type_id = 1 -- Current Assets, focusing on cash accounts
AND je.is_deleted = false
AND th.company_id = p_company_id
GROUP BY
je.transaction_date, coa.account_number, coa.name
ORDER BY
coa.account_number, je.transaction_date ;
END;
$function$
-- Function: get_customer_opening_balance
CREATE OR REPLACE FUNCTION public.get_customer_opening_balance(p_customer_id uuid, p_finyear_id integer)
RETURNS TABLE(transaction_date timestamp without time zone, customer_id uuid, customer_name text, description text, debit numeric, credit numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_year_text text;
BEGIN
-- Fetch the year text for the given finance year id
SELECT year INTO v_year_text FROM public.finance_year WHERE id = p_finyear_id;
IF v_year_text IS NULL THEN
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
END IF;
RETURN QUERY
SELECT
th.transaction_date,
th.customer_id,
c.name::text AS customer_name, -- explicit cast here!
th.description,
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) AS debit,
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) AS credit
FROM public.transaction_headers th
JOIN public.customers c ON th.customer_id = c.id
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.chart_of_accounts sd ON sd.name ILIKE '%Sundry Debtors%'
WHERE th.customer_id = p_customer_id
AND je.account_id = sd.id
AND th.transaction_source_type = 13
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
ORDER BY th.transaction_date;
END;
$function$
-- Function: get_dashboard_totals_by_company_id
CREATE OR REPLACE FUNCTION public.get_dashboard_totals_by_company_id(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(income_total numeric, expense_total numeric, pending_dues_total numeric, pending_payments_total numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_organization_id UUID;
v_financial_year_start DATE;
v_financial_year_end DATE;
v_finance_year TEXT;
BEGIN
-- Fetch organization ID
SELECT organization_id INTO v_organization_id
FROM public.companies
WHERE id = p_company_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Invalid company ID: %', p_company_id;
END IF;
-- Fetch financial year details
SELECT start_date, end_date, year INTO v_financial_year_start, v_financial_year_end, v_finance_year
FROM public.finance_year
WHERE id = p_finance_year_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Invalid finance year ID: %', p_finance_year_id;
END IF;
-- Calculate totals
RETURN QUERY
SELECT
-- Income Total
ROUND(
(
SELECT COALESCE(SUM(je.amount), 0)
FROM public.journal_entries je
INNER JOIN public.chart_of_accounts ca ON je.account_id = ca.id
INNER JOIN public.account_types at ON ca.account_type_id = at.id
INNER JOIN public.account_categories ac ON at.account_category_id = ac.id
INNER JOIN public.transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 4
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 2
) AS income_total,
-- Expense Total
ROUND(
(
SELECT COALESCE(SUM(je.amount), 0)
FROM public.journal_entries je
INNER JOIN public.chart_of_accounts ca ON je.account_id = ca.id
INNER JOIN public.account_types at ON ca.account_type_id = at.id
INNER JOIN public.account_categories ac ON at.account_category_id = ac.id
INNER JOIN public.transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 5
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 2
) AS expense_total,
ROUND(
(
SELECT
COALESCE(SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END), 0) -
COALESCE(SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END), 0)
FROM public.journal_entries je
INNER JOIN public.chart_of_accounts ca ON je.account_id = ca.id
INNER JOIN public.account_types at ON ca.account_type_id = at.id
INNER JOIN public.account_categories ac ON at.account_category_id = ac.id
INNER JOIN public.transaction_headers tr ON je.transaction_id = tr.id
WHERE at.name IN ('Accounts Receivable', 'Current Assets') -- Filter specifically for receivables
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 2
) AS pending_dues_total,
-- Pending Payments Total
ROUND(
(
SELECT COALESCE(SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END), 0) -
COALESCE(SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END), 0)
FROM public.journal_entries je
INNER JOIN public.chart_of_accounts ca ON je.account_id = ca.id
INNER JOIN public.account_types at ON ca.account_type_id = at.id
INNER JOIN public.transaction_headers tr ON je.transaction_id = tr.id
WHERE ca.account_type_id IN (
SELECT id
FROM public.get_account_type_hierarchy(2)
)
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
AND je.entry_type IN ('C', 'D') -- Ensure only valid entry types are included
), 2
) AS pending_payments_total
;
END;
$function$
-- Function: get_dashboard_totals_test
CREATE OR REPLACE FUNCTION public.get_dashboard_totals_test(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(income_total numeric, expense_total numeric, pending_dues_total numeric, pending_payments_total numeric, prev_income_total numeric, prev_expense_total numeric, prev_pending_dues_total numeric, prev_pending_payments_total numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_organization_id UUID;
v_financial_year_start DATE;
v_financial_year_end DATE;
v_prev_financial_year_start DATE;
v_prev_financial_year_end DATE;
v_finance_year INTEGER;
v_today DATE := CURRENT_DATE;
BEGIN
-- Fetch organization ID
SELECT organization_id INTO v_organization_id
FROM public.companies
WHERE id = p_company_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Invalid company ID: %', p_company_id;
END IF;
-- Get financial year integer from 'YYYY-YY'
SELECT LEFT(year, 4)::INTEGER INTO v_finance_year
FROM public.finance_year
WHERE id = p_finance_year_id
LIMIT 1;
IF NOT FOUND THEN
RAISE EXCEPTION 'Invalid finance year ID: %', p_finance_year_id;
END IF;
-- Define financial year range
v_financial_year_start := MAKE_DATE(v_finance_year, 4, 1);
v_financial_year_end := MAKE_DATE(v_finance_year + 1, 3, 31);
-- Define previous financial year range
v_prev_financial_year_start := MAKE_DATE(v_finance_year - 1, 4, 1);
v_prev_financial_year_end := MAKE_DATE(v_finance_year, 3, 31);
-- Adjust financial year ends if the year is ongoing
IF v_today < v_financial_year_end THEN
v_financial_year_end := v_today;
END IF;
IF v_today < v_prev_financial_year_end THEN
v_prev_financial_year_end := v_today - INTERVAL '1 year';
END IF;
-- Align both end dates to same day/month by limiting to shorter period
v_financial_year_end := LEAST(v_financial_year_end, v_prev_financial_year_end + INTERVAL '1 year');
v_prev_financial_year_end := v_financial_year_end - INTERVAL '1 year';
-- Return calculated data
RETURN QUERY
SELECT
-- Current Year Income
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 4
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Current Year Expense
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 5
and tr.transaction_source_type = 2
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Pending Dues
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Pending Payments
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Previous Year Income
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 4
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Previous Year Expense
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 5
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Previous Year Pending Dues
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Previous Year Pending Payments
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
AND je.is_deleted = FALSE
), 0), 2);
END;
$function$
-- Function: get_employee_opening_balance
CREATE OR REPLACE FUNCTION public.get_employee_opening_balance(p_employee_id uuid, p_finyear_id integer)
RETURNS TABLE(transaction_date timestamp without time zone, employee_id uuid, employee_name text, description text, debit numeric, credit numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_year_text text;
BEGIN
-- Fetch the year text for the given finance year id
SELECT year INTO v_year_text FROM public.finance_year WHERE id = p_finyear_id;
IF v_year_text IS NULL THEN
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
END IF;
RETURN QUERY
SELECT
th.transaction_date,
th.employee_id,
(e.first_name || ' ' || e.last_name) AS employee_name,
th.description,
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) AS debit,
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) AS credit
FROM public.transaction_headers th
JOIN public.employees e ON th.employee_id = e.id
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.chart_of_accounts sp ON sp.name ILIKE '%Salary Payable%'
WHERE th.employee_id = p_employee_id
AND je.account_id = sp.id
AND th.transaction_source_type = 13
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
ORDER BY th.transaction_date;
END;
$function$
-- Function: get_expense_overview
CREATE OR REPLACE FUNCTION public.get_expense_overview(p_company_id uuid, p_period_type integer, p_finance_id integer)
RETURNS TABLE(period text, year numeric, account_name text, total_expense numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_financial_year_start date;
v_financial_year_end date;
v_finance_year text;
v_organization_id uuid;
-- Define constants for period types
CONST_MONTHLY CONSTANT INTEGER := 1;
CONST_QUARTERLY CONSTANT INTEGER := 2;
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
CONST_YEARLY CONSTANT INTEGER := 4;
BEGIN
-- Step 1: Fetch the organization ID based on the company ID
SELECT organization_id INTO v_organization_id
FROM public.companies
WHERE id = p_company_id;
-- Step 2: Get financial year boundaries
SELECT fy.start_date, fy.end_date, fy.year INTO v_financial_year_start, v_financial_year_end, v_finance_year
FROM public.finance_year fy
WHERE id = p_finance_id;
-- Check the period type and execute the appropriate logic
IF p_period_type = CONST_MONTHLY THEN
RETURN QUERY
WITH RECURSIVE AccountHierarchy AS (
-- Base case: Select root accounts (expense accounts)
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
coa.parent_account_id
FROM
public.chart_of_accounts coa
WHERE
coa.account_type_id = 5 -- Expense accounts
AND coa.organization_id = v_organization_id
UNION ALL
-- Recursive case: Select child accounts under the root accounts
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
coa.parent_account_id
FROM
public.chart_of_accounts coa
INNER JOIN
AccountHierarchy ah ON coa.parent_account_id = ah.account_id
),
months AS (
SELECT
TO_CHAR(months.m, 'Mon') AS period,
EXTRACT(YEAR FROM months.m) AS extracted_year,
EXTRACT(MONTH FROM months.m) AS month_num
FROM
generate_series(v_financial_year_start, v_financial_year_end, '1 month'::interval) AS months(m)
)
SELECT
months.period,
months.extracted_year AS year,
ah.account_name,
COALESCE(SUM(je.amount), 0) AS total_expense
FROM
months
INNER JOIN journal_entries je
ON EXTRACT(MONTH FROM je.transaction_date) = months.month_num
AND EXTRACT(YEAR FROM je.transaction_date) = months.extracted_year
INNER JOIN transaction_headers tr
ON je.transaction_id = tr.id
AND tr.company_id = p_company_id -- Ensure filtering by company_id
INNER JOIN AccountHierarchy ah
ON je.account_id = ah.account_id
WHERE
je.entry_type = 'D' -- Only debits are considered expenses
AND je.is_deleted = FALSE -- Filter non-deleted entries
GROUP BY
months.period, months.extracted_year, months.month_num, ah.account_name
ORDER BY
months.extracted_year, months.month_num, ah.account_name;
ELSIF p_period_type = CONST_QUARTERLY THEN
RETURN QUERY
WITH RECURSIVE AccountHierarchy AS (
-- Base case: Select root accounts (expense accounts)
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
coa.parent_account_id
FROM
public.chart_of_accounts coa
WHERE
coa.account_type_id = 5 -- Expense accounts
AND coa.organization_id = v_organization_id
UNION ALL
-- Recursive case: Select child accounts under the root accounts
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
coa.parent_account_id
FROM
public.chart_of_accounts coa
INNER JOIN
AccountHierarchy ah ON coa.parent_account_id = ah.account_id
),
quarters AS (
SELECT
'Q1' AS period, 1 AS quarter_num, EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
v_financial_year_start AS start_date, v_financial_year_start + interval '2 month' AS end_date
UNION ALL
SELECT
'Q2' AS period, 2 AS quarter_num, EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
v_financial_year_start + interval '3 month' AS start_date, v_financial_year_start + interval '5 month' AS end_date
UNION ALL
SELECT
'Q3' AS period, 3 AS quarter_num, EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
v_financial_year_start + interval '6 month' AS start_date, v_financial_year_start + interval '8 month' AS end_date
UNION ALL
SELECT
'Q4' AS period, 4 AS quarter_num, EXTRACT(YEAR FROM v_financial_year_start) + 1 AS extracted_year,
v_financial_year_start + interval '9 month' AS start_date, v_financial_year_end AS end_date
)
SELECT
quarters.period,
quarters.extracted_year AS year,
ah.account_name,
COALESCE(SUM(je.amount), 0) AS total_expense
FROM
quarters
LEFT JOIN journal_entries je
ON je.transaction_date BETWEEN quarters.start_date AND quarters.end_date
LEFT JOIN transaction_headers tr
ON je.transaction_id = tr.id
AND tr.company_id = p_company_id -- Ensure filtering by company_id
INNER JOIN AccountHierarchy ah
ON je.account_id = ah.account_id
WHERE
je.entry_type = 'D' -- Only debits are considered expenses
AND je.is_deleted = FALSE -- Filter non-deleted entries
GROUP BY
quarters.period, quarters.extracted_year, quarters.quarter_num, ah.account_name
ORDER BY
quarters.extracted_year, quarters.quarter_num, ah.account_name;
-- Repeat similar adjustments for Half-Yearly and Yearly periods.
ELSIF p_period_type = CONST_HALF_YEARLY THEN
RETURN QUERY
WITH RECURSIVE AccountHierarchy AS (
-- Base case: Select root accounts (expense accounts)
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
coa.parent_account_id
FROM
public.chart_of_accounts coa
WHERE
coa.account_type_id = 5 -- Expense accounts
AND coa.organization_id = v_organization_id
UNION ALL
-- Recursive case: Select child accounts under the root accounts
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
coa.parent_account_id
FROM
public.chart_of_accounts coa
INNER JOIN
AccountHierarchy ah ON coa.parent_account_id = ah.account_id
),
half_years AS (
SELECT
'H1' AS period,
1 AS half_year_num,
EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
v_financial_year_start AS start_date,
v_financial_year_start + interval '5 month' AS end_date
UNION ALL
SELECT
'H2' AS period,
2 AS half_year_num,
EXTRACT(YEAR FROM v_financial_year_start) AS extracted_year,
v_financial_year_start + interval '6 month' AS start_date,
v_financial_year_end AS end_date
)
SELECT
half_years.period,
half_years.extracted_year AS year,
ah.account_name,
COALESCE(SUM(je.amount), 0) AS total_expense
FROM
half_years
LEFT JOIN journal_entries je
ON je.transaction_date BETWEEN half_years.start_date AND half_years.end_date
LEFT JOIN transaction_headers tr
ON je.transaction_id = tr.id
AND tr.company_id = p_company_id -- Ensure filtering by company_id
INNER JOIN AccountHierarchy ah
ON je.account_id = ah.account_id
WHERE
je.entry_type = 'D' -- Only debits are considered expenses
AND je.is_deleted = FALSE -- Filter non-deleted entries
GROUP BY
half_years.period, half_years.extracted_year, half_years.half_year_num, ah.account_name
ORDER BY
half_years.extracted_year, half_years.half_year_num, ah.account_name;
ELSIF p_period_type = CONST_YEARLY THEN
RETURN QUERY
WITH RECURSIVE AccountHierarchy AS (
-- Base case: Select root accounts (expense accounts)
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
coa.parent_account_id
FROM
public.chart_of_accounts coa
WHERE
coa.account_type_id = 5 -- Expense accounts
AND coa.organization_id = v_organization_id
UNION ALL
-- Recursive case: Select child accounts under the root accounts
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
coa.parent_account_id
FROM
public.chart_of_accounts coa
INNER JOIN
AccountHierarchy ah ON coa.parent_account_id = ah.account_id
)
SELECT
'Year' AS period,
EXTRACT(YEAR FROM v_financial_year_start) AS year,
ah.account_name,
COALESCE(SUM(je.amount), 0) AS total_expense
FROM
journal_entries je
LEFT JOIN transaction_headers tr
ON je.transaction_id = tr.id
AND tr.company_id = p_company_id -- Ensure filtering by company_id
INNER JOIN AccountHierarchy ah
ON je.account_id = ah.account_id
WHERE
je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.entry_type = 'D' -- Only debits are considered expenses
AND je.is_deleted = FALSE -- Filter non-deleted entries
GROUP BY
year, ah.account_name
ORDER BY
year, ah.account_name;
END IF;
END;
$function$
-- Function: get_full_account_hierarchy_overview
CREATE OR REPLACE FUNCTION public.get_full_account_hierarchy_overview(p_company_id uuid, p_fin_year_id integer)
RETURNS TABLE(account_id uuid, account_name text, parent_account_id uuid, hierarchy_path text[], financial_year integer, apr numeric, may numeric, jun numeric, jul numeric, aug numeric, sep numeric, oct numeric, nov numeric, "dec" numeric, jan numeric, feb numeric, mar numeric, total_sum numeric, difference numeric)
LANGUAGE plpgsql
AS $function$
#variable_conflict use_column
DECLARE
v_current_year INTEGER := p_fin_year_id;
v_previous_year INTEGER := p_fin_year_id - 1;
v_organization_id UUID;
BEGIN
SELECT c.organization_id INTO v_organization_id
FROM public.companies c
WHERE c.id = p_company_id;
RETURN QUERY
WITH RECURSIVE account_with_path AS (
-- This is a much safer way to build the path
SELECT
ca.id,
ca.name,
ca.parent_account_id,
-- If parent is missing or is self, start a new path. Otherwise, build the path.
COALESCE(p.name, '') || '/' || ca.name AS path_str
FROM chart_of_accounts ca
LEFT JOIN chart_of_accounts p ON ca.parent_account_id = p.id AND ca.parent_account_id != ca.id
WHERE ca.organization_id = v_organization_id
),
financial_years AS (
SELECT id AS fin_year_id, start_date, end_date
FROM finance_year
WHERE id IN (v_previous_year, v_current_year)
),
aggregated_data AS (
SELECT
awp.id, awp.name, awp.parent_account_id,
-- Convert the path string to an array for AG Grid
string_to_array(ltrim(awp.path_str, '/'), '/') AS hierarchy_path,
fy.fin_year_id,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 4 THEN 1 ELSE 0 END), 0) AS apr,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 5 THEN 1 ELSE 0 END), 0) AS may,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 6 THEN 1 ELSE 0 END), 0) AS jun,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 7 THEN 1 ELSE 0 END), 0) AS jul,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 8 THEN 1 ELSE 0 END), 0) AS aug,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 9 THEN 1 ELSE 0 END), 0) AS sep,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 10 THEN 1 ELSE 0 END), 0) AS oct,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 11 THEN 1 ELSE 0 END), 0) AS nov,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 12 THEN 1 ELSE 0 END), 0) AS "dec",
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 1 THEN 1 ELSE 0 END), 0) AS jan,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 2 THEN 1 ELSE 0 END), 0) AS feb,
COALESCE(SUM(je.amount * CASE WHEN EXTRACT(MONTH FROM je.transaction_date) = 3 THEN 1 ELSE 0 END), 0) AS mar,
COALESCE(SUM(je.amount), 0) AS total_sum
FROM account_with_path awp
CROSS JOIN financial_years fy
LEFT JOIN journal_entries je ON je.account_id = awp.id AND je.transaction_date BETWEEN fy.start_date AND fy.end_date
LEFT JOIN transaction_headers th ON th.id = je.transaction_id AND th.company_id = p_company_id
WHERE (th.transaction_source_type IS NULL OR th.transaction_source_type != 13)
GROUP BY awp.id, awp.name, awp.parent_account_id, awp.path_str, fy.fin_year_id
)
SELECT
a1.id, a1.name, a1.parent_account_id, a1.hierarchy_path, a1.fin_year_id,
a1.apr, a1.may, a1.jun, a1.jul, a1.aug, a1.sep, a1.oct, a1.nov, a1."dec",
a1.jan, a1.feb, a1.mar,
a1.total_sum,
(COALESCE(a1.total_sum, 0) - COALESCE(a2.total_sum, 0)) AS difference
FROM aggregated_data a1
LEFT JOIN aggregated_data a2 ON a1.id = a2.id AND a1.fin_year_id = v_current_year AND a2.fin_year_id = v_previous_year
ORDER BY a1.hierarchy_path, a1.fin_year_id;
END;
$function$
-- Function: get_n_years_expenses
CREATE OR REPLACE FUNCTION public.get_n_years_expenses(p_company_id uuid, p_fin_year_ids integer[])
RETURNS TABLE(account_id uuid, account_name text, finance_year_id integer, finance_year_label text, month_number integer, month_label text, amount numeric)
LANGUAGE plpgsql
STABLE PARALLEL SAFE
AS $function$
DECLARE
months_arr TEXT[] := ARRAY['Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar'];
yrec RECORD;
m INT;
month_start DATE;
month_end DATE;
BEGIN
FOR yrec IN
SELECT
fy.id,
fy.start_date,
fy.end_date,
CONCAT(EXTRACT(YEAR FROM fy.start_date), '-', RIGHT(TO_CHAR(fy.end_date, 'YYYY'), 2)) AS label
FROM public.finance_year fy
WHERE fy.id = ANY(p_fin_year_ids)
ORDER BY fy.start_date
LOOP
FOR m IN 1..12 LOOP
month_start := yrec.start_date + ((m - 1) * INTERVAL '1 month');
month_end := (month_start + INTERVAL '1 month' - INTERVAL '1 day')::DATE;
RETURN QUERY
WITH RECURSIVE AccountHierarchy AS (
SELECT coa.id, coa.name, coa.parent_account_id
FROM public.chart_of_accounts coa
WHERE coa.account_type_id = 5
UNION ALL
SELECT coa.id, coa.name, coa.parent_account_id
FROM public.chart_of_accounts coa
INNER JOIN AccountHierarchy ah ON coa.parent_account_id = ah.id
)
SELECT
ah.id AS account_id,
ah.name AS account_name,
yrec.id AS finance_year_id,
yrec.label AS finance_year_label,
m AS month_number,
months_arr[m] AS month_label,
COALESCE(SUM(je.amount), 0) AS amount
FROM AccountHierarchy ah
LEFT JOIN public.journal_entries je
ON je.account_id = ah.id
AND je.transaction_date BETWEEN month_start AND month_end
AND je.is_deleted = FALSE
LEFT JOIN public.transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
GROUP BY ah.id, ah.name;
END LOOP;
END LOOP;
END;
$function$
-- Function: get_vendor_ledger
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_vendor_ledger(p_vendor_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_id uuid, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
LANGUAGE plpgsql
AS $function$
DECLARE
v_sundary_creditors_account_id UUID;
v_vendor_advance_account_id UUID;
BEGIN
SELECT
accounts_payable_account_id,
vendor_advance_account_id
INTO
v_sundary_creditors_account_id,
v_vendor_advance_account_id
FROM public.organization_accounts
WHERE organization_id = p_organization_id
LIMIT 1;
RETURN QUERY
WITH all_transactions AS (
SELECT
th.transaction_date::date,
coa.id AS account_id,
coa.name AS account_name,
CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END AS debit,
CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END AS credit,
th.document_number,
th.document_id,
th.transaction_source_type,
tst.sort_order,
th.id AS transaction_id,
je.id AS journal_entry_id
FROM public.transaction_headers th
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.transaction_source_types tst ON th.transaction_source_type = tst.id
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.company_id = p_company_id
AND th.vendor_id = p_vendor_id
AND je.account_id IN (v_sundary_creditors_account_id, v_vendor_advance_account_id)
AND th.transaction_date BETWEEN p_start_date AND p_end_date
AND th.is_deleted = false
AND je.is_deleted = false
)
SELECT
at.transaction_date,
at.account_id,
at.account_name,
at.debit,
at.credit,
SUM(at.debit - at.credit) OVER (
ORDER BY
at.transaction_date,
at.sort_order,
at.transaction_id,
at.journal_entry_id
) AS balance,
at.transaction_source_type,
at.document_number,
at.document_id
FROM all_transactions AS at
ORDER BY
at.transaction_date,
at.sort_order,
at.transaction_id,
at.journal_entry_id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_vendor_ledger(p_vendor_id uuid, p_company_id uuid, p_organization_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(transaction_date date, account_name text, debit numeric, credit numeric, balance numeric, transaction_source_type integer, document_number text, document_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
WITH vendor_transactions AS (
SELECT
th.transaction_date::date AS transaction_date,
coa.name AS account_name,
CASE
WHEN je.entry_type = 'D' THEN je.amount
ELSE 0
END AS debit,
CASE
WHEN je.entry_type = 'C' THEN je.amount
ELSE 0
END AS credit,
th.company_id,
th.vendor_id,
th.transaction_source_type,
th.document_number,
th.document_id
FROM
public.transaction_headers th
INNER JOIN
public.journal_entries je ON th.id = je.transaction_id
INNER JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
WHERE
th.vendor_id = p_vendor_id
AND th.company_id = p_company_id
AND coa.organization_id = p_organization_id
AND th.is_deleted = false
AND je.is_deleted = false
AND th.transaction_date >= p_start_date
AND th.transaction_date <= p_end_date
)
SELECT
vt.transaction_date,
vt.account_name,
vt.debit,
vt.credit,
SUM(vt.debit - vt.credit) OVER (PARTITION BY vt.vendor_id ORDER BY vt.transaction_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance,
vt.transaction_source_type,
vt.document_number,
vt.document_id
FROM
vendor_transactions vt
ORDER BY
vt.transaction_date;
END;
$function$
-- Function: get_year_wise_opening_balance
CREATE OR REPLACE FUNCTION public.get_year_wise_opening_balance(p_finyear_id integer)
RETURNS TABLE(id bigint, transaction_date timestamp without time zone, entity_id uuid, entity_name text, description text, debit numeric, credit numeric, entity_type text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_year_text text;
BEGIN
-- Fetch the year text for the given finance year id
SELECT fy.year INTO v_year_text FROM public.finance_year fy WHERE fy.id = p_finyear_id;
IF v_year_text IS NULL THEN
RAISE EXCEPTION 'Finance year with id % not found!', p_finyear_id;
END IF;
RETURN QUERY
-- VENDORS
SELECT
th.id,
th.transaction_date,
th.vendor_id as entity_id,
v.name::text as entity_name,
th.description,
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) as debit,
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) as credit,
'Vendor' as entity_type
FROM public.transaction_headers th
JOIN public.vendors v ON th.vendor_id = v.id
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.chart_of_accounts sc ON je.account_id = sc.id AND sc.name ILIKE '%Sundry Creditors%'
WHERE th.transaction_source_type = 13
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
UNION ALL
-- CUSTOMERS
SELECT
th.id,
th.transaction_date,
th.customer_id as entity_id,
c.name::text as entity_name,
th.description,
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) as debit,
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) as credit,
'Customer' as entity_type
FROM public.transaction_headers th
JOIN public.customers c ON th.customer_id = c.id
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.chart_of_accounts sd ON je.account_id = sd.id AND sd.name ILIKE '%Sundry Debtors%'
WHERE th.transaction_source_type = 13
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
UNION ALL
-- EMPLOYEES
SELECT
th.id,
th.transaction_date,
th.employee_id as entity_id,
e.first_name::text as entity_name,
th.description,
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) as debit,
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) as credit,
'Employee' as entity_type
FROM public.transaction_headers th
JOIN public.employees e ON th.employee_id = e.id
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.chart_of_accounts sp ON je.account_id = sp.id AND sp.name ILIKE '%Salary Payable%'
WHERE th.transaction_source_type = 13
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
UNION ALL
-- COA ACCOUNTS (other than above)
SELECT
th.id,
th.transaction_date,
th.document_id as entity_id,
coa.name::text as entity_name,
th.description,
COALESCE(CASE WHEN je.entry_type = 'D' THEN je.amount END, 0) as debit,
COALESCE(CASE WHEN je.entry_type = 'C' THEN je.amount END, 0) as credit,
'COA' as entity_type
FROM public.transaction_headers th
JOIN public.journal_entries je ON th.id = je.transaction_id
JOIN public.chart_of_accounts coa ON th.document_id = coa.id AND je.account_id = th.document_id
WHERE th.transaction_source_type = 13
AND th.description LIKE 'Opening Balance for %' || v_year_text || '%'
ORDER BY entity_name;
END;
$function$
-- Function: assign_user_default_permissions
CREATE OR REPLACE FUNCTION public.assign_user_default_permissions(p_user_id uuid, p_role_id integer, p_organization_id uuid, p_created_by uuid DEFAULT NULL::uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_permission_count INT;
v_existing_organization_user INT;
v_existing_user_role INT;
BEGIN
RAISE NOTICE 'Assigning default permissions for User=% to Role=%', p_user_id, p_role_id;
-- 1️⃣ Ensure user exists in organization_users
SELECT COUNT(*) INTO v_existing_organization_user
FROM organization_users ou
WHERE ou.user_id = p_user_id
AND ou.organization_id = p_organization_id
AND ou.is_deleted = FALSE;
IF v_existing_organization_user = 0 THEN
INSERT INTO organization_users (
id, user_id, effective_start_date, created_on_utc,
created_by, organization_id, is_deleted
)
VALUES (
uuid_generate_v4(),
p_user_id,
NOW(),
NOW(),
COALESCE(p_created_by, p_user_id),
p_organization_id,
FALSE
);
RAISE NOTICE '✅ Added user to organization_users';
END IF;
-- 2️⃣ Ensure user has matching role in user_roles
SELECT COUNT(*) INTO v_existing_user_role
FROM user_roles ur
WHERE ur.user_id = p_user_id
AND ur.role_id = p_role_id
AND ur.organization_id = p_organization_id
AND ur.is_deleted = FALSE;
IF v_existing_user_role = 0 THEN
INSERT INTO user_roles (
user_id, role_id, created_on_utc, created_by,
is_deleted, start_date, organization_id
)
VALUES (
p_user_id,
p_role_id,
NOW(),
COALESCE(p_created_by, p_user_id),
FALSE,
NOW(),
p_organization_id
);
RAISE NOTICE '✅ Added role assignment for user';
END IF;
-- 3️⃣ Assign permissions from role_permissions (NOT template mappings)
INSERT INTO user_permissions (
user_id,
permission_id,
organization_id,
created_on_utc,
created_by,
is_deleted
)
SELECT
p_user_id,
rp.permission_id,
p_organization_id,
NOW(),
COALESCE(p_created_by, p_user_id),
FALSE
FROM role_permissions rp
WHERE rp.role_id = p_role_id
AND NOT EXISTS (
SELECT 1
FROM user_permissions up
WHERE up.user_id = p_user_id
AND up.permission_id = rp.permission_id
AND up.organization_id = p_organization_id
AND up.is_deleted = FALSE
);
GET DIAGNOSTICS v_permission_count = ROW_COUNT;
RAISE NOTICE '✅ Assigned % permissions to User=% for Role=%',
v_permission_count, p_user_id, p_role_id;
EXCEPTION
WHEN OTHERS THEN
RAISE WARNING '⚠️ Error assigning permissions for user %: %', p_user_id, SQLERRM;
END;
$function$
-- Function: upsert_role_permissions
CREATE OR REPLACE FUNCTION public.upsert_role_permissions(p_role_id integer, p_permission_ids uuid[], p_user_id uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
BEGIN
-- 1️⃣ Reactivate existing soft-deleted permissions that are now in the list
UPDATE public.role_permissions
SET is_deleted = FALSE,
deleted_on_utc = NULL,
modified_on_utc = NOW(),
modified_by = p_user_id
WHERE role_id = p_role_id
AND permission_id = ANY(p_permission_ids)
AND is_deleted = TRUE;
-- 2️⃣ Insert missing (new) permissions
INSERT INTO public.role_permissions (
role_id,
permission_id,
created_on_utc,
created_by,
is_deleted
)
SELECT
p_role_id,
pid,
NOW(),
p_user_id,
FALSE
FROM UNNEST(p_permission_ids) AS pid
WHERE NOT EXISTS (
SELECT 1
FROM public.role_permissions rp
WHERE rp.role_id = p_role_id
AND rp.permission_id = pid
);
-- 3️⃣ Soft delete permissions not in the provided list
UPDATE public.role_permissions
SET is_deleted = TRUE,
deleted_on_utc = NOW(),
modified_on_utc = NOW(),
modified_by = p_user_id
WHERE role_id = p_role_id
AND permission_id NOT IN (SELECT unnest(p_permission_ids))
AND is_deleted = FALSE;
END;
$function$
-- Function: get_outstanding_invoices_by_customer_id
CREATE OR REPLACE FUNCTION public.get_outstanding_invoices_by_customer_id(p_company_id uuid, p_customer_id uuid)
RETURNS TABLE(customer_name text, coa_name text, amount numeric, due_date date, aging_bucket text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
c.name::text AS customer_name,
coa.name AS coa_name,
je.amount,
(th.transaction_date + INTERVAL '30 days')::date AS due_date,
CASE
WHEN (th.transaction_date + INTERVAL '30 days') <= CURRENT_DATE
THEN 'Current'
WHEN (th.transaction_date + INTERVAL '30 days') BETWEEN CURRENT_DATE - INTERVAL '30 days' AND CURRENT_DATE
THEN '1-30 Days Past Due'
WHEN (th.transaction_date + INTERVAL '60 days') BETWEEN CURRENT_DATE - INTERVAL '60 days' AND CURRENT_DATE - INTERVAL '31 days'
THEN '31-60 Days Past Due'
ELSE 'Over 60 Days Past Due'
END AS aging_bucket
FROM
public.journal_entries je
JOIN
public.transaction_headers th ON je.transaction_id = th.id
JOIN
public.chart_of_accounts coa ON je.account_id = coa.id
JOIN
public.customers c ON th.customer_id = c.id
WHERE
coa.account_type_id = (SELECT id FROM public.account_types WHERE name = 'Accounts Receivable')
AND je.is_deleted = false
AND th.company_id = p_company_id
AND th.customer_id = p_customer_id
AND (th.transaction_date + INTERVAL '30 days') <= CURRENT_DATE
ORDER BY
(th.transaction_date + INTERVAL '30 days')::date;
END;
$function$
-- Function: get_outstanding_invoices_by_customer
CREATE OR REPLACE FUNCTION public.get_outstanding_invoices_by_customer(p_company_id uuid, p_customer_id uuid, p_start_date date DEFAULT NULL::date, p_end_date date DEFAULT NULL::date)
RETURNS TABLE(customer_name text, coa_name text, amount numeric, due_date date, aging_bucket text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := COALESCE(p_start_date, CURRENT_DATE - INTERVAL '365 days');
v_end_date date := COALESCE(p_end_date, CURRENT_DATE);
BEGIN
RETURN QUERY
SELECT
c.name::text AS customer_name,
coa.name AS coa_name,
je.amount,
(th.transaction_date + INTERVAL '30 days')::date AS due_date,
/* Aging bucket calculation */
CASE
WHEN (th.transaction_date + INTERVAL '30 days') <= CURRENT_DATE
THEN 'Current'
WHEN (th.transaction_date + INTERVAL '30 days') BETWEEN CURRENT_DATE - INTERVAL '30 days' AND CURRENT_DATE
THEN '1-30 Days Past Due'
WHEN (th.transaction_date + INTERVAL '60 days') BETWEEN CURRENT_DATE - INTERVAL '60 days' AND CURRENT_DATE - INTERVAL '31 days'
THEN '31-60 Days Past Due'
ELSE 'Over 60 Days Past Due'
END AS aging_bucket
FROM public.journal_entries je
JOIN public.transaction_headers th ON je.transaction_id = th.id
JOIN public.chart_of_accounts coa ON je.account_id = coa.id
JOIN public.customers c ON th.customer_id = c.id
WHERE
th.company_id = p_company_id
AND th.customer_id = p_customer_id
AND je.is_deleted = false
AND coa.account_type_id = (
SELECT id FROM public.account_types WHERE name = 'Accounts Receivable'
)
/* Overdue invoices only (due_date <= today) */
AND (th.transaction_date + INTERVAL '30 days')::date <= CURRENT_DATE
/* Date range filtering */
AND th.transaction_date::date BETWEEN v_start_date AND v_end_date
ORDER BY (th.transaction_date + INTERVAL '30 days')::date;
END;
$function$
-- Function: postgres_fdw_handler
CREATE OR REPLACE FUNCTION public.postgres_fdw_handler()
RETURNS fdw_handler
LANGUAGE c
STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_handler$function$
-- Function: postgres_fdw_validator
CREATE OR REPLACE FUNCTION public.postgres_fdw_validator(text[], oid)
RETURNS void
LANGUAGE c
STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_validator$function$
-- Function: postgres_fdw_disconnect
CREATE OR REPLACE FUNCTION public.postgres_fdw_disconnect(text)
RETURNS boolean
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_disconnect$function$
-- Function: postgres_fdw_disconnect_all
CREATE OR REPLACE FUNCTION public.postgres_fdw_disconnect_all()
RETURNS boolean
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_disconnect_all$function$
-- Function: postgres_fdw_get_connections
CREATE OR REPLACE FUNCTION public.postgres_fdw_get_connections(check_conn boolean DEFAULT false, OUT server_name text, OUT user_name text, OUT valid boolean, OUT used_in_xact boolean, OUT closed boolean, OUT remote_backend_pid integer)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_get_connections_1_2$function$
-- Function: notify_user_permission_change
CREATE OR REPLACE FUNCTION public.notify_user_permission_change()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
v_user_id uuid;
v_org_id uuid;
payload json;
BEGIN
-- Handle DELETE separately (NEW is null)
IF TG_OP = 'DELETE' THEN
v_user_id := OLD.user_id;
v_org_id := OLD.organization_id;
ELSE
v_user_id := NEW.user_id;
v_org_id := NEW.organization_id;
END IF;
payload := json_build_object(
'entity', 'user_permissions',
'operation', TG_OP,
'userId', v_user_id,
'organizationId', v_org_id,
'occurredAt', now()
);
PERFORM pg_notify('permission_user_changed', payload::text);
RETURN NULL;
END;
$function$
-- Function: get_system_user_id
CREATE OR REPLACE FUNCTION public.get_system_user_id()
RETURNS uuid
LANGUAGE plpgsql
AS $function$
DECLARE
v_user_id uuid;
BEGIN
SELECT id
INTO v_user_id
FROM public.users
WHERE first_name = 'System'
AND is_deleted = false
LIMIT 1;
RETURN COALESCE(
v_user_id,
'dd4f94f2-0f79-4748-b94b-bf935e3944c7'::uuid
);
END;
$function$
-- Function: upsert_user_roles
CREATE OR REPLACE FUNCTION public.upsert_user_roles(p_user_id uuid, p_role_ids integer[], p_organization_id uuid, p_created_by uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
BEGIN
INSERT INTO public.user_roles
(
user_id,
role_id,
organization_id,
created_on_utc,
created_by,
is_deleted,
start_date,
end_date
)
SELECT
p_user_id,
role_id,
p_organization_id,
NOW(),
p_created_by,
false,
NOW(),
NOW() + INTERVAL '1 year'
FROM UNNEST(p_role_ids) AS role_id
ON CONFLICT (user_id, role_id, organization_id)
DO UPDATE
SET
is_deleted = false,
start_date = NOW(),
end_date = NOW() + INTERVAL '1 year',
modified_on_utc = NOW(),
modified_by = p_created_by;
END;
$function$
-- Function: get_bank_statements
CREATE OR REPLACE FUNCTION public.get_bank_statements(p_organization_id uuid, p_finyear_id integer, p_date_from timestamp without time zone DEFAULT NULL::timestamp without time zone, p_date_to timestamp without time zone DEFAULT NULL::timestamp without time zone)
RETURNS TABLE(id integer, organization_id uuid, bank_id uuid, bank_name text, txn_date timestamp without time zone, cheque_number character varying, description text, value_date timestamp without time zone, branch_code character varying, debit_amount numeric, credit_amount numeric, balance numeric, has_reconciled boolean, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, deleted_on_utc timestamp without time zone, is_deleted boolean, rec_status_id integer, rec_status text, rec_confidence_score numeric, rec_strategy_name text, rec_matched_party_name text, rec_party_id uuid, rec_party_type text, rec_reviewed_by uuid, rec_reviewed_on_utc timestamp without time zone, rec_matched_amount numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
bs.id,
bs.organization_id,
bs.bank_id,
coa.name AS bank_name,
bs.txn_date,
bs.cheque_number,
bs.description,
bs.value_date,
bs.branch_code,
bs.debit_amount,
bs.credit_amount,
bs.balance,
bs.has_reconciled,
bs.created_by,
bs.created_on_utc,
bs.modified_by,
bs.modified_on_utc,
bs.deleted_on_utc,
bs.is_deleted,
/* -----------------------------------------
Final reconciliation status
----------------------------------------- */
COALESCE(br.reconciliation_status_id, 0) AS rec_status_id,
CASE
WHEN br.reconciliation_status_id IS NOT NULL THEN
COALESCE(rs.status::text, 'unmatched')
WHEN brs.confidence_score >= 70.99 THEN
'matched'
WHEN brs.status = 'pending' THEN
'pending'
ELSE
'unmatched'
END AS rec_status,
/* -----------------------------------------
Latest reconciliation suggestion
----------------------------------------- */
COALESCE(brs.confidence_score, 0) AS rec_confidence_score,
COALESCE(brs.strategy_name::text, 'No Match Found') AS rec_strategy_name,
brs.matched_party_name::text AS rec_matched_party_name,
brs.party_id AS rec_party_id,
brs.party_type::text AS rec_party_type,
brs.reviewed_by AS rec_reviewed_by,
brs.reviewed_on_utc AS rec_reviewed_on_utc,
COALESCE(brs.matched_amount, 0) AS rec_matched_amount
FROM public.bank_statements bs
/* Bank account (COA) */
INNER JOIN public.chart_of_accounts coa
ON coa.id = bs.bank_id
AND coa.account_type_id = 9
AND coa.is_deleted = FALSE
/* Financial year scope */
INNER JOIN public.finance_year fy
ON fy.id = p_finyear_id
/* Latest AI / manual staging record */
LEFT JOIN LATERAL (
SELECT brs_inner.*
FROM public.bank_reconciliation_stagings brs_inner
WHERE brs_inner.organization_id = bs.organization_id
AND brs_inner.bank_statement_id = bs.id
AND brs_inner.is_deleted = FALSE
ORDER BY brs_inner.created_on_utc DESC
LIMIT 1
) brs ON TRUE
/* Finalized reconciliation */
LEFT JOIN public.bank_reconciliations br
ON br.bank_statement_id = bs.id
AND br.organization_id = bs.organization_id
/* Reconciliation status lookup */
LEFT JOIN public.reconciliation_statuses rs
ON rs.id = br.reconciliation_status_id
WHERE bs.organization_id = p_organization_id
AND bs.txn_date BETWEEN fy.start_date AND fy.end_date
AND (p_date_from IS NULL OR bs.txn_date >= p_date_from)
AND (p_date_to IS NULL OR bs.txn_date <= p_date_to)
AND bs.is_deleted = FALSE
ORDER BY bs.txn_date, bs.id;
END;
$function$
-- Function: upsert_user_permissions_from_user_roles
CREATE OR REPLACE FUNCTION public.upsert_user_permissions_from_user_roles(p_user_id uuid, p_organization_id uuid, p_role_ids integer[], p_created_by uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
BEGIN
/*
* STEP 1: Insert or revive permissions coming from roles
*/
INSERT INTO public.user_permissions (
user_id,
organization_id,
permission_id,
created_on_utc,
created_by,
is_deleted,
is_explicit
)
SELECT DISTINCT
p_user_id,
p_organization_id,
rp.permission_id,
now(),
p_created_by,
false,
false -- role-derived
FROM public.role_permissions rp
WHERE rp.role_id = ANY (p_role_ids)
AND rp.is_deleted = false
ON CONFLICT (user_id, organization_id, permission_id)
DO UPDATE
SET
is_deleted = false,
deleted_on_utc = NULL,
modified_on_utc = now(),
modified_by = p_created_by,
is_explicit = false;
/*
* STEP 2: Soft-delete role-derived permissions
* that are no longer present in current roles
*/
UPDATE public.user_permissions up
SET
is_deleted = true,
deleted_on_utc = now(),
modified_on_utc = now(),
modified_by = p_created_by
WHERE up.user_id = p_user_id
AND up.organization_id = p_organization_id
AND up.is_explicit = false
AND up.is_deleted = false
AND up.permission_id NOT IN (
SELECT DISTINCT rp.permission_id
FROM public.role_permissions rp
WHERE rp.role_id = ANY (p_role_ids)
AND rp.is_deleted = false
);
END;
$function$
-- Function: sync_user_permissions_from_roles
CREATE OR REPLACE FUNCTION public.sync_user_permissions_from_roles(p_user_id uuid DEFAULT NULL::uuid, p_organization_id uuid DEFAULT NULL::uuid, p_triggered_by text DEFAULT 'manual'::text, p_notes text DEFAULT NULL::text)
RETURNS uuid
LANGUAGE plpgsql
AS $function$
DECLARE
-- Sync run tracking
v_sync_run_id uuid := gen_random_uuid();
-- Loop variables
v_user_id uuid;
v_org_id uuid;
v_role_id int;
v_permission_id uuid;
-- For UPSERT result
v_user_permission_id int;
BEGIN
/*
============================================================
START SYNC RUN
============================================================
*/
INSERT INTO public.user_permission_sync_runs (
id,
started_on_utc,
triggered_by,
notes
)
VALUES (
v_sync_run_id,
NOW(),
p_triggered_by,
p_notes
);
/*
============================================================
MAIN SYNC LOOP
- Filtered by optional user / organization
============================================================
*/
FOR v_user_id, v_org_id IN
SELECT DISTINCT
ur.user_id,
ur.organization_id
FROM public.user_roles ur
WHERE ur.is_deleted = false
AND (p_user_id IS NULL OR ur.user_id = p_user_id)
AND (p_organization_id IS NULL OR ur.organization_id = p_organization_id)
LOOP
/*
--------------------------------------------------------
LOOP ROLES FOR USER + ORGANIZATION
--------------------------------------------------------
*/
FOR v_role_id IN
SELECT ur.role_id
FROM public.user_roles ur
WHERE ur.user_id = v_user_id
AND ur.organization_id = v_org_id
AND ur.is_deleted = false
LOOP
/*
----------------------------------------------------
LOOP PERMISSIONS FOR ROLE
----------------------------------------------------
*/
FOR v_permission_id IN
SELECT rp.permission_id
FROM public.role_permissions rp
WHERE rp.role_id = v_role_id
AND rp.is_deleted = false
LOOP
/*
------------------------------------------------
UPSERT USER PERMISSION (SAFE)
------------------------------------------------
*/
INSERT INTO public.user_permissions (
user_id,
permission_id,
organization_id,
created_on_utc,
created_by,
is_deleted,
modified_on_utc,
modified_by,
deleted_on_utc
)
VALUES (
v_user_id,
v_permission_id,
v_org_id,
NOW(),
v_user_id,
false,
NULL,
NULL,
NULL
)
ON CONFLICT (user_id, organization_id, permission_id)
DO UPDATE
SET
is_deleted = false,
deleted_on_utc = NULL,
modified_on_utc = NOW(),
modified_by = v_user_id
WHERE public.user_permissions.is_deleted = true
RETURNING id
INTO v_user_permission_id;
/*
------------------------------------------------
BACKUP / DELTA SNAPSHOT
- Only when INSERT or REVIVE happened
------------------------------------------------
*/
IF FOUND THEN
INSERT INTO public.user_permission_sync_deltas (
id,
sync_run_id,
user_permission_id,
user_id,
organization_id,
permission_id,
action,
created_on_utc
)
VALUES (
gen_random_uuid(),
v_sync_run_id,
v_user_permission_id,
v_user_id,
v_org_id,
v_permission_id,
'UPSERT',
NOW()
);
END IF;
END LOOP; -- permissions
END LOOP; -- roles
END LOOP; -- users + organizations
/*
============================================================
COMPLETE SYNC RUN
============================================================
*/
UPDATE public.user_permission_sync_runs
SET completed_on_utc = NOW()
WHERE id = v_sync_run_id;
RETURN v_sync_run_id;
END;
$function$
-- Function: insert_multiple_transactions_and_journal_entries
-- SOURCE
CREATE OR REPLACE FUNCTION public.insert_multiple_transactions_and_journal_entries(p_transactions_data jsonb)
RETURNS TABLE(transaction_id bigint, document_id uuid, reference text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_transaction_id BIGINT;
v_transaction_record JSONB;
v_journal_entry JSONB;
v_narration_id BIGINT;
v_reference TEXT;
v_is_reversed BOOLEAN;
BEGIN
-- Loop through each transaction in the input array
FOR v_transaction_record IN SELECT * FROM jsonb_array_elements(p_transactions_data)
LOOP
-- ✅ Extract is_reversed (default false if null)
v_is_reversed := COALESCE((v_transaction_record->>'is_reversal')::boolean, false);
-- Insert into transaction_headers
INSERT INTO public.transaction_headers (
company_id,
customer_id,
vendor_id,
employee_id,
transaction_date,
transaction_source_type,
status_id,
document_id,
document_number,
description,
created_by,
created_on_utc,
is_reversed -- ✅ NEW COLUMN
)
VALUES(
(v_transaction_record->>'company_id')::uuid,
NULLIF((v_transaction_record->>'customer_id')::uuid, '00000000-0000-0000-0000-000000000000'),
NULLIF((v_transaction_record->>'vendor_id')::uuid, '00000000-0000-0000-0000-000000000000'),
NULLIF((v_transaction_record->>'employee_id')::uuid, '00000000-0000-0000-0000-000000000000'),
(v_transaction_record->>'transaction_date')::date,
(v_transaction_record->>'transaction_source_type')::integer,
(v_transaction_record->>'status_id')::integer,
(v_transaction_record->>'document_id')::uuid,
v_transaction_record->>'document_number',
v_transaction_record->>'description',
(v_transaction_record->>'user_id')::uuid,
CURRENT_TIMESTAMP,
v_is_reversed -- ✅ INSERT VALUE
)
RETURNING id INTO v_transaction_id;
-------------------------------------------------------------------
-- 2️⃣ If reversal → mark original transaction as reversed
-------------------------------------------------------------------
IF v_is_reversed = true THEN
UPDATE public.transaction_headers th
SET
is_reversed = true,
modified_on_utc = CURRENT_TIMESTAMP
WHERE th.company_id = (v_transaction_record->>'company_id')::uuid
AND th.document_id = (v_transaction_record->>'document_id')::uuid
AND th.transaction_source_type = (v_transaction_record->>'transaction_source_type')::integer
AND th.id <> v_transaction_id
AND th.is_reversed = false;
END IF;
-- ✅ Insert into payment_references if reference exists and not empty
v_reference := NULLIF(TRIM(v_transaction_record->>'reference'), '');
IF v_reference IS NOT NULL THEN
INSERT INTO public.payment_references (
transaction_id,
reference
)
VALUES (
v_transaction_id,
v_reference
);
END IF;
-- Loop through each journal entry associated with the transaction
FOR v_journal_entry IN SELECT * FROM jsonb_array_elements(v_transaction_record->'journal_entries')
LOOP
-- Insert narration first
INSERT INTO public.journal_narrations (
transaction_date,
description_template_id,
dynamic_data,
created_by,
created_on_utc
)
VALUES(
(v_journal_entry->>'transaction_date')::timestamp,
(v_journal_entry->>'description_template_id')::integer,
COALESCE((v_journal_entry->>'dynamic_data'), ''),
(v_transaction_record->>'user_id')::uuid,
CURRENT_TIMESTAMP
)
RETURNING id INTO v_narration_id;
-- Insert journal entry
INSERT INTO public.journal_entries (
transaction_id,
account_id,
transaction_date,
amount,
entry_type,
entry_source_id,
journal_narration_id
)
VALUES(
v_transaction_id,
(v_journal_entry->>'account_id')::uuid,
(v_journal_entry->>'transaction_date')::date,
(v_journal_entry->>'amount')::numeric,
(v_journal_entry->>'entry_type')::char,
(v_journal_entry->>'entry_source_id')::integer,
v_narration_id
);
END LOOP;
-- Return transaction ID, document ID, and reference for this transaction
RETURN QUERY SELECT v_transaction_id, (v_transaction_record->>'document_id')::uuid, v_reference;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Error occurred: %', SQLERRM;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.insert_multiple_transactions_and_journal_entries(p_transactions_data jsonb)
RETURNS TABLE(transaction_id bigint, document_id uuid)
LANGUAGE plpgsql
AS $function$
DECLARE
v_transaction_id BIGINT;
v_transaction_record JSONB;
v_journal_entry JSONB;
BEGIN
-- Loop through each transaction in the input array
FOR v_transaction_record IN SELECT * FROM jsonb_array_elements(p_transactions_data)
LOOP
-- Insert into transaction_headers
INSERT INTO public.transaction_headers (
company_id,
customer_id,
vendor_id,
employee_id,
transaction_date,
transaction_source_type,
status_id,
document_id,
document_number,
description,
created_by,
created_on_utc
)
VALUES(
(v_transaction_record->>'company_id')::uuid,
NULLIF((v_transaction_record->>'customer_id')::uuid, '00000000-0000-0000-0000-000000000000'),
NULLIF((v_transaction_record->>'vendor_id')::uuid, '00000000-0000-0000-0000-000000000000'),
NULLIF((v_transaction_record->>'employee_id')::uuid, '00000000-0000-0000-0000-000000000000'),
(v_transaction_record->>'transaction_date')::date,
(v_transaction_record->>'transaction_source_type')::integer,
(v_transaction_record->>'status_id')::integer,
(v_transaction_record->>'document_id')::uuid,
v_transaction_record->>'document_number',
v_transaction_record->>'description',
(v_transaction_record->>'user_id')::uuid,
CURRENT_TIMESTAMP
)
RETURNING id INTO v_transaction_id;
-- Loop through each journal entry associated with the transaction
FOR v_journal_entry IN SELECT * FROM jsonb_array_elements(v_transaction_record->'journal_entries')
LOOP
INSERT INTO public.journal_entries (
transaction_id,
account_id,
transaction_date,
amount,
entry_type,
description_template_id,
dynamic_data,
entry_source_id
)
VALUES(
v_transaction_id,
(v_journal_entry->>'account_id')::uuid,
(v_journal_entry->>'transaction_date')::date,
(v_journal_entry->>'amount')::numeric,
(v_journal_entry->>'entry_type')::char,
(v_journal_entry->>'description_template_id')::integer,
(v_journal_entry->>'dynamic_data')::jsonb,
(v_journal_entry->>'entry_source_id')::integer
);
END LOOP;
-- Return transaction ID and document ID for this transaction
RETURN QUERY SELECT v_transaction_id, (v_transaction_record->>'document_id')::uuid;
END LOOP;
EXCEPTION
-- Catch any error and raise an exception
WHEN OTHERS THEN
RAISE EXCEPTION 'Error occurred: %', SQLERRM;
END;
$function$
-- Function: get_dashboard_totals_with_previous_year_comparison
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_dashboard_totals_with_previous_year_comparison(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(income_total numeric, expense_total numeric, pending_dues_total numeric, pending_payments_total numeric, prev_income_total numeric, prev_expense_total numeric, prev_pending_dues_total numeric, prev_pending_payments_total numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_organization_id UUID;
v_start_date DATE;
v_end_date DATE;
v_prev_start DATE;
v_prev_end DATE;
BEGIN
-- Get org
SELECT organization_id INTO v_organization_id
FROM public.companies
WHERE id = p_company_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Invalid company ID: %', p_company_id;
END IF;
/* ---------------------------------------------------------
Rolling 12 Months
--------------------------------------------------------- */
v_start_date := (date_trunc('month', CURRENT_DATE) - interval '11 months')::date;
v_end_date := (date_trunc('month', CURRENT_DATE) + interval '1 month')::date;
/* ---------------------------------------------------------
Previous Rolling 12 Months
--------------------------------------------------------- */
v_prev_start := v_start_date - interval '1 year';
v_prev_end := v_end_date - interval '1 year';
RETURN QUERY
SELECT
/* ================= CURRENT ================= */
-- Income
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 4
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date >= v_start_date
AND je.transaction_date < v_end_date
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND COALESCE(tr.is_reversed, FALSE) = FALSE
), 0), 2),
-- Expense
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 5
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date >= v_start_date
AND je.transaction_date < v_end_date
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND COALESCE(tr.is_reversed, FALSE) = FALSE
), 0), 2),
-- Pending Dues
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date >= v_start_date
AND je.transaction_date < v_end_date
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND COALESCE(tr.is_reversed, FALSE) = FALSE
), 0), 2),
-- Pending Payments
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date >= v_start_date
AND je.transaction_date < v_end_date
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND COALESCE(tr.is_reversed, FALSE) = FALSE
), 0), 2),
/* ================= PREVIOUS ROLLING ================= */
-- Previous Income
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 4
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date >= v_prev_start
AND je.transaction_date < v_prev_end
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND COALESCE(tr.is_reversed, FALSE) = FALSE
), 0), 2),
-- Previous Expense
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 5
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date >= v_prev_start
AND je.transaction_date < v_prev_end
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND COALESCE(tr.is_reversed, FALSE) = FALSE
), 0), 2),
-- Previous Pending Dues
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date >= v_prev_start
AND je.transaction_date < v_prev_end
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND COALESCE(tr.is_reversed, FALSE) = FALSE
), 0), 2),
-- Previous Pending Payments
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date >= v_prev_start
AND je.transaction_date < v_prev_end
AND COALESCE(je.is_deleted, FALSE) = FALSE
AND COALESCE(tr.is_reversed, FALSE) = FALSE
), 0), 2);
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_dashboard_totals_with_previous_year_comparison(p_company_id uuid, p_finance_year_id integer)
RETURNS TABLE(income_total numeric, expense_total numeric, pending_dues_total numeric, pending_payments_total numeric, prev_income_total numeric, prev_expense_total numeric, prev_pending_dues_total numeric, prev_pending_payments_total numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_organization_id UUID;
v_financial_year_start DATE;
v_financial_year_end DATE;
v_prev_financial_year_start DATE;
v_prev_financial_year_end DATE;
v_finance_year INTEGER;
v_today DATE := CURRENT_DATE;
BEGIN
-- Fetch organization ID
SELECT organization_id INTO v_organization_id
FROM public.companies
WHERE id = p_company_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Invalid company ID: %', p_company_id;
END IF;
-- Get financial year integer from 'YYYY-YY'
SELECT LEFT(year, 4)::INTEGER INTO v_finance_year
FROM public.finance_year
WHERE id = p_finance_year_id
LIMIT 1;
IF NOT FOUND THEN
RAISE EXCEPTION 'Invalid finance year ID: %', p_finance_year_id;
END IF;
-- Define financial year range
v_financial_year_start := MAKE_DATE(v_finance_year, 4, 1);
v_financial_year_end := MAKE_DATE(v_finance_year + 1, 3, 31);
-- Define previous financial year range
v_prev_financial_year_start := MAKE_DATE(v_finance_year - 1, 4, 1);
v_prev_financial_year_end := MAKE_DATE(v_finance_year, 3, 31);
-- Adjust financial year ends if the year is ongoing
IF v_today < v_financial_year_end THEN
v_financial_year_end := v_today;
END IF;
IF v_today < v_prev_financial_year_end THEN
v_prev_financial_year_end := v_today - INTERVAL '1 year';
END IF;
-- Align both end dates to same day/month by limiting to shorter period
v_financial_year_end := LEAST(v_financial_year_end, v_prev_financial_year_end + INTERVAL '1 year');
v_prev_financial_year_end := v_financial_year_end - INTERVAL '1 year';
-- Return calculated data
RETURN QUERY
SELECT
-- Current Year Income
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 4
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Current Year Expense
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 5
AND tr.transaction_source_type = 2
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Pending Dues
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Pending Payments
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Previous Year Income
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 4
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Previous Year Expense
ROUND(COALESCE((
SELECT SUM(je.amount)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN account_categories ac ON at.account_category_id = ac.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ac.id = 5
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Previous Year Pending Dues
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
AND je.is_deleted = FALSE
), 0), 2),
-- Previous Year Pending Payments
ROUND(COALESCE((
SELECT SUM(CASE WHEN je.entry_type = 'C' THEN je.amount ELSE 0 END) -
SUM(CASE WHEN je.entry_type = 'D' THEN je.amount ELSE 0 END)
FROM journal_entries je
JOIN chart_of_accounts ca ON je.account_id = ca.id
JOIN account_types at ON ca.account_type_id = at.id
JOIN transaction_headers tr ON je.transaction_id = tr.id
WHERE ca.account_type_id IN (SELECT id FROM get_account_type_hierarchy(2))
AND tr.company_id = p_company_id
AND ca.organization_id = v_organization_id
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
AND je.is_deleted = FALSE
), 0), 2);
END;
$function$
-- Function: get_all_gate_passes
CREATE OR REPLACE FUNCTION public.get_all_gate_passes(p_company_id uuid, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
RETURNS TABLE(id uuid, gate_pass_no character varying, gate_pass_type character varying, warehouse_id uuid, warehouse_name character varying, party_type character varying, party_id uuid, vehicle_no character varying, driver_name character varying, purpose character varying, gate_out_time timestamp without time zone, gate_in_time timestamp without time zone, status character varying, remarks text, created_on_utc timestamp without time zone, created_by character varying, modified_by character varying)
LANGUAGE plpgsql
AS $function$
BEGIN
-- 🔥 DEBUG LOGS
RAISE NOTICE 'CompanyId: %', p_company_id;
RAISE NOTICE 'StartDate: %', p_start_date;
RAISE NOTICE 'EndDate: %', p_end_date;
RETURN QUERY
SELECT
gh.id,
gh.gate_pass_no,
gh.gate_pass_type,
gh.warehouse_id,
w.name AS warehouse_name,
gh.party_type,
gh.party_id,
gh.vehicle_no,
gh.driver_name,
gh.purpose,
gh.gate_out_time,
gh.gate_in_time,
gh.status,
gh.remarks,
gh.created_on_utc,
CAST(TRIM(CONCAT(cu.first_name, ' ', cu.last_name)) AS varchar),
CAST(TRIM(CONCAT(mu.first_name, ' ', mu.last_name)) AS varchar)
FROM public.gate_pass_headers gh
LEFT JOIN public.warehouses w
ON gh.warehouse_id = w.id
AND w.is_deleted = false
LEFT JOIN public.users cu
ON cu.id = gh.created_by
LEFT JOIN public.users mu
ON mu.id = gh.modified_by
WHERE gh.company_id = p_company_id
AND gh.is_deleted = false
AND (p_start_date IS NULL OR gh.created_on_utc >= p_start_date)
AND (p_end_date IS NULL OR gh.created_on_utc <= p_end_date)
ORDER BY gh.created_on_utc DESC;
END;
$function$
-- Procedure: create_organization
CREATE OR REPLACE PROCEDURE public.create_organization(IN p_id uuid, IN p_name text, IN p_description text, IN p_phone_number text, IN p_email text, IN p_address_line text, IN p_gstin text, IN p_tag_line text, IN p_city_id uuid, IN p_short_name text, IN p_pan text, IN p_tan text, IN p_created_by uuid, IN p_org_type_id integer)
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Step 1: Insert into organizations table
INSERT INTO public.organizations (
id,
name,
description,
phone_number,
email,
address_line,
gstin,
tag_line,
city_id,
short_name,
pan,
tan,
outstanding_limit,
is_non_work,
interest_percentage,
created_on_utc,
created_by,
organization_type_id
) VALUES (
p_id, -- Organization ID
p_name, -- Organization Name
p_description, -- Description
p_phone_number, -- Phone Number
p_email, -- Email
p_address_line, -- Address Line
p_gstin, -- GSTIN
p_tag_line, -- Tag Line
p_city_id, -- City ID
p_short_name, -- Short Name
p_pan, -- PAN
p_tan, -- TAN
0, -- Outstanding Limit
false, -- Is Non-Work
0, -- Interest Percentage
NOW(), -- Created On Timestamp
p_created_by, -- Created By
p_org_type_id
);
-- Notify the successful creation of the organization
RAISE NOTICE 'Organization created successfully with ID: %', p_id;
END;
$procedure$
-- Procedure: initilize_organization_test
CREATE OR REPLACE PROCEDURE public.initilize_organization_test(IN p_id uuid, IN p_name text, IN p_company_guids text, IN p_company_names text, IN p_user_id uuid, IN p_user_first_name text, IN p_user_last_name text, IN p_phone_number character varying, IN p_email character varying, IN p_gstin text, IN p_description text, IN p_tag_line text, IN p_short_name text, IN p_pan text, IN p_tan text, IN p_address_line1 text, IN p_address_line2 text, IN p_country_id uuid, IN p_state_id uuid, IN p_city_name text, IN p_zip_code text, IN p_created_by uuid, IN p_default_company_id uuid, IN p_org_type_id integer)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_city_id uuid;
v_address_id uuid; -- Generate a unique address ID
v_user_id uuid; -- Generate a unique user ID
v_permission_ids uuid[];
v_company_names text[]; -- Array to hold parsed company names
v_company_guids uuid[]; -- Array to hold parsed company GUIDs
v_company_name text; -- Variable for iterating through company names
v_company_guid uuid; -- Variable for iterating through company GUIDs
v_user_company_id uuid;
-- Constants for default organization and company IDs
DEFAULT_ORGANIZATION_ID CONSTANT uuid := '68929d37-b647-4d7b-8c91-7dfe2396c93b';
--DEFAULT_COMPANY_ID CONSTANT uuid := '048000ba-e62c-474b-b5eb-fe629a4dc863';
ADMIN_PERMISSION CONSTANT text := 'Dhanman.Admin';
BEGIN
-- Parse company names and GUIDs into arrays
v_company_names := string_to_array(p_company_names, ',');
v_company_guids := string_to_array(p_company_guids, ',');
-- Print arrays
RAISE NOTICE 'Company Names: %', v_company_names;
RAISE NOTICE 'Company GUIDs: %', v_company_guids;
-- Get city_id
v_city_id := get_city_id(p_city_name, p_zip_code, p_state_id, p_created_by);
-- Get address_id
v_address_id := get_address_id(p_country_id, p_state_id , v_city_id , p_address_line1, p_zip_code, p_created_by, p_address_line2);
v_user_company_id := v_company_guids[1]; -- Pick the first company ID
-- Print the generated address_id and fetched state_id and country_id
RAISE NOTICE 'Generated address_id: %, City ID: %, Country ID: %', v_address_id, v_city_id, p_country_id;
-- Check if organization exists by any unique identifier
IF NOT EXISTS (SELECT 1 FROM organizations WHERE id = p_id) THEN
-- Only create organization if no matching record exists
CALL public.create_organization(
p_id,
p_name,
p_description,
p_phone_number,
p_email,
p_address_line1,
p_gstin,
p_tag_line,
v_city_id,
p_short_name,
p_pan,
p_tan,
p_created_by,
p_org_type_id
);
RAISE NOTICE 'Organization ID % successfully inserted.', p_id;
ELSE
RAISE NOTICE 'Organization with ID % already exists. Skipping organization creation.', p_id;
END IF;
-- Fetch the permission_id for 'Dhanman.Admin'
SELECT array_agg(id) INTO v_permission_ids
FROM permissions
WHERE name = ADMIN_PERMISSION;
-- Assign all default users and default permissions as admin
CALL public.assign_default_users_to_organization(p_id, v_permission_ids, p_created_by);
-- Iterate through the company names and GUIDs
FOR i IN 1..array_length(v_company_names, 1) LOOP
v_company_name := v_company_names[i];
v_company_guid := v_company_guids[i];
-- Call initialize_company for each company
CALL public.initialize_company(
v_company_guid,
p_id,
v_company_name,
p_description,
p_gstin,
p_pan,
p_tan,
'INR',
p_short_name,
p_tag_line,
p_name,
p_phone_number,
p_email,
p_created_by,
p_default_company_id
);
RAISE NOTICE 'Company % initialized with GUID %.', v_company_name, v_company_guid;
END LOOP;
-- Create and get user_id
v_user_id := create_user(
p_user_id,
v_company_guids[1],
p_email,
p_phone_number,
p_user_first_name,
p_user_last_name,
v_address_id,
p_created_by
);
-- Insert into organization_users table to link the generated user to the organization
CALL public.insert_organization_user(v_user_id, p_created_by, p_id);
-- 7. Map User → All Companies
FOR i IN 1..array_length(v_company_guids, 1) LOOP
INSERT INTO company_users (
id,
company_id,
user_id,
effective_start_date,
effective_end_date,
created_on_utc,
created_by
)
VALUES (
gen_random_uuid(),
v_company_guids[i],
v_user_id,
NOW(),
DATE '9999-12-31',
NOW(),
p_created_by
)
ON CONFLICT DO NOTHING;
END LOOP;
-- assign permission to default user
CALL public.insert_user_permission(
v_user_id ,
p_id ,
v_permission_ids,
p_created_by
);
-- Call initialize_org_coa to copy chart_of_accounts from the old organization to the new organization
CALL public.initialize_org_coa(
DEFAULT_ORGANIZATION_ID, -- Old organization ID
p_id, -- New organization ID
p_company_guids, -- New Company Id
p_created_by
);
-- Print a confirmation after the chart_of_accounts is copied
RAISE NOTICE 'Chart of accounts successfully copied from organization % to %.', DEFAULT_ORGANIZATION_ID, p_id;
END;
$procedure$
-- Procedure: initialize_company
CREATE OR REPLACE PROCEDURE public.initialize_company(IN p_id uuid, IN p_organization_id uuid, IN p_name text, IN p_description text, IN p_gstin text, IN p_pan text, IN p_tan text, IN p_currency text, IN p_short_name text, IN p_tag_line text, IN p_proprietor_name text, IN p_phone_number character varying, IN p_email character varying, IN p_created_by uuid, IN p_default_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_contact_id uuid := gen_random_uuid();
v_company_contact_id uuid := gen_random_uuid();
v_company_exists boolean;
v_contact_exists boolean;
v_company_contact_exists boolean;
DEFAULT_USER_ID CONSTANT uuid := 'e3a9d60f-fec9-4c4f-9b0b-bd85ef454a44';
BEGIN
-- ============================
-- 1. Create Company
-- ============================
SELECT EXISTS (
SELECT 1 FROM public.companies
WHERE id = p_id
OR (organization_id = p_organization_id AND name = p_name)
) INTO v_company_exists;
IF NOT v_company_exists THEN
INSERT INTO public.companies (
id, organization_id, name, description,
gstin, pan, tan, currency,
short_name, tag_line, proprietor_name,
outstanding_limit, is_non_work, is_apartment,
interest_percentage, created_on_utc, created_by
) VALUES (
p_id, p_organization_id, p_name, p_description,
p_gstin, p_pan, p_tan, p_currency,
p_short_name, p_tag_line, p_proprietor_name,
0, false, true,
0, NOW(), p_created_by
);
RAISE NOTICE 'Company % created.', p_name;
END IF;
-- ============================
-- 2. Create / Fetch Contact
-- ============================
SELECT EXISTS (
SELECT 1 FROM contacts
WHERE email = p_email OR phone_number = p_phone_number
) INTO v_contact_exists;
IF NOT v_contact_exists THEN
INSERT INTO contacts (
id, salutation, first_name, last_name,
email, phone_number, mobile_number,
is_primary, created_on_utc, created_by
) VALUES (
v_contact_id, 'Mr.', p_proprietor_name, NULL,
p_email, p_phone_number, p_phone_number,
true, NOW(), p_created_by
);
ELSE
SELECT id INTO v_contact_id
FROM contacts
WHERE email = p_email OR phone_number = p_phone_number
LIMIT 1;
END IF;
-- ============================
-- 3. Link Company & Contact
-- ============================
SELECT EXISTS (
SELECT 1 FROM company_contacts
WHERE company_id = p_id AND contact_id = v_contact_id
) INTO v_company_contact_exists;
IF NOT v_company_contact_exists THEN
INSERT INTO company_contacts (
id, company_id, contact_id,
created_on_utc, created_by
) VALUES (
v_company_contact_id, p_id, v_contact_id,
NOW(), p_created_by
);
END IF;
-- ============================
-- 4. Attach Default System User
-- ============================
INSERT INTO company_users (
id, company_id, user_id,
effective_start_date, effective_end_date,
created_on_utc, created_by
)
VALUES (
gen_random_uuid(), p_id, DEFAULT_USER_ID,
NOW(), DATE '9999-12-31',
NOW(), p_created_by
)
ON CONFLICT DO NOTHING;
-- ============================
-- 5. Initialize Finance & JV
-- ============================
IF NOT v_company_exists THEN
CALL public.insert_company_finance_years(p_id);
CALL public.initialize_journal_voucher_header_ids(
p_default_company_id,
p_id
);
END IF;
RAISE NOTICE 'Company initialization completed for %', p_name;
END;
$procedure$
-- Procedure: assign_default_users_to_organization
CREATE OR REPLACE PROCEDURE public.assign_default_users_to_organization(IN p_organization_id uuid, IN p_permission_ids uuid[], IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
loop_user_id UUID;
BEGIN
-- Insert default users into organization_users (only if they don't exist)
INSERT INTO public.organization_users (
id,
user_id,
effective_start_date,
effective_end_date,
created_on_utc,
created_by,
organization_id
)
SELECT
gen_random_uuid(),
d.user_id,
NOW(),
DATE '9999-12-31',
NOW(),
p_created_by,
p_organization_id
FROM
public.default_organization_users d
WHERE NOT EXISTS (
SELECT 1
FROM public.organization_users ou
WHERE ou.user_id = d.user_id
AND ou.organization_id = p_organization_id
);
RAISE NOTICE 'Added % default users to organization %',
(SELECT COUNT(*) FROM public.default_organization_users), p_organization_id;
-- Assign permissions for these users by calling insert_user_permission
FOR loop_user_id IN (SELECT d.user_id
FROM public.default_organization_users d)
LOOP
CALL public.insert_user_permission(
loop_user_id,
p_organization_id,
p_permission_ids,
p_created_by
);
END LOOP;
RAISE NOTICE 'Attempted to assign permission % to default users in organization %',
p_permission_ids,
p_organization_id;
END;
$procedure$
-- Procedure: hard_delete_organization
CREATE OR REPLACE PROCEDURE public.hard_delete_organization(IN p_organization_id uuid)
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Delete from user_permissions (cascading through organization and users)
DELETE FROM user_permissions
WHERE organization_id = p_organization_id;
-- Delete from organization_users (cascading through organization)
DELETE FROM organization_users
WHERE organization_id = p_organization_id;
-- Delete from company_finance_year (cascading through companies)
DELETE FROM company_finance_year
WHERE company_id IN (
SELECT id FROM companies WHERE organization_id = p_organization_id
);
-- Delete from company_users (cascading through companies)
DELETE FROM company_users
WHERE company_id IN (
SELECT id FROM companies WHERE organization_id = p_organization_id
);
-- Delete from company_contacts (cascading through companies)
DELETE FROM company_contacts
WHERE company_id IN (
SELECT id FROM companies WHERE organization_id = p_organization_id
);
-- Delete from contacts (cascading through companies)
DELETE FROM contacts
WHERE id IN (
SELECT contact_id FROM company_contacts
WHERE company_id IN (
SELECT id FROM companies WHERE organization_id = p_organization_id
)
);
--delete from company_preference
DELETE FROM public.company_preferences
WHERE company_id in (SELECT id from public.companies where organization_id = p_organization_id);
-- Delete from companies (cascading through organization)
DELETE FROM companies
WHERE organization_id = p_organization_id;
DELETE FROM organization_accounts
WHERE organization_id = p_organization_id;
-- Delete from chart_of_accounts (cascading through organization)
DELETE FROM chart_of_accounts
WHERE organization_id = p_organization_id;
-- Finally, delete the organization itself
DELETE FROM organizations
WHERE id = p_organization_id;
-- Optional: Log the deletion action
RAISE NOTICE 'Organization with ID % and all related data have been hard deleted.',
p_organization_id;
END;
$procedure$
-- Procedure: initilize_organization
-- SOURCE
CREATE OR REPLACE PROCEDURE public.initilize_organization(IN p_id uuid, IN p_name text, IN p_company_guids text, IN p_company_names text, IN p_user_id uuid, IN p_user_first_name text, IN p_user_last_name text, IN p_phone_number character varying, IN p_email character varying, IN p_gstin text, IN p_description text, IN p_tag_line text, IN p_short_name text, IN p_pan text, IN p_tan text, IN p_address_line1 text, IN p_address_line2 text, IN p_country_id uuid, IN p_state_id uuid, IN p_city_name text, IN p_zip_code text, IN p_created_by uuid, IN p_default_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_city_id uuid;
v_address_id uuid; -- Generate a unique address ID
v_user_id uuid; -- Generate a unique user ID
v_permission_ids uuid[];
v_company_names text[]; -- Array to hold parsed company names
v_company_guids uuid[]; -- Array to hold parsed company GUIDs
v_company_name text; -- Variable for iterating through company names
v_company_guid uuid; -- Variable for iterating through company GUIDs
v_user_company_id uuid;
-- Constants for default organization and company IDs
DEFAULT_ORGANIZATION_ID CONSTANT uuid := '68929d37-b647-4d7b-8c91-7dfe2396c93b';
--DEFAULT_COMPANY_ID CONSTANT uuid := '048000ba-e62c-474b-b5eb-fe629a4dc863';
ADMIN_PERMISSION CONSTANT text := 'Dhanman.Admin';
BEGIN
-- Parse company names and GUIDs into arrays
v_company_names := string_to_array(p_company_names, ',');
v_company_guids := string_to_array(p_company_guids, ',');
-- Print arrays
RAISE NOTICE 'Company Names: %', v_company_names;
RAISE NOTICE 'Company GUIDs: %', v_company_guids;
-- Get city_id
v_city_id := get_city_id(p_city_name, p_zip_code, p_state_id, p_created_by);
-- Get address_id
v_address_id := get_address_id(p_country_id, p_state_id , v_city_id , p_address_line1, p_zip_code, p_created_by, p_address_line2);
v_user_company_id := v_company_guids[1]; -- Pick the first company ID
-- Create and get user_id
v_user_id := create_user(p_user_id, v_user_company_id, p_email, p_phone_number, p_user_first_name, p_user_last_name, v_address_id, p_created_by);
RAISE NOTICE 'Generated User ID: %',v_user_company_id;
-- Print the generated address_id and fetched state_id and country_id
RAISE NOTICE 'Generated address_id: %, City ID: %, Country ID: %, User ID: %', v_address_id, v_city_id, p_country_id, v_user_id;
/* -- Check if organization exists by any unique identifier
IF NOT EXISTS (SELECT 1 FROM organizations WHERE id = p_id) THEN
-- Only create organization if no matching record exists
CALL public.create_organization(
p_id,
p_name,
p_description,
p_phone_number,
p_email,
p_address_line1,
p_gstin,
p_tag_line,
v_city_id,
p_short_name,
p_pan,
p_tan,
p_created_by
);
RAISE NOTICE 'Organization ID % successfully inserted.', p_id;
ELSE
RAISE NOTICE 'Organization with ID % already exists. Skipping organization creation.', p_id;
END IF;
-- Insert into organization_users table to link the generated user to the organization
CALL public.insert_organization_user(v_user_id, p_created_by, p_id);
-- Fetch the permission_id for 'Dhanman.Admin'
SELECT array_agg(id) INTO v_permission_ids
FROM permissions
WHERE name = ADMIN_PERMISSION;
CALL public.insert_user_permission(
v_user_id ,
p_id ,
v_permission_ids,
p_created_by
);
-- Assign all default users and default permissions as admin
CALL public.assign_default_users_to_organization(p_id, v_permission_ids, p_created_by);
-- Iterate through the company names and GUIDs
FOR i IN 1..array_length(v_company_names, 1) LOOP
v_company_name := v_company_names[i];
v_company_guid := v_company_guids[i];
-- Call initialize_company for each company
CALL public.initialize_company(
v_company_guid,
p_id,
v_company_name,
p_description,
p_gstin,
p_pan,
p_tan,
'INR',
p_short_name,
p_tag_line,
p_name,
p_phone_number,
p_email,
p_created_by,
v_user_id,
p_default_company_id
);
RAISE NOTICE 'Company % initialized with GUID %.', v_company_name, v_company_guid;
END LOOP;
-- Call initialize_org_coa to copy chart_of_accounts from the old organization to the new organization
CALL public.initialize_org_coa(
DEFAULT_ORGANIZATION_ID, -- Old organization ID
p_id, -- New organization ID
p_company_guids, -- New Company Id
p_created_by
);
-- Print a confirmation after the chart_of_accounts is copied
RAISE NOTICE 'Chart of accounts successfully copied from organization % to %.', DEFAULT_ORGANIZATION_ID, p_id;*/
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.initilize_organization(IN p_id uuid, IN p_name text, IN p_company_guids text, IN p_company_names text, IN p_user_id uuid, IN p_user_first_name text, IN p_user_last_name text, IN p_phone_number character varying, IN p_email character varying, IN p_gstin text, IN p_description text, IN p_tag_line text, IN p_short_name text, IN p_pan text, IN p_tan text, IN p_address_line1 text, IN p_address_line2 text, IN p_country_id uuid, IN p_state_id uuid, IN p_city_name text, IN p_zip_code text, IN p_created_by uuid, IN p_default_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_city_id uuid;
v_address_id uuid; -- Generate a unique address ID
v_user_id uuid; -- Generate a unique user ID
v_permission_id uuid;
v_company_names text[]; -- Array to hold parsed company names
v_company_guids uuid[]; -- Array to hold parsed company GUIDs
v_company_name text; -- Variable for iterating through company names
v_company_guid uuid; -- Variable for iterating through company GUIDs
v_user_company_id uuid;
-- Constants for default organization and company IDs
DEFAULT_ORGANIZATION_ID CONSTANT uuid := '68929d37-b647-4d7b-8c91-7dfe2396c93b';
--DEFAULT_COMPANY_ID CONSTANT uuid := '048000ba-e62c-474b-b5eb-fe629a4dc863';
ADMIN_PERMISSION CONSTANT text := 'Dhanman.Admin';
BEGIN
-- Parse company names and GUIDs into arrays
v_company_names := string_to_array(p_company_names, ',');
v_company_guids := string_to_array(p_company_guids, ',');
-- Get city_id
v_city_id := get_city_id(p_city_name, p_zip_code, p_state_id, p_created_by);
-- Get address_id
v_address_id := get_address_id(p_country_id, p_state_id , v_city_id , p_address_line1, p_zip_code, p_created_by, p_address_line2);
v_user_company_id := v_company_guids[1]; -- Pick the first company ID
-- Create and get user_id
v_user_id := create_user(p_user_id, v_user_company_id, p_email, p_phone_number, p_user_first_name, p_user_last_name, v_address_id, p_created_by);
RAISE NOTICE 'Generated User ID: %',v_user_company_id;
-- Print the generated address_id and fetched state_id and country_id
RAISE NOTICE 'Generated address_id: %, City ID: %, Country ID: %, User ID: %', v_address_id, v_city_id, p_country_id, v_user_id;
-- Check if organization exists by any unique identifier
IF NOT EXISTS (SELECT 1 FROM organizations WHERE id = p_id) THEN
-- Only create organization if no matching record exists
CALL public.create_organization(
p_id,
p_name,
p_description,
p_phone_number,
p_email,
p_address_line1,
p_gstin,
p_tag_line,
v_city_id,
p_short_name,
p_pan,
p_tan,
p_created_by
);
RAISE NOTICE 'Organization ID % successfully inserted.', p_id;
ELSE
RAISE NOTICE 'Organization with ID % already exists. Skipping organization creation.', p_id;
END IF;
-- Insert into organization_users table to link the generated user to the organization
CALL public.insert_organization_user(v_user_id, p_created_by, p_id);
-- Fetch the permission_id for 'Dhanman.Admin'
SELECT id INTO v_permission_id
FROM permissions
WHERE name = ADMIN_PERMISSION;
CALL public.insert_user_permission(
v_user_id ,
p_id ,
v_permission_id,
p_created_by
);
-- Assign all default users and default permissions as admin
CALL public.assign_default_users_to_organization(p_id, v_permission_id, p_created_by);
-- Iterate through the company names and GUIDs
FOR i IN 1..array_length(v_company_names, 1) LOOP
v_company_name := v_company_names[i];
v_company_guid := v_company_guids[i];
-- Call initialize_company for each company
CALL public.initialize_company(
v_company_guid,
p_id,
v_company_name,
p_description,
p_gstin,
p_pan,
p_tan,
'INR',
p_short_name,
p_tag_line,
p_name,
p_phone_number,
p_email,
p_created_by,
v_user_id,
p_default_company_id
);
RAISE NOTICE 'Company % initialized with GUID %.', v_company_name, v_company_guid;
END LOOP;
-- Call initialize_org_coa to copy chart_of_accounts from the old organization to the new organization
CALL public.initialize_org_coa(
DEFAULT_ORGANIZATION_ID, -- Old organization ID
p_id, -- New organization ID
p_company_guids, -- New Company Id
p_created_by
);
-- Print a confirmation after the chart_of_accounts is copied
RAISE NOTICE 'Chart of accounts successfully copied from organization % to %.', DEFAULT_ORGANIZATION_ID, p_id;
END;
$procedure$
-- Procedure: initialize_company
-- SOURCE
CREATE OR REPLACE PROCEDURE public.initialize_company(IN p_id uuid, IN p_organization_id uuid, IN p_name text, IN p_description text, IN p_gstin text, IN p_pan text, IN p_tan text, IN p_currency text, IN p_short_name text, IN p_tag_line text, IN p_proprietor_name text, IN p_phone_number character varying, IN p_email character varying, IN p_created_by uuid, IN p_default_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_contact_id uuid := gen_random_uuid();
v_company_contact_id uuid := gen_random_uuid();
v_company_exists boolean;
v_contact_exists boolean;
v_company_contact_exists boolean;
DEFAULT_USER_ID CONSTANT uuid := 'e3a9d60f-fec9-4c4f-9b0b-bd85ef454a44';
BEGIN
-- ============================
-- 1. Create Company
-- ============================
SELECT EXISTS (
SELECT 1 FROM public.companies
WHERE id = p_id
OR (organization_id = p_organization_id AND name = p_name)
) INTO v_company_exists;
IF NOT v_company_exists THEN
INSERT INTO public.companies (
id, organization_id, name, description,
gstin, pan, tan, currency,
short_name, tag_line, proprietor_name,
outstanding_limit, is_non_work, is_apartment,
interest_percentage, created_on_utc, created_by
) VALUES (
p_id, p_organization_id, p_name, p_description,
p_gstin, p_pan, p_tan, p_currency,
p_short_name, p_tag_line, p_proprietor_name,
0, false, true,
0, NOW(), p_created_by
);
RAISE NOTICE 'Company % created.', p_name;
END IF;
-- ============================
-- 2. Create / Fetch Contact
-- ============================
SELECT EXISTS (
SELECT 1 FROM contacts
WHERE email = p_email OR phone_number = p_phone_number
) INTO v_contact_exists;
IF NOT v_contact_exists THEN
INSERT INTO contacts (
id, salutation, first_name, last_name,
email, phone_number, mobile_number,
is_primary, created_on_utc, created_by
) VALUES (
v_contact_id, 'Mr.', p_proprietor_name, NULL,
p_email, p_phone_number, p_phone_number,
true, NOW(), p_created_by
);
ELSE
SELECT id INTO v_contact_id
FROM contacts
WHERE email = p_email OR phone_number = p_phone_number
LIMIT 1;
END IF;
-- ============================
-- 3. Link Company & Contact
-- ============================
SELECT EXISTS (
SELECT 1 FROM company_contacts
WHERE company_id = p_id AND contact_id = v_contact_id
) INTO v_company_contact_exists;
IF NOT v_company_contact_exists THEN
INSERT INTO company_contacts (
id, company_id, contact_id,
created_on_utc, created_by
) VALUES (
v_company_contact_id, p_id, v_contact_id,
NOW(), p_created_by
);
END IF;
-- ============================
-- 4. Attach Default System User
-- ============================
INSERT INTO company_users (
id, company_id, user_id,
effective_start_date, effective_end_date,
created_on_utc, created_by
)
VALUES (
gen_random_uuid(), p_id, DEFAULT_USER_ID,
NOW(), DATE '9999-12-31',
NOW(), p_created_by
)
ON CONFLICT DO NOTHING;
-- ============================
-- 5. Initialize Finance & JV
-- ============================
IF NOT v_company_exists THEN
CALL public.insert_company_finance_years(p_id);
CALL public.initialize_journal_voucher_header_ids(
p_default_company_id,
p_id
);
END IF;
RAISE NOTICE 'Company initialization completed for %', p_name;
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.initialize_company(IN p_id uuid, IN p_organization_id uuid, IN p_name text, IN p_description text, IN p_gstin text, IN p_pan text, IN p_tan text, IN p_currency text, IN p_short_name text, IN p_tag_line text, IN p_proprietor_name text, IN p_phone_number character varying, IN p_email character varying, IN p_created_by uuid, IN p_user_id uuid, IN p_default_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_contact_id uuid := gen_random_uuid();
v_company_contact_id uuid := gen_random_uuid();
v_finance_year_id int;
v_prev_finance_year_id INTEGER;
v_next_finance_year_id INTEGER;
DEFAULT_USER_ID CONSTANT uuid := '50fd5012-940b-4fec-ad0a-2ac900239c8b';
v_company_exists boolean;
v_contact_exists boolean;
v_company_contact_exists boolean;
v_user_company_exists boolean;
v_default_user_company_exists boolean;
BEGIN
-- Check if company already exists
SELECT EXISTS (
SELECT 1 FROM public.companies
WHERE id = p_id
OR (organization_id = p_organization_id AND name = p_name)
) INTO v_company_exists;
IF NOT v_company_exists THEN
-- Insert into companies table
INSERT INTO public.companies (
id, organization_id, name, description, gstin, pan, tan,
currency, short_name, tag_line, proprietor_name,
outstanding_limit, is_non_work, is_apartment, interest_percentage,
created_on_utc, created_by
) VALUES (
p_id, p_organization_id, p_name, p_description, p_gstin, p_pan, p_tan,
p_currency, p_short_name, p_tag_line, p_proprietor_name,
0, false, true, 0, NOW(), p_created_by
);
RAISE NOTICE 'Company % created successfully.', p_name;
ELSE
RAISE NOTICE 'Company with ID %, name %, or GSTIN/PAN % already exists. Skipping company creation.',
p_id, p_name, p_gstin;
END IF;
-- Check if contact already exists for this email/phone
SELECT EXISTS (
SELECT 1 FROM contacts
WHERE email = p_email OR phone_number = p_phone_number
) INTO v_contact_exists;
IF NOT v_contact_exists THEN
-- Insert contact
INSERT INTO contacts (
id, salutation, first_name, last_name, email,
phone_number, mobile_number, is_primary,
created_on_utc, created_by
) VALUES (
v_contact_id, 'Mr.', p_proprietor_name, NULL, p_email,
p_phone_number, p_phone_number, TRUE,
NOW(), p_created_by
);
RAISE NOTICE 'Contact created for company % with email %.', p_name, p_email;
ELSE
-- Get existing contact ID
SELECT id INTO v_contact_id
FROM contacts
WHERE email = p_email OR phone_number = p_phone_number
LIMIT 1;
RAISE NOTICE 'Contact with email % or phone % already exists. Using existing contact.', p_email, p_phone_number;
END IF;
-- Check if company-contact relationship exists
SELECT EXISTS (
SELECT 1 FROM company_contacts
WHERE company_id = p_id AND contact_id = v_contact_id
) INTO v_company_contact_exists;
IF NOT v_company_contact_exists THEN
-- Link company and contact
INSERT INTO company_contacts (
id, company_id, contact_id, created_on_utc, created_by
) VALUES (
v_company_contact_id, p_id, v_contact_id, NOW(), p_created_by
);
END IF;
-- Check if user-company relationship exists for main user
SELECT EXISTS (
SELECT 1 FROM company_users
WHERE company_id = p_id AND user_id = p_user_id
) INTO v_user_company_exists;
IF NOT v_user_company_exists THEN
-- Link main user to company
INSERT INTO company_users (
id, company_id, user_id, effective_start_date,
effective_end_date, created_on_utc, created_by
) VALUES (
gen_random_uuid(), p_id, p_user_id, NOW(),
DATE '9999-12-31', NOW(), p_created_by
);
END IF;
-- Check if user-company relationship exists for default user
SELECT EXISTS (
SELECT 1 FROM company_users
WHERE company_id = p_id AND user_id = DEFAULT_USER_ID
) INTO v_default_user_company_exists;
IF NOT v_default_user_company_exists THEN
-- Link default user to company
INSERT INTO company_users (
id, company_id, user_id, effective_start_date,
effective_end_date, created_on_utc, created_by
) VALUES (
gen_random_uuid(), p_id, DEFAULT_USER_ID, NOW(),
DATE '9999-12-31', NOW(), p_created_by
);
END IF;
-- Initialize finance years if company was newly created
IF NOT v_company_exists THEN
CALL public.insert_company_finance_years(p_id);
CALL public.initialize_journal_voucher_header_ids(p_default_company_id, p_id);
END IF;
RAISE NOTICE 'Company initialization completed for % (ID: %)', p_name, p_id;
END;
$procedure$
-- Procedure: insert_company_user
CREATE OR REPLACE PROCEDURE public.insert_company_user(IN p_user_id uuid, IN p_created_by uuid, IN p_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_existing_id uuid;
v_is_deleted boolean;
BEGIN
-- Check if user-company relationship exists (including soft-deleted)
SELECT id, is_deleted
INTO v_existing_id, v_is_deleted
FROM public.company_users
WHERE user_id = p_user_id
AND company_id = p_company_id
LIMIT 1;
-- If exists and active (not deleted), do nothing
IF FOUND AND v_is_deleted = false THEN
RAISE NOTICE 'User % already exists in company % (active), skipping insert.', p_user_id, p_company_id;
-- If exists but soft-deleted, reactivate the record
ELSIF FOUND AND v_is_deleted = true THEN
UPDATE public.company_users
SET is_deleted = false,
modified_on_utc = NOW(),
modified_by = p_created_by,
deleted_on_utc = NULL
WHERE id = v_existing_id;
RAISE NOTICE 'User % re-activated in company % (id=%)', p_user_id, p_company_id, v_existing_id;
-- If no record found, insert new
ELSE
INSERT INTO public.company_users (
id,
user_id,
company_id,
effective_start_date,
effective_end_date,
created_on_utc,
created_by,
is_deleted
) VALUES (
gen_random_uuid(),
p_user_id,
p_company_id,
NOW(),
DATE '9999-12-31',
NOW(),
p_created_by,
false
);
RAISE NOTICE 'Company-user relationship created for user % in company %', p_user_id, p_company_id;
END IF;
END;
$procedure$
-- Procedure: insert_into_manual_journal
-- SOURCE
CREATE OR REPLACE PROCEDURE public.insert_into_manual_journal(IN p_company_id uuid, IN p_journal_header_id uuid, IN p_date date, IN p_amount numeric, IN p_note text, IN p_description text, IN p_is_debit boolean, IN p_account_id uuid, IN p_created_by uuid, IN p_journal_details_data jsonb, IN p_transaction_header_data jsonb, IN journal_entries_data jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_voucher_number character varying;
v_voucher_detail JSONB;
v_transaction_record JSONB;
v_transaction_result RECORD;
v_transactions_data JSONB;
BEGIN
v_voucher_number := get_new_voucher_number(p_company_id, p_date);
RAISE NOTICE 'new v_voucher_number: %', v_voucher_number;
-- Insert into journal_voucher_headers
INSERT INTO public.journal_voucher_headers(
id,
company_id,
date,
amount,
note,
is_debit,
account_id,
voucher_number,
created_by,
created_on_utc,
is_deleted
)
VALUES (
p_journal_header_id,
p_company_id,
p_date,
p_amount,
p_note,
p_is_debit,
p_account_id,
v_voucher_number,
p_created_by,
CURRENT_TIMESTAMP,
FALSE
);
RAISE NOTICE 'Inserted into journal_voucher_headers: %', p_journal_header_id;
-- Insert into journal_voucher_details
FOR v_voucher_detail IN SELECT * FROM jsonb_array_elements(p_journal_details_data)
LOOP
INSERT INTO public.journal_voucher_details(
id,
journal_header_id,
credit_account_id,
debit_account_id,
amount,
tds_tcs,
narration,
created_by,
created_on_utc,
is_deleted
)
VALUES (
(v_voucher_detail->>'detail_id')::UUID,
p_journal_header_id,
(v_voucher_detail->>'credit_account_id')::UUID,
(v_voucher_detail->>'debit_account_id')::UUID,
(v_voucher_detail->>'amount')::NUMERIC,
v_voucher_detail->>'tds_tcs',
v_voucher_detail->>'narration',
p_created_by,
CURRENT_TIMESTAMP,
FALSE
);
RAISE NOTICE 'Inserted into journal_voucher_details';
END LOOP;
-- Prepare single transaction record matching insert_multiple_transactions_and_journal_entries input
v_transaction_record := jsonb_build_object(
'company_id', p_transaction_header_data->>'company_id',
'customer_id', p_transaction_header_data->>'customer_id',
'vendor_id', p_transaction_header_data->>'vendor_id',
'employee_id', p_transaction_header_data->>'employee_id',
'transaction_date', p_transaction_header_data->>'transaction_date',
'transaction_source_type', p_transaction_header_data->>'transaction_source_type',
'status_id', p_transaction_header_data->>'status_id',
'document_id', p_transaction_header_data->>'document_id',
'document_number', v_voucher_number,
'description', p_transaction_header_data->>'description',
'user_id', p_created_by,
'journal_entries', journal_entries_data
);
v_transactions_data := jsonb_build_array(v_transaction_record);
-- Call the batch function to insert into transaction_headers and journal_entries (and narration)
FOR v_transaction_result IN
SELECT * FROM public.insert_multiple_transactions_and_journal_entries(v_transactions_data)
LOOP
RAISE NOTICE 'Inserted transaction_id: %, document_id: %',
v_transaction_result.transaction_id, v_transaction_result.document_id;
-- You can use v_transaction_result.transaction_id if you need to reference it for other inserts
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Error occurred: %', SQLERRM;
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.insert_into_manual_journal(IN p_company_id uuid, IN p_journal_header_id uuid, IN p_date date, IN p_amount numeric, IN p_note text, IN p_description text, IN p_is_debit boolean, IN p_account_id uuid, IN p_created_by uuid, IN p_journal_details_data jsonb, IN p_transaction_header_data jsonb, IN journal_entries_data jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_voucher_number character varying;
v_voucher_detail JSONB;
v_transaction_id BIGINT;
v_journal_entry JSONB;
BEGIN
v_voucher_number := get_new_voucher_number(p_company_id,p_date);
RAISE NOTICE 'new v_voucher_number: %', v_voucher_number;
-- Insert into journal_voucher_headers
INSERT INTO public.journal_voucher_headers(
id,
company_id,
date,
amount,
note,
is_debit,
account_id,
voucher_number,
created_by,
created_on_utc,
is_deleted
)
VALUES (
p_journal_header_id,
p_company_id,
p_date,
p_amount,
p_note,
p_is_debit,
p_account_id,
v_voucher_number,
p_created_by,
CURRENT_TIMESTAMP,
FALSE
);
RAISE NOTICE 'Inserted into journal_voucher_headers: %', p_journal_header_id;
-- Loop through each journal entry and insert into journal_voucher_details
FOR v_voucher_detail IN SELECT * FROM jsonb_array_elements(p_journal_details_data)
LOOP
INSERT INTO public.journal_voucher_details(
id,
journal_header_id,
credit_account_id,
debit_account_id,
amount,
tds_tcs,
narration,
created_by,
created_on_utc,
is_deleted
)
VALUES (
(v_voucher_detail->>'detail_id')::UUID,
p_journal_header_id,
(v_voucher_detail->>'credit_account_id')::UUID,
(v_voucher_detail->>'debit_account_id')::UUID,
(v_voucher_detail->>'amount')::NUMERIC,
v_voucher_detail->>'tds_tcs',
v_voucher_detail->>'narration',
p_created_by,
CURRENT_TIMESTAMP,
FALSE
);
RAISE NOTICE 'Inserted into journal_voucher_details';
END LOOP;
INSERT INTO public.transaction_headers (
company_id,
customer_id,
vendor_id,
employee_id,
transaction_date,
transaction_source_type,
status_id,
document_id,
document_number,
description,
created_by,
created_on_utc
)
VALUES(
(p_transaction_header_data->>'company_id')::UUID,
NULLIF((p_transaction_header_data->>'customer_id')::UUID, '00000000-0000-0000-0000-000000000000'),
NULLIF((p_transaction_header_data->>'vendor_id')::UUID, '00000000-0000-0000-0000-000000000000'),
NULLIF((p_transaction_header_data->>'employee_id')::UUID, '00000000-0000-0000-0000-000000000000'),
(p_transaction_header_data->>'transaction_date')::DATE,
(p_transaction_header_data->>'transaction_source_type')::INT,
(p_transaction_header_data->>'status_id')::INT,
(p_transaction_header_data->>'document_id')::UUID,
v_voucher_number,
(p_transaction_header_data->>'description')::TEXT,
p_created_by,
CURRENT_TIMESTAMP
)
RETURNING id INTO v_transaction_id;
-- Loop through each journal entry and insert
FOR v_journal_entry IN SELECT * FROM jsonb_array_elements(journal_entries_data)
LOOP
RAISE NOTICE 'transaction_date: %', (v_journal_entry->>'transaction_date')::DATE;
INSERT INTO public.journal_entries (
transaction_id,
account_id,
transaction_date,
amount,
entry_type,
description_template_id,
dynamic_data,
entry_source_id
)
VALUES (
v_transaction_id,
(v_journal_entry->>'account_id')::uuid,
(v_journal_entry->>'transaction_date')::date,
(v_journal_entry->>'amount')::decimal,
(v_journal_entry->>'entry_type')::char,
(v_journal_entry->>'description_template_id')::int,
(v_journal_entry->>'dynamic_data')::jsonb,
(v_journal_entry->>'entry_source_id')::int
);
END LOOP;
END;
$procedure$
-- Procedure: insert_organization_user
-- SOURCE
CREATE OR REPLACE PROCEDURE public.insert_organization_user(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_existing_id uuid;
v_is_deleted boolean;
BEGIN
-- Check if user-organization relationship exists (including soft-deleted)
SELECT id, is_deleted
INTO v_existing_id, v_is_deleted
FROM public.organization_users
WHERE user_id = p_user_id
AND organization_id = p_organization_id
LIMIT 1;
-- If exists and active (not deleted), do nothing
-- If exists and active (not deleted), do nothing
-- If exists and active (not deleted), do nothing
-- If exists and active (not deleted), do nothing
-- If exists and active (not deleted), do nothing
IF FOUND AND v_is_deleted = false THEN
RAISE NOTICE 'User % already exists in organization % (active), skipping insert.', p_user_id, p_organization_id;
-- If exists but soft-deleted, reactivate the record
ELSIF FOUND AND v_is_deleted = true THEN
UPDATE public.organization_users
SET is_deleted = false,
modified_on_utc = NOW(),
modified_by = p_created_by,
deleted_on_utc = NULL
WHERE id = v_existing_id;
RAISE NOTICE 'User % re-activated in organization % (id=%)', p_user_id, p_organization_id, v_existing_id;
-- If no record found, insert new
ELSE
INSERT INTO public.organization_users(
id,
user_id,
organization_id,
effective_start_date,
effective_end_date,
created_on_utc,
created_by,
is_deleted
)
VALUES (
gen_random_uuid(),
p_user_id,
p_organization_id,
NOW(),
DATE '9999-12-31',
NOW(),
p_created_by,
false
);
RAISE NOTICE 'Organization-user relationship created for user % in organization %', p_user_id, p_organization_id;
END IF;
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.insert_organization_user(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid)
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Check if this user-organization relationship already exists
IF NOT EXISTS (
SELECT 1
FROM public.organization_users
WHERE user_id = p_user_id
AND organization_id = p_organization_id
) THEN
-- Only insert if the relationship doesn't exist
INSERT INTO public.organization_users (
id,
user_id,
effective_start_date,
effective_end_date,
created_on_utc,
created_by,
organization_id
) VALUES (
gen_random_uuid(),
p_user_id,
NOW(),
DATE '9999-12-31',
NOW(),
p_created_by,
p_organization_id
);
RAISE NOTICE 'Organization-user relationship created for user % in organization %', p_user_id, p_organization_id;
ELSE
RAISE NOTICE 'User % already exists in organization %. Skipping insertion.', p_user_id, p_organization_id;
END IF;
END;
$procedure$
-- Procedure: insert_user_role
CREATE OR REPLACE PROCEDURE public.insert_user_role(IN p_user_id uuid, IN p_role_id integer, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_existing_id INT;
v_is_deleted BOOLEAN;
BEGIN
-- Look for existing role assignment for user
SELECT id, is_deleted
INTO v_existing_id, v_is_deleted
FROM public.user_roles
WHERE user_id = p_user_id
AND role_id = p_role_id
LIMIT 1;
-- Case 1: Record exists and active -> do nothing
IF FOUND AND v_is_deleted = false THEN
RAISE NOTICE 'Role % already active for user %, skipping insert', p_role_id, p_user_id;
-- Case 2: Record exists but soft-deleted -> reactivate
ELSIF FOUND AND v_is_deleted = true THEN
UPDATE public.user_roles
SET is_deleted = false,
modified_on_utc = NOW(),
modified_by = p_created_by,
deleted_on_utc = NULL
WHERE id = v_existing_id;
RAISE NOTICE 'Role % re-activated for user % (id=%)', p_role_id, p_user_id, v_existing_id;
-- Case 3: No record - insert new
ELSE
INSERT INTO public.user_roles (
user_id,
role_id,
created_on_utc,
created_by,
is_deleted,
start_date,
end_date
)
VALUES (
p_user_id,
p_role_id,
NOW(),
p_created_by,
false,
NOW(), -- Fixed start_date as now()
'9999-12-31 00:00:00'::timestamp -- Fixed end_date as default far future
);
RAISE NOTICE 'Role % assigned to user % (new)', p_role_id, p_user_id;
END IF;
END;
$procedure$
-- Procedure: insert_user_roles
CREATE OR REPLACE PROCEDURE public.insert_user_roles(IN p_user_id uuid, IN p_role_ids integer[], IN p_organization_id uuid, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_role_id integer;
v_existing_id INT;
v_is_deleted BOOLEAN;
BEGIN
FOREACH v_role_id IN ARRAY p_role_ids
LOOP
-- Find matching user-role-org record
SELECT id, is_deleted
INTO v_existing_id, v_is_deleted
FROM public.user_roles
WHERE user_id = p_user_id
AND role_id = v_role_id
AND organization_id = p_organization_id
LIMIT 1;
-- If active record exists -> do nothing
IF FOUND AND v_is_deleted = false THEN
RAISE NOTICE 'Role % already active for user % in organization %, skipping insert', v_role_id, p_user_id, p_organization_id;
-- If found and soft-deleted -> reactivate
ELSIF FOUND AND v_is_deleted = true THEN
UPDATE public.user_roles
SET is_deleted = false,
modified_on_utc = NOW(),
modified_by = p_created_by,
deleted_on_utc = NULL
WHERE id = v_existing_id;
RAISE NOTICE 'Role % re-activated for user % in organization % (id=%)', v_role_id, p_user_id, p_organization_id, v_existing_id;
-- No record exists, insert new
ELSE
INSERT INTO public.user_roles (
user_id,
role_id,
organization_id,
created_on_utc,
created_by,
is_deleted,
start_date,
end_date
)
VALUES (
p_user_id,
v_role_id,
p_organization_id,
NOW(),
p_created_by,
false,
NOW(),
'9999-12-31 00:00:00'::timestamp
);
RAISE NOTICE 'Role % assigned to user % in organization % (new)', v_role_id, p_user_id, p_organization_id;
END IF;
END LOOP;
END;
$procedure$
-- Procedure: migrate_opening_balances_for_organization
CREATE OR REPLACE PROCEDURE public.migrate_opening_balances_for_organization(IN p_organization_id uuid, IN p_finyear_id integer, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_status_id integer := 1;
v_transaction_source_type_id integer := 18; -- as per your instruction
v_description_template_id integer := 1;
v_entry_source_id integer := 1;
v_opening_equity uuid;
v_sundry_debtors uuid;
v_sundry_creditors uuid;
v_salary_payable uuid;
rec_company RECORD;
ob_rec RECORD;
v_transaction_id bigint;
v_document_number text;
v_transaction_date timestamp;
v_entry_type text;
v_entity_type text;
v_entity_id uuid;
v_main_account uuid;
v_should_insert boolean;
v_name text;
v_customer_id uuid;
v_vendor_id uuid;
v_employee_id uuid;
v_account_id uuid;
v_year_label text;
BEGIN
-- COA account IDs
SELECT id INTO v_opening_equity
FROM public.chart_of_accounts
WHERE account_type_id = (SELECT account_type_id FROM public.chart_of_accounts WHERE name = 'Opening Balance Equity' LIMIT 1)
AND name = 'Opening Balance Equity'
LIMIT 1;
IF v_opening_equity IS NULL THEN
RAISE EXCEPTION 'Chart of Accounts "Opening Balance Equity" not found!';
END IF;
SELECT accounts_receivable_account_id, accounts_payable_account_id, salary_payable_account_id INTO v_sundry_debtors, v_sundry_creditors, v_salary_payable
FROM public.organization_accounts
WHERE organization_id = p_organization_id;
IF v_sundry_debtors IS NULL THEN
RAISE EXCEPTION 'Chart of Accounts "Sundry Debtors" not found!';
END IF;
IF v_sundry_creditors IS NULL THEN
RAISE EXCEPTION 'Chart of Accounts "Sundry Creditors" not found!';
END IF;
IF v_salary_payable IS NULL THEN
RAISE EXCEPTION 'Chart of Accounts "Salary Payable" not found!';
END IF;
-- Set transaction_date and year label from the finance year
SELECT start_date, "year" INTO v_transaction_date, v_year_label FROM public.finance_year WHERE id = p_finyear_id;
IF v_transaction_date IS NULL OR v_year_label IS NULL THEN
RAISE EXCEPTION 'Finance year with id % not found or year column is NULL!', p_finyear_id;
END IF;
-- Create a temporary table to track inserted COA accounts
CREATE TEMP TABLE IF NOT EXISTS temp_inserted_coa_accounts (account_id uuid PRIMARY KEY);
-- For each company in the organization, with the selected finance year
FOR rec_company IN
SELECT c.id AS company_id
FROM public.companies c
JOIN public.company_finance_year cfy ON c.id = cfy.company_id
WHERE c.organization_id = p_organization_id
AND cfy.finance_year_id = p_finyear_id
LOOP
-- For each opening balance for this organization and year
FOR ob_rec IN
SELECT *
FROM public.opening_balances
WHERE organization_id = p_organization_id
AND finyear_id = p_finyear_id
LOOP
v_should_insert := false;
v_name := null;
v_customer_id := null;
v_vendor_id := null;
v_employee_id := null;
v_account_id := null;
-- Determine main account and entity type, and check mapping
IF ob_rec.customer_id IS NOT NULL THEN
v_entity_type := 'Customer';
v_entity_id := ob_rec.customer_id;
v_main_account := v_sundry_debtors;
-- Check customers.company_id = rec_company.company_id
SELECT c.name INTO v_name
FROM public.customers c
WHERE c.id = ob_rec.customer_id AND c.company_id = rec_company.company_id;
IF v_name IS NOT NULL THEN
v_should_insert := true;
v_document_number := v_name; -- Only the name, not 'OPENBAL-' || v_name
v_customer_id := ob_rec.customer_id;
END IF;
ELSIF ob_rec.vendor_id IS NOT NULL THEN
v_entity_type := 'Vendor';
v_entity_id := ob_rec.vendor_id;
v_main_account := v_sundry_creditors;
SELECT v.name INTO v_name
FROM public.vendors v
WHERE v.id = ob_rec.vendor_id AND v.company_id = rec_company.company_id;
IF v_name IS NOT NULL THEN
v_should_insert := true;
v_document_number := v_name; -- Only the name, not 'OPENBAL-' || v_name
v_vendor_id := ob_rec.vendor_id;
END IF;
ELSIF ob_rec.employee_id IS NOT NULL THEN
v_entity_type := 'Employee';
v_entity_id := ob_rec.employee_id;
v_main_account := v_salary_payable;
SELECT e.first_name INTO v_name
FROM public.employees e
WHERE e.id = ob_rec.employee_id AND e.company_id = rec_company.company_id;
IF v_name IS NOT NULL THEN
v_should_insert := true;
v_document_number := v_name; -- Only the name, not 'OPENBAL-' || v_name
v_employee_id := ob_rec.employee_id;
END IF;
ELSE
-- COA-only
v_entity_type := 'COA';
v_entity_id := ob_rec.account_id;
v_main_account := ob_rec.account_id;
-- Get COA name
SELECT c.name INTO v_name
FROM public.chart_of_accounts c
WHERE c.id = ob_rec.account_id;
IF v_name IS NOT NULL THEN
v_document_number := v_name; -- Only the name, not 'OPENBAL-' || v_name
v_account_id := ob_rec.account_id;
-- insert only if this coa id is not already present
IF NOT EXISTS (SELECT 1 FROM temp_inserted_coa_accounts WHERE account_id = v_account_id) THEN
v_should_insert := true;
INSERT INTO temp_inserted_coa_accounts(account_id) VALUES (v_account_id);
END IF;
END IF;
END IF;
IF v_should_insert THEN
-- Insert into transaction_headers
INSERT INTO public.transaction_headers (
company_id, transaction_date, status_id, document_number, description, created_on_utc, created_by, transaction_source_type,
customer_id, vendor_id, employee_id, document_id
)
VALUES (
rec_company.company_id,
v_transaction_date,
v_status_id,
v_document_number,
'Opening Balance for ' || v_year_label, -- Only the year label from finance_year
v_transaction_date,
p_created_by,
v_transaction_source_type_id,
v_customer_id,
v_vendor_id,
v_employee_id,
v_account_id
)
RETURNING id INTO v_transaction_id;
-- Insert into journal_entries (double-entry)
INSERT INTO public.journal_entries (
transaction_id, transaction_date, amount, entry_type, description_template_id, dynamic_data, entry_source_id, account_id
) VALUES (
v_transaction_id, v_transaction_date, ob_rec.balance, ob_rec.entry_type, v_description_template_id, '{}'::jsonb, v_entry_source_id, v_main_account
);
-- Opening Balance Equity
IF ob_rec.entry_type = 'D' THEN
v_entry_type := 'C';
ELSE
v_entry_type := 'D';
END IF;
INSERT INTO public.journal_entries (
transaction_id, transaction_date, amount, entry_type, description_template_id, dynamic_data, entry_source_id, account_id
) VALUES (
v_transaction_id, v_transaction_date, ob_rec.balance, v_entry_type, v_description_template_id, '{}'::jsonb, v_entry_source_id, v_opening_equity
);
END IF;
END LOOP;
END LOOP;
DROP TABLE IF EXISTS temp_inserted_coa_accounts;
END;
$procedure$
-- Procedure: upsert_user_permissions
CREATE OR REPLACE PROCEDURE public.upsert_user_permissions(IN p_user_id uuid, IN p_permission_ids uuid[], IN p_organization_id uuid, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_permission_id uuid;
v_existing_id int;
v_existing_deleted bool;
v_inserted_id int;
BEGIN
FOR v_permission_id IN SELECT unnest(p_permission_ids) LOOP
SELECT id, is_deleted INTO v_existing_id, v_existing_deleted
FROM public.user_permissions
WHERE user_id = p_user_id
AND permission_id = v_permission_id
AND organization_id = p_organization_id
FOR UPDATE;
IF v_existing_id IS NOT NULL AND NOT v_existing_deleted THEN
CONTINUE;
ELSIF v_existing_id IS NOT NULL AND v_existing_deleted THEN
UPDATE public.user_permissions
SET is_deleted = false,
deleted_on_utc = NULL,
modified_by = p_created_by,
modified_on_utc = NOW()
WHERE id = v_existing_id;
ELSE
INSERT INTO public.user_permissions (
user_id,
permission_id,
organization_id,
created_on_utc,
created_by,
is_deleted
) VALUES (
p_user_id,
v_permission_id,
p_organization_id,
NOW(),
p_created_by,
false
) RETURNING id INTO v_inserted_id;
END IF;
END LOOP;
END;
$procedure$
-- Procedure: insert_user_permission
CREATE OR REPLACE PROCEDURE public.insert_user_permission(IN p_user_id uuid, IN p_organization_id uuid, IN p_permission_ids uuid[], IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_permission_id uuid;
v_existing_id INT;
v_is_deleted BOOLEAN;
BEGIN
RAISE NOTICE '▶️ START insert_user_permission for user_id=%, org_id=%, created_by=%, permissions=%',
p_user_id, p_organization_id, p_created_by, p_permission_ids;
IF p_user_id IS NULL OR p_organization_id IS NULL OR p_created_by IS NULL THEN
RAISE EXCEPTION '❌ One of the input parameters is NULL (user_id=%, org_id=%, created_by=%)',
p_user_id, p_organization_id, p_created_by;
END IF;
FOREACH v_permission_id IN ARRAY p_permission_ids
LOOP
RAISE NOTICE '🔍 Checking permission_id=% for user_id=% in org_id=%',
v_permission_id, p_user_id, p_organization_id;
-- Check if the record exists
SELECT id, is_deleted
INTO v_existing_id, v_is_deleted
FROM public.user_permissions
WHERE user_id = p_user_id
AND organization_id = p_organization_id
AND permission_id = v_permission_id
LIMIT 1;
IF FOUND THEN
RAISE NOTICE '✅ Found existing record with id=%, is_deleted=%', v_existing_id, v_is_deleted;
ELSE
RAISE NOTICE '❌ No existing record found for this combination';
END IF;
IF FOUND AND v_is_deleted = false THEN
RAISE NOTICE '⚠️ Permission % already active for user %, skipping insert',
v_permission_id, p_user_id;
ELSIF FOUND AND v_is_deleted = true THEN
UPDATE public.user_permissions
SET is_deleted = false,
modified_on_utc = NOW(),
modified_by = p_created_by,
deleted_on_utc = NULL
WHERE id = v_existing_id;
RAISE NOTICE '♻️ Permission % re-activated for user % (id=%)',
v_permission_id, p_user_id, v_existing_id;
ELSE
RAISE NOTICE '✅ Inserting Permission % assigned to user % (new) org: %', v_permission_id, p_user_id, p_organization_id;
INSERT INTO public.user_permissions (
user_id,
organization_id,
permission_id,
created_on_utc,
created_by,
is_deleted
)
VALUES (
p_user_id,
p_organization_id,
v_permission_id,
NOW(),
p_created_by,
false
);
END IF;
END LOOP;
RAISE NOTICE '✅ Completed insert_user_permission for user_id=%', p_user_id;
END;
$procedure$
-- Procedure: upsert_user_permissions
CREATE OR REPLACE PROCEDURE public.upsert_user_permissions(IN p_user_id uuid, IN p_permission_ids uuid[], IN p_role_ids integer[], IN p_organization_id uuid, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_permission_id uuid;
v_role_id int;
v_existing_id int;
v_existing_deleted bool;
v_existing_role_id int;
BEGIN
-- 0️⃣ Soft-delete permissions NOT in the new list
UPDATE public.user_permissions
SET is_deleted = TRUE,
deleted_on_utc = NOW(),
modified_by = p_created_by,
modified_on_utc = NOW()
WHERE user_id = p_user_id
AND organization_id = p_organization_id
AND (p_permission_ids IS NULL OR permission_id <> ALL(p_permission_ids))
AND is_deleted = FALSE;
-- 1️⃣ Upsert (reactivate or insert) listed permissions
FOR v_permission_id IN SELECT unnest(p_permission_ids) LOOP
SELECT id, is_deleted
INTO v_existing_id, v_existing_deleted
FROM public.user_permissions
WHERE user_id = p_user_id
AND permission_id = v_permission_id
AND organization_id = p_organization_id
FOR UPDATE;
IF v_existing_id IS NOT NULL AND NOT v_existing_deleted THEN
CONTINUE;
ELSIF v_existing_id IS NOT NULL AND v_existing_deleted THEN
UPDATE public.user_permissions
SET is_deleted = FALSE,
deleted_on_utc = NULL,
modified_by = p_created_by,
modified_on_utc = NOW()
WHERE id = v_existing_id;
ELSE
INSERT INTO public.user_permissions (
user_id, permission_id, organization_id,
created_on_utc, created_by, is_deleted
) VALUES (
p_user_id, v_permission_id, p_organization_id,
NOW(), p_created_by, false
);
END IF;
END LOOP;
-- 2️⃣ Soft-delete roles NOT in the new list
UPDATE public.user_roles
SET is_deleted = TRUE,
deleted_on_utc = NOW(),
modified_by = p_created_by,
modified_on_utc = NOW()
WHERE user_id = p_user_id
AND organization_id = p_organization_id
AND (p_role_ids IS NULL OR role_id <> ALL(p_role_ids))
AND is_deleted = FALSE;
-- 3️⃣ Upsert (reactivate or insert) listed roles
FOR v_role_id IN SELECT unnest(p_role_ids) LOOP
SELECT id
INTO v_existing_role_id
FROM public.user_roles
WHERE user_id = p_user_id
AND role_id = v_role_id
AND organization_id = p_organization_id;
IF v_existing_role_id IS NULL THEN
INSERT INTO public.user_roles (
user_id, role_id, organization_id,
created_on_utc, created_by, start_date, is_deleted
) VALUES (
p_user_id, v_role_id, p_organization_id,
NOW(), p_created_by, NOW(), FALSE
);
ELSE
UPDATE public.user_roles
SET is_deleted = FALSE,
deleted_on_utc = NULL,
modified_by = p_created_by,
modified_on_utc = NOW()
WHERE id = v_existing_role_id
AND is_deleted = TRUE;
END IF;
END LOOP;
END;
$procedure$
-- Procedure: upsert_user_permission_template
CREATE OR REPLACE PROCEDURE public.upsert_user_permission_template(IN p_user_id uuid, IN p_permission_template_ids integer[], IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_permission_id uuid;
v_existing_up_id int;
v_existing_up_deleted boolean;
v_inserted_up_id int;
v_org_user_id uuid;
BEGIN
-- nothing to do if no templates provided
IF p_permission_template_ids IS NULL OR array_length(p_permission_template_ids, 1) IS NULL THEN
RETURN;
END IF;
----------------------------------------------------------------
-- 1) Ensure organization_users row exists for this user + org
-- (Do this before touching user_permissions)
----------------------------------------------------------------
SELECT id
INTO v_org_user_id
FROM public.organization_users
WHERE user_id = p_user_id
AND organization_id IS NOT DISTINCT FROM p_organization_id
FOR UPDATE;
IF v_org_user_id IS NULL THEN
INSERT INTO public.organization_users (
id,
user_id,
effective_start_date,
effective_end_date,
created_on_utc,
is_deleted,
created_by,
organization_id
) VALUES (
gen_random_uuid(), -- requires pgcrypto (or replace with uuid_generate_v4())
p_user_id,
NOW(), -- effective_start_date
NOW() + INTERVAL '1 year', -- effective_end_date
NOW(), -- created_on_utc
false, -- is_deleted
p_created_by,
COALESCE(p_organization_id, '00000000-0000-0000-0000-000000000000'::uuid)
)
RETURNING id INTO v_org_user_id;
END IF;
----------------------------------------------------------------
-- 2) Upsert user_permissions for permission_ids from templates
----------------------------------------------------------------
FOR v_permission_id IN
SELECT DISTINCT ptm.permission_id
FROM public.permission_template_mappings ptm
WHERE ptm.permission_template_id = ANY (p_permission_template_ids)
LOOP
-- Look for an existing user_permissions row for this user, permission and organization.
SELECT id, is_deleted
INTO v_existing_up_id, v_existing_up_deleted
FROM public.user_permissions
WHERE user_id = p_user_id
AND permission_id = v_permission_id
AND organization_id IS NOT DISTINCT FROM p_organization_id
FOR UPDATE;
-- If it exists and is active, nothing to do.
IF v_existing_up_id IS NOT NULL AND NOT v_existing_up_deleted THEN
CONTINUE;
-- If it exists and is marked deleted, reactivate it.
ELSIF v_existing_up_id IS NOT NULL AND v_existing_up_deleted THEN
UPDATE public.user_permissions
SET is_deleted = false,
deleted_on_utc = NULL,
modified_by = p_created_by,
modified_on_utc = NOW()
WHERE id = v_existing_up_id;
-- Otherwise insert a new row.
ELSE
INSERT INTO public.user_permissions (
user_id,
permission_id,
organization_id,
created_on_utc,
created_by,
is_deleted
) VALUES (
p_user_id,
v_permission_id,
p_organization_id,
NOW(),
p_created_by,
false
) RETURNING id INTO v_inserted_up_id;
END IF;
END LOOP;
END;
$procedure$
-- Procedure: insert_into_bank_transfers
CREATE OR REPLACE PROCEDURE public.insert_into_bank_transfers(IN p_company_id uuid, IN p_bank_transfer_id uuid, IN p_transfer_date date, IN p_amount numeric, IN p_description text, IN p_source_account_id uuid, IN p_target_account_id uuid, IN p_mode_of_payment integer, IN p_reference text, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_transfer_number text;
v_transaction_id bigint;
v_narration_id bigint;
BEGIN
-- 1️⃣ Generate transfer number
v_transfer_number :=
get_new_transfer_number(p_company_id, p_transfer_date);
-- 2️⃣ Insert bank transfer
INSERT INTO public.bank_transfers (
id,
company_id,
source_account_id,
target_account_id,
amount,
transfer_date,
mode_of_payment,
reference,
description,
is_bulk,
created_by,
created_on_utc,
is_deleted,
transfer_number
)
VALUES (
p_bank_transfer_id,
p_company_id,
p_source_account_id,
p_target_account_id,
p_amount,
p_transfer_date,
COALESCE(p_mode_of_payment, 0),
NULLIF(p_reference, ''),
p_description,
FALSE,
p_created_by,
CURRENT_TIMESTAMP,
FALSE,
v_transfer_number
);
-- 3️⃣ Insert transaction header
INSERT INTO public.transaction_headers (
company_id,
transaction_date,
transaction_source_type,
status_id,
document_id,
document_number,
description,
created_by,
created_on_utc
)
VALUES (
p_company_id,
p_transfer_date,
19, -- BANK_TRANSFER
1,
p_bank_transfer_id,
v_transfer_number,
p_description,
p_created_by,
CURRENT_TIMESTAMP
)
RETURNING id INTO v_transaction_id;
-- 4️⃣ Create narration ONCE
INSERT INTO public.journal_narrations (
transaction_date,
description_template_id,
dynamic_data,
created_on_utc,
created_by
)
VALUES (
p_transfer_date,
49, -- BANK_TRANSFER narration template
jsonb_build_object(
'type', 'bank_transfer',
'reference', p_reference
)::text,
CURRENT_TIMESTAMP,
p_created_by
)
RETURNING id INTO v_narration_id;
-- 5️⃣ Credit source account
INSERT INTO public.journal_entries (
transaction_id,
transaction_date,
account_id,
amount,
entry_type,
entry_source_id,
journal_narration_id
)
VALUES (
v_transaction_id,
p_transfer_date,
p_source_account_id,
p_amount,
'C',
2,
v_narration_id
);
-- 6️⃣ Debit target account
INSERT INTO public.journal_entries (
transaction_id,
transaction_date,
account_id,
amount,
entry_type,
entry_source_id,
journal_narration_id
)
VALUES (
v_transaction_id,
p_transfer_date,
p_target_account_id,
p_amount,
'D',
2,
v_narration_id
);
END;
$procedure$
-- Procedure: update_existing_user_role_permissions
CREATE OR REPLACE PROCEDURE public.update_existing_user_role_permissions(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_permission_ids uuid[] DEFAULT NULL::uuid[], IN p_role_ids integer[] DEFAULT NULL::integer[])
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_role_permission_ids uuid[];
v_final_permission_ids uuid[];
BEGIN
RAISE NOTICE 'Procedure started for user_id=%', p_user_id;
-- Insert user to organization
IF p_organization_id IS NOT NULL THEN
RAISE NOTICE 'Inserting user into organization: %', p_organization_id;
CALL public.insert_organization_user(
p_user_id,
p_created_by,
p_organization_id
);
END IF;
-- Insert user to company
IF p_company_id IS NOT NULL THEN
RAISE NOTICE 'Inserting user into company: %', p_company_id;
CALL public.insert_company_user(
p_user_id,
p_created_by,
p_company_id
);
END IF;
/* ---------------------------------------------
1. Fetch permission_ids from role_permissions
--------------------------------------------- */
IF p_role_ids IS NOT NULL THEN
RAISE NOTICE 'Fetching permissions for roles: %', p_role_ids;
SELECT ARRAY_AGG(DISTINCT rp.permission_id)
INTO v_role_permission_ids
FROM public.role_permissions rp
WHERE rp.role_id = ANY (p_role_ids)
AND rp.is_deleted = false;
RAISE NOTICE 'Permissions from roles: %', v_role_permission_ids;
END IF;
/* ---------------------------------------------
2. Merge direct permissions + role permissions
--------------------------------------------- */
v_final_permission_ids :=
ARRAY(
SELECT DISTINCT unnest(
COALESCE(p_permission_ids, '{}') ||
COALESCE(v_role_permission_ids, '{}')
)
);
RAISE NOTICE 'Final permission list: %', v_final_permission_ids;
/* ---------------------------------------------
3. Insert permissions to user
--------------------------------------------- */
IF v_final_permission_ids IS NOT NULL
AND array_length(v_final_permission_ids, 1) > 0
AND p_organization_id IS NOT NULL THEN
RAISE NOTICE 'Inserting permissions for user into organization %', p_organization_id;
CALL public.insert_user_permission(
p_user_id,
p_organization_id,
v_final_permission_ids,
p_created_by
);
ELSE
RAISE NOTICE 'No permissions inserted (empty list or organization missing)';
END IF;
/* ---------------------------------------------
4. Insert user roles
--------------------------------------------- */
IF p_role_ids IS NOT NULL THEN
RAISE NOTICE 'Assigning roles to user: %', p_role_ids;
CALL public.insert_user_roles(
p_user_id,
p_role_ids,
p_organization_id,
p_created_by
);
END IF;
RAISE NOTICE 'Procedure completed successfully for user_id=%', p_user_id;
END;
$procedure$
-- Procedure: update_existing_user_role_permissions
CREATE OR REPLACE PROCEDURE public.update_existing_user_role_permissions(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_role_ids integer[] DEFAULT NULL::integer[])
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_role_permission_ids uuid[];
BEGIN
RAISE NOTICE 'UPDATE procedure started for user_id=%', p_user_id;
/* ---------------------------------------------
Ensure user-organization & company mapping
--------------------------------------------- */
IF p_organization_id IS NOT NULL THEN
RAISE NOTICE 'Ensuring user exists in organization: %', p_organization_id;
CALL public.insert_organization_user(
p_user_id,
p_created_by,
p_organization_id
);
END IF;
IF p_company_id IS NOT NULL THEN
RAISE NOTICE 'Ensuring user exists in company: %', p_company_id;
CALL public.insert_company_user(
p_user_id,
p_created_by,
p_company_id
);
END IF;
/* ---------------------------------------------
1. Fetch permissions ONLY from roles
--------------------------------------------- */
IF p_role_ids IS NOT NULL THEN
RAISE NOTICE 'Roles received for update: %', p_role_ids;
SELECT ARRAY_AGG(DISTINCT rp.permission_id)
INTO v_role_permission_ids
FROM public.role_permissions rp
WHERE rp.role_id = ANY (p_role_ids)
AND rp.is_deleted = false;
RAISE NOTICE 'Permissions derived from roles: %', v_role_permission_ids;
ELSE
RAISE NOTICE 'No roles provided, skipping permission derivation';
END IF;
/* ---------------------------------------------
2. Update user permissions (role-based only)
--------------------------------------------- */
IF v_role_permission_ids IS NOT NULL
AND array_length(v_role_permission_ids, 1) > 0
AND p_organization_id IS NOT NULL THEN
RAISE NOTICE 'Updating user permissions using role-based permissions';
CALL public.insert_user_permission(
p_user_id,
p_organization_id,
v_role_permission_ids,
p_created_by
);
ELSE
RAISE NOTICE 'No role-based permissions to apply';
END IF;
/* ---------------------------------------------
3. Update user roles
--------------------------------------------- */
IF p_role_ids IS NOT NULL THEN
RAISE NOTICE 'Updating user roles: %', p_role_ids;
CALL public.insert_user_roles(
p_user_id,
p_role_ids,
p_organization_id,
p_created_by
);
END IF;
RAISE NOTICE 'UPDATE procedure completed successfully for user_id=%', p_user_id;
END;
$procedure$
-- Procedure: update_existing_users_role_permissions
CREATE OR REPLACE PROCEDURE public.update_existing_users_role_permissions(IN p_user_ids uuid[], IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_role_ids integer[] DEFAULT NULL::integer[])
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_user_id uuid;
v_role_permission_ids uuid[];
BEGIN
RAISE NOTICE 'UPDATE procedure started for users=%', p_user_ids;
/* ---------------------------------------------
1. Fetch permissions ONLY from roles (once)
--------------------------------------------- */
IF p_role_ids IS NOT NULL THEN
RAISE NOTICE 'Roles received: %', p_role_ids;
SELECT ARRAY_AGG(DISTINCT rp.permission_id)
INTO v_role_permission_ids
FROM public.role_permissions rp
WHERE rp.role_id = ANY (p_role_ids)
AND rp.is_deleted = false;
RAISE NOTICE 'Permissions derived from roles: %', v_role_permission_ids;
ELSE
RAISE NOTICE 'No roles provided, skipping permission derivation';
END IF;
/* ---------------------------------------------
2. Loop through each user
--------------------------------------------- */
FOREACH v_user_id IN ARRAY p_user_ids
LOOP
RAISE NOTICE 'Processing user_id=%', v_user_id;
-- Ensure user-organization mapping
IF p_organization_id IS NOT NULL THEN
CALL public.insert_organization_user(
v_user_id,
p_created_by,
p_organization_id
);
END IF;
-- Ensure user-company mapping
IF p_company_id IS NOT NULL THEN
CALL public.insert_company_user(
v_user_id,
p_created_by,
p_company_id
);
END IF;
-- Assign role-based permissions
IF v_role_permission_ids IS NOT NULL
AND array_length(v_role_permission_ids, 1) > 0
AND p_organization_id IS NOT NULL THEN
CALL public.insert_user_permission(
v_user_id,
p_organization_id,
v_role_permission_ids,
p_created_by
);
END IF;
-- Assign roles
IF p_role_ids IS NOT NULL THEN
CALL public.insert_user_roles(
v_user_id,
p_role_ids,
p_organization_id,
p_created_by
);
END IF;
RAISE NOTICE 'Completed user_id=%', v_user_id;
END LOOP;
RAISE NOTICE 'UPDATE procedure completed for all users';
END;
$procedure$
-- Procedure: add_existing_user_role_permissions
CREATE OR REPLACE PROCEDURE public.add_existing_user_role_permissions(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_permission_ids uuid[] DEFAULT NULL::uuid[], IN p_role_ids integer[] DEFAULT NULL::integer[])
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Insert user to organization if organization_id is provided
IF p_organization_id IS NOT NULL THEN
CALL public.insert_organization_user(p_user_id, p_created_by, p_organization_id);
END IF;
-- Insert user to company if company_id is provided
IF p_company_id IS NOT NULL THEN
CALL public.insert_company_user(p_user_id, p_created_by, p_company_id);
END IF;
-- Insert permissions if both permission_ids array and organization_id are provided
IF p_permission_ids IS NOT NULL AND p_organization_id IS NOT NULL THEN
CALL public.insert_user_permission(p_user_id, p_organization_id, p_permission_ids, p_created_by);
END IF;
-- Insert roles if role_ids array is provided
IF p_role_ids IS NOT NULL THEN
CALL public.insert_user_roles(p_user_id, p_role_ids, p_organization_id, p_created_by);
END IF;
END;
$procedure$
-- Procedure: insert_multiple_bank_statements_by_excel
-- SOURCE
CREATE OR REPLACE PROCEDURE public.insert_multiple_bank_statements_by_excel(IN p_statements jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
stmt RECORD;
v_created_by UUID;
v_existing_id INT;
BEGIN
/* -------------------------------------------------
Resolve System user (mandatory fallback)
------------------------------------------------- */
SELECT id
INTO v_created_by
FROM public.users
WHERE first_name = 'System'
LIMIT 1;
IF v_created_by IS NULL THEN
RAISE EXCEPTION '❌ System user not found. created_by cannot be resolved.';
END IF;
/* -------------------------------------------------
Iterate input JSON
------------------------------------------------- */
FOR stmt IN
SELECT *
FROM jsonb_to_recordset(p_statements) AS (
organization_id UUID,
bank_id UUID,
txn_date TIMESTAMP,
cheque_number TEXT,
description TEXT,
value_date TIMESTAMP,
branch_code TEXT,
debit_amount NUMERIC,
credit_amount NUMERIC,
balance NUMERIC,
created_by UUID
)
LOOP
BEGIN
/* -------------------------------------------------
Hard validations (NO silent corruption)
------------------------------------------------- */
IF stmt.organization_id IS NULL THEN
RAISE EXCEPTION
'organization_id cannot be NULL (txn_date=%, desc=%)',
stmt.txn_date, stmt.description;
END IF;
IF stmt.bank_id IS NULL THEN
RAISE EXCEPTION
'bank_id cannot be NULL (txn_date=%, desc=%)',
stmt.txn_date, stmt.description;
END IF;
IF stmt.created_by IS NULL THEN
stmt.created_by := v_created_by;
END IF;
/* -------------------------------------------------
Detect existing row (idempotent key)
------------------------------------------------- */
SELECT id
INTO v_existing_id
FROM public.bank_statements bs
WHERE bs.organization_id = stmt.organization_id
AND bs.bank_id = stmt.bank_id
AND bs.is_deleted = FALSE
AND (
(stmt.txn_date::time <> '00:00:00'
AND bs.txn_date = stmt.txn_date)
OR
(stmt.txn_date::time = '00:00:00'
AND bs.txn_date::date = stmt.txn_date::date)
)
AND COALESCE(bs.debit_amount, 0) = COALESCE(stmt.debit_amount, 0)
AND COALESCE(bs.credit_amount, 0) = COALESCE(stmt.credit_amount, 0)
AND COALESCE(bs.cheque_number, '') = COALESCE(stmt.cheque_number, '')
AND COALESCE(bs.description, '') = COALESCE(stmt.description, '')
LIMIT 1;
/* -------------------------------------------------
INSERT
------------------------------------------------- */
IF v_existing_id IS NULL THEN
INSERT INTO public.bank_statements (
organization_id,
bank_id,
txn_date,
cheque_number,
description,
value_date,
branch_code,
debit_amount,
credit_amount,
balance,
has_reconciled,
created_by,
created_on_utc,
is_deleted
)
VALUES (
stmt.organization_id,
stmt.bank_id,
stmt.txn_date,
stmt.cheque_number,
stmt.description,
stmt.value_date,
stmt.branch_code,
stmt.debit_amount,
stmt.credit_amount,
stmt.balance,
FALSE,
stmt.created_by,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
FALSE
);
RAISE NOTICE '✅ Inserted: %, %',
stmt.txn_date, stmt.description;
/* -------------------------------------------------
UPDATE (single-row safe)
------------------------------------------------- */
ELSE
UPDATE public.bank_statements
SET
cheque_number = stmt.cheque_number,
value_date = stmt.value_date,
branch_code = stmt.branch_code,
balance = COALESCE(stmt.balance, balance),
modified_by = stmt.created_by,
modified_on_utc =
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp
WHERE id = v_existing_id;
RAISE NOTICE '🔄 Updated: %, %',
stmt.txn_date, stmt.description;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION
'❌ Error for org=%, bank=%, txn_date=%, desc=% → %',
stmt.organization_id,
stmt.bank_id,
stmt.txn_date,
stmt.description,
SQLERRM;
END;
END LOOP;
RAISE NOTICE '🎉 All bank statements processed successfully.';
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.insert_multiple_bank_statements_by_excel(IN p_statements jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
stmt RECORD;
v_created_by UUID;
BEGIN
-- Use system user or default to a fallback ID if required
SELECT id INTO v_created_by
FROM users
WHERE first_name = 'System'
LIMIT 1;
-- Loop through each bank statement in the JSONB array
FOR stmt IN
SELECT * FROM jsonb_to_recordset(p_statements) AS (
company_id UUID,
txn_date DATE,
cheque_number TEXT,
description TEXT,
value_date DATE,
branch_code TEXT,
debit_amount NUMERIC,
credit_amount NUMERIC,
balance NUMERIC,
bank_id UUID
)
LOOP
BEGIN
-- Check for existing bank statement with same txn_date and description
IF NOT EXISTS (
SELECT 1
FROM public.bank_statements
WHERE company_id = stmt.company_id
AND bank_id = stmt.bank_id
AND txn_date = stmt.txn_date
AND description = stmt.description
AND is_deleted = false
) THEN
-- Insert new bank statement
INSERT INTO public.bank_statements (
company_id,
txn_date,
cheque_number,
description,
value_date,
branch_code,
debit_amount,
credit_amount,
balance,
bank_id,
created_by,
created_on_utc,
is_deleted,
has_reconciled
) VALUES (
stmt.company_id,
stmt.txn_date,
stmt.cheque_number,
stmt.description,
stmt.value_date,
stmt.branch_code,
stmt.debit_amount,
stmt.credit_amount,
stmt.balance,
stmt.bank_id,
v_created_by,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
false,
false
);
RAISE NOTICE 'Inserted bank statement for date: %, company: %', stmt.txn_date, stmt.company_id;
ELSE
-- Update the existing statement
UPDATE public.bank_statements
SET
cheque_number = stmt.cheque_number,
value_date = stmt.value_date,
branch_code = stmt.branch_code,
debit_amount = stmt.debit_amount,
credit_amount = stmt.credit_amount,
balance = stmt.balance,
modified_by = v_created_by,
modified_on_utc = (CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp
WHERE company_id = stmt.company_id
AND bank_id = stmt.bank_id
AND txn_date = stmt.txn_date
AND description = stmt.description
AND is_deleted = false;
RAISE NOTICE 'Updated existing bank statement for date: %, company: %', stmt.txn_date, stmt.company_id;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Error in processing statement for txn_date: %, description: %, Error: %', stmt.txn_date, stmt.description, SQLERRM;
END;
END LOOP;
RAISE NOTICE 'All bank statements processed successfully';
END;
$procedure$
-- View: vw_permission_hierarchy
WITH RECURSIVE permission_tree AS (
SELECT p.id,
p.name,
p.parent_permission_id,
p.description,
1 AS level,
p.id AS root_permission_id
FROM permissions p
UNION ALL
SELECT c.id,
c.name,
c.parent_permission_id,
c.description,
(pt.level + 1) AS level,
pt.root_permission_id
FROM (permissions c
JOIN permission_tree pt ON ((c.parent_permission_id = pt.id)))
)
SELECT root_permission_id,
id AS permission_id,
name AS permission_name,
parent_permission_id,
description,
level
FROM permission_tree;
-- View: vw_organization_summary
SELECT DISTINCT o.id AS organization_id,
o.name AS organization_name,
c.id AS company_id,
c.name AS company_name,
u.id AS user_id,
concat(u.first_name, ' ', u.last_name) AS user_full_name,
u.email AS user_email,
u.phone_number AS user_phone,
'Company User'::text AS user_role_source,
o.created_on_utc AS organization_created_on
FROM (((organizations o
JOIN companies c ON ((c.organization_id = o.id)))
JOIN company_users cu ON ((cu.company_id = c.id)))
JOIN users u ON ((u.id = cu.user_id)))
UNION
SELECT DISTINCT o.id AS organization_id,
o.name AS organization_name,
NULL::uuid AS company_id,
NULL::text AS company_name,
u.id AS user_id,
concat(u.first_name, ' ', u.last_name) AS user_full_name,
u.email AS user_email,
u.phone_number AS user_phone,
'Organization User'::text AS user_role_source,
o.created_on_utc AS organization_created_on
FROM ((organizations o
JOIN organization_users ou ON ((ou.organization_id = o.id)))
JOIN users u ON ((u.id = ou.user_id)));
-- View: transaction_sums_by_party
SELECT th.customer_id,
th.vendor_id,
th.employee_id,
c.name AS customer_name,
v.name AS vendor_name,
sum(
CASE
WHEN ((je.entry_type = 'C'::bpchar) AND (coa.account_type_id = ANY (ARRAY[4, 14, 15, 19, 20]))) THEN je.amount
ELSE (0)::numeric
END) AS total_invoiced,
sum(
CASE
WHEN ((je.entry_type = 'D'::bpchar) AND (coa.account_type_id = ANY (ARRAY[5, 16, 17, 18, 21, 22]))) THEN je.amount
ELSE (0)::numeric
END) AS total_billed,
sum(
CASE
WHEN ((je.entry_type = 'C'::bpchar) AND (coa.account_type_id = ANY (ARRAY[8, 9]))) THEN je.amount
ELSE (0)::numeric
END) AS total_received,
sum(
CASE
WHEN ((je.entry_type = 'D'::bpchar) AND (coa.account_type_id = ANY (ARRAY[8, 9]))) THEN je.amount
ELSE (0)::numeric
END) AS total_paid
FROM ((((transaction_headers th
LEFT JOIN customers c ON ((th.customer_id = c.id)))
LEFT JOIN vendors v ON ((th.vendor_id = v.id)))
JOIN journal_entries je ON ((je.transaction_id = th.id)))
JOIN chart_of_accounts coa ON ((je.account_id = coa.id)))
WHERE ((th.is_deleted = false) AND (je.is_deleted = false) AND (th.company_id = '9891a03a-d80a-430e-ab33-c419f99cffa0'::uuid))
GROUP BY th.customer_id, th.vendor_id, th.employee_id, c.name, v.name
ORDER BY COALESCE(c.name, v.name);
-- View: vw_user_permissions_details
SELECT up.id AS user_permission_id,
u.id AS user_id,
u.first_name AS user_name,
p.id AS permission_id,
p.name AS permission_name,
o.id AS organization_id,
o.name AS organization_name,
o.short_name AS organization_short_name,
o.phone_number AS organization_phone_number,
o.email AS organization_email,
o.gstin AS organization_gstin,
up.created_on_utc,
up.created_by,
up.modified_on_utc,
up.modified_by,
up.is_deleted,
p.deleted_on_utc AS permission_deleted_on_utc,
up.deleted_on_utc AS user_permission_deleted_on_utc
FROM (((user_permissions up
JOIN permissions p ON ((up.permission_id = p.id)))
JOIN users u ON ((up.user_id = u.id)))
JOIN organizations o ON ((up.organization_id = o.id)))
WHERE ((up.is_deleted = false) AND (p.is_deleted = false) AND (o.is_deleted = false));
-- View: transaction_party_view
SELECT th.id AS transaction_id,
th.company_id,
th.transaction_date,
th.document_number,
th.description,
th.customer_id,
c.name AS customer_name,
th.vendor_id,
v.name AS vendor_name,
th.employee_id,
je.entry_type,
coa.account_type_id,
coa.name AS account_name,
je.amount,
CASE
WHEN ((je.entry_type = 'C'::bpchar) AND (coa.account_type_id = ANY (ARRAY[4, 14, 15, 19, 20]))) THEN je.amount
ELSE (0)::numeric
END AS invoiced,
CASE
WHEN ((je.entry_type = 'C'::bpchar) AND (coa.account_type_id = ANY (ARRAY[8, 9]))) THEN je.amount
ELSE (0)::numeric
END AS received,
CASE
WHEN ((je.entry_type = 'D'::bpchar) AND (coa.account_type_id = ANY (ARRAY[5, 16, 17, 18, 21, 22]))) THEN je.amount
ELSE (0)::numeric
END AS billed,
CASE
WHEN ((je.entry_type = 'D'::bpchar) AND (coa.account_type_id = ANY (ARRAY[8, 9]))) THEN je.amount
ELSE (0)::numeric
END AS paid
FROM ((((transaction_headers th
LEFT JOIN customers c ON ((th.customer_id = c.id)))
LEFT JOIN vendors v ON ((th.vendor_id = v.id)))
JOIN journal_entries je ON ((je.transaction_id = th.id)))
JOIN chart_of_accounts coa ON ((je.account_id = coa.id)))
WHERE ((th.is_deleted = false) AND (je.is_deleted = false));
-- Trigger: trg_user_permissions_notify
CREATE TRIGGER trg_user_permissions_notify AFTER INSERT OR DELETE OR UPDATE ON user_permissions FOR EACH ROW EXECUTE FUNCTION notify_user_permission_change()