| 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_upis | Match | ||||||
| Table | company_users | 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 | companies | Match | ||||||
| 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 | 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 | 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 | account_categories | Match | ||||||
| Table | account_groups | Match | ||||||
| Table | document_meta_datas | Match | ||||||
| Table | account_opening_balances | Match | ||||||
| Table | bank_reconciliation_links | Missing in Target |
|
|||||
| Table | bank_transfers | Match | ||||||
| Table | journal_voucher_details | Match | ||||||
| Table | schema_versions | Match | ||||||
| Table | states | Match | ||||||
| Table | temp_organization_logs | Match | ||||||
| Table | temp_paymnet_data | Missing in Target |
|
|||||
| Table | bank_reconciliation_stagings | Missing in Target |
|
|||||
| Table | payment_references | Missing in Target |
|
|||||
| Table | journal_narrations | Missing in Target |
|
|||||
| Table | organization_users | Match | ||||||
| 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 | bank_transfer_ids | Match | ||||||
| Table | banks | Match | ||||||
| Table | budget_lines | Match | ||||||
| Table | budget_statuses | Match | ||||||
| Table | budget_workflow | Match | ||||||
| Table | budgets | Match | ||||||
| Table | cities | Match | ||||||
| Table | cron_dummy_log | Missing in Target |
|
|||||
| Table | permission_groups | Match | ||||||
| Table | company_bank_accounts | Match | ||||||
| Table | company_contacts | 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 | 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 | bank_reconciliations | Missing in Target |
|
|||||
| Table | feature_toggles | Missing in Target |
|
|||||
| Table | user_permissions_backup | 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 | user_permission_sync_deltas | 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 | roles | Match | ||||||
| Table | opening_balances | 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 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, "customer_advance_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "customer_security_deposit_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "employee_advance_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "employee_loan_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "vendor_advance_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "vendor_security_deposit_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "esi_payable_employee_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "esi_payable_employer_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "lwf_payable_employee_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "lwf_payable_employer_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "pf_payable_employee_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "pf_payable_employer_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "tds_on_contractors_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "opening_balance_equity_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "tds_on_salaries_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, 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 | 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 | account_types | Match | ||||||
| Table | addresses | Match | ||||||
| Table | audit_queries | Missing in Target |
|
|||||
| Table | audit_query_responses | Missing in Target |
|
|||||
| Table | audit_query_statuses | Missing in Target |
|
|||||
| Table | bank_accounts | Match | ||||||
| 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 | Match | ||||||
| Table | organization_types | Match | ||||||
| Table | organizations | Match | ||||||
| Table | permissions | Match | ||||||
| Table | reconciliation_statuses | Missing in Target |
|
|||||
| 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 | user_deletion_request_statuses | Match | ||||||
| Table | user_deletion_requests | Match | ||||||
| Table | user_global_permissions | Match | ||||||
| Table | company_preferences | Match | ||||||
| Table | designations | Match | ||||||
| Table | dummy_log_table | Match | ||||||
| Table | dummy_test_table | Match | ||||||
| Table | email_templates | Match | ||||||
| Table | employees | Missing in Target |
|
|||||
| Table | entry_sources | Match | ||||||
| Table | finance_year | Match | ||||||
| Table | fixed_deposits | Match | ||||||
| Table | general_ledgers | Match | ||||||
| Table | images | Match | ||||||
| Table | email_notification_rules | Missing in Target |
|
|||||
| Table | email_audit_logs | 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_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 | get_three_years_expenses | Match | ||||||
| Function | get_all_coa | Match | ||||||
| Function | pg_get_tabledef | Match | ||||||
| Function | get_cash_flow_statement | Match | ||||||
| Function | get_bank_statements | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bank_statements(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 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.company_id,
10
bs.bank_id,
11
coa.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
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
-- ✅ Finalized Reconciliation Status
29
COALESCE(br.reconciliation_status_id, 0) AS rec_status_id,
30
31
-- Determine rec_status based on staging status and confidence score
32
CASE
33
WHEN br.reconciliation_status_id IS NOT NULL THEN
34
COALESCE(rs.status::text, 'unmatched')
35
WHEN brs.confidence_score >= 70.99 THEN
36
'matched' -- Auto-match based on high confidence score
37
WHEN brs.status = 'pending' THEN
38
'pending' -- Pending review status
39
ELSE
40
'unmatched'
41
END AS rec_status,
42
43
-- Latest staging details (AI / manual reconciliation suggestion)
44
COALESCE(brs.confidence_score, 0) AS rec_confidence_score,
45
COALESCE(brs.strategy_name::text, 'No Match Found') AS rec_strategy_name,
46
brs.matched_party_name::text AS rec_matched_party_name,
47
brs.party_id AS rec_party_id,
48
brs.party_type::text AS rec_party_type,
49
brs.reviewed_by AS rec_reviewed_by,
50
brs.reviewed_on_utc AS rec_reviewed_on_utc,
51
COALESCE(brs.matched_amount, 0) AS rec_matched_amount
52
53
FROM public.bank_statements bs
54
INNER JOIN public.chart_of_accounts coa
55
ON coa.id = bs.bank_id
56
AND coa.account_type_id = 9
57
AND coa.is_deleted = FALSE
58
INNER JOIN public.finance_year fy
59
ON fy.id = p_finyear_id
60
61
-- 🟢 Latest staging entry (AI / manual reconciliation suggestion)
62
LEFT JOIN LATERAL (
63
SELECT brs_inner.*
64
FROM public.bank_reconciliation_stagings brs_inner
65
WHERE brs_inner.company_id = bs.company_id
66
AND brs_inner.bank_statement_id = bs.id
67
AND brs_inner.is_deleted = FALSE
68
ORDER BY brs_inner.created_on_utc DESC
69
LIMIT 1
70
) brs ON TRUE
71
72
-- 🟢 Finalized reconciliations
73
LEFT JOIN public.bank_reconciliations br
74
ON br.bank_statement_id = bs.id
75
AND br.company_id = bs.company_id
76
77
-- 🟢 Status lookup
78
LEFT JOIN public.reconciliation_statuses rs
79
ON rs.id = br.reconciliation_status_id
80
81
WHERE bs.company_id = p_company_id
82
AND bs.txn_date BETWEEN fy.start_date AND fy.end_date
83
AND (p_date_from IS NULL OR bs.txn_date >= p_date_from)
84
AND (p_date_to IS NULL OR bs.txn_date <= p_date_to)
85
AND bs.is_deleted = FALSE
86
87
ORDER BY bs.txn_date, bs.id;
88
END;
89
$function$
|
|||||
| 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 | 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
CONST_MONTHLY CONSTANT INTEGER := 1;
10
CONST_QUARTERLY CONSTANT INTEGER := 2;
11
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
12
CONST_YEARLY CONSTANT INTEGER := 4;
13
CONST_YTD CONSTANT INTEGER := 5;
14
BEGIN
8
BEGIN
15
-- Get financial year boundaries
9
-- Step 1: Get financial year boundaries
16
SELECT start_date, end_date INTO v_financial_year_start, v_financial_year_end
10
SELECT start_date, end_date INTO v_financial_year_start, v_financial_year_end
17
FROM public.finance_year
11
FROM public.finance_year
18
WHERE id = p_finance_id;
12
WHERE id = p_finance_id;
19
13
20
-- Get organization_id for the given company_id
14
-- Raise an exception if financial year is not found
21
SELECT organization_id INTO v_organization_id
22
FROM public.companies
23
WHERE id = p_company_id;
24
25
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
15
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
26
RAISE EXCEPTION 'Financial year with ID % not found', p_finance_id;
16
RAISE EXCEPTION 'Financial year with ID % not found', p_finance_id;
27
END IF;
17
END IF;
28
IF v_organization_id IS NULL THEN
29
RAISE EXCEPTION 'Company % not found or missing organization_id', p_company_id;
30
END IF;
31
18
32
IF p_period_type = CONST_MONTHLY THEN
19
-- Step 2: Categorize expenses based on period type
20
IF p_period_type = 1 THEN -- Monthly Categorization
33
RETURN QUERY
21
RETURN QUERY
34
WITH months AS (
22
WITH months AS (
35
SELECT
23
SELECT
36
TO_CHAR(m, 'Mon') AS period,
24
TO_CHAR(m, 'Mon') AS period,
37
EXTRACT(YEAR FROM m) AS year,
25
EXTRACT(YEAR FROM m) AS year,
38
EXTRACT(MONTH FROM m) AS month_num
26
EXTRACT(MONTH FROM m) AS month_num
39
FROM generate_series(v_financial_year_start, v_financial_year_end, '1 month'::interval) AS m
27
FROM generate_series(v_financial_year_start, v_financial_year_end, '1 month'::interval) AS m
40
)
28
)
41
SELECT
29
SELECT
42
months.period,
30
months.period,
43
months.year,
31
months.year,
44
coa.id AS account_id,
32
coa.id AS account_id, -- 🔹 Added missing account_id
45
coa.name AS account_name,
33
coa.name AS account_name,
46
COALESCE(SUM(je.amount), 0) AS total_expense
34
COALESCE(SUM(je.amount), 0) AS total_expense
47
FROM months
35
FROM months
48
LEFT JOIN public.journal_entries je
36
LEFT JOIN public.journal_entries je
49
ON EXTRACT(MONTH FROM je.transaction_date) = months.month_num
37
ON EXTRACT(MONTH FROM je.transaction_date) = months.month_num
50
AND EXTRACT(YEAR FROM je.transaction_date) = months.year
38
AND EXTRACT(YEAR FROM je.transaction_date) = months.year
51
AND je.is_deleted = FALSE
52
INNER JOIN public.transaction_headers th
39
INNER JOIN public.transaction_headers th
53
ON je.transaction_id = th.id
40
ON je.transaction_id = th.id
54
AND th.company_id = p_company_id
41
AND th.company_id = p_company_id
55
INNER JOIN public.chart_of_accounts coa
42
INNER JOIN public.chart_of_accounts coa
56
ON je.account_id = coa.id
43
ON je.account_id = coa.id
57
AND coa.organization_id = v_organization_id
44
WHERE
58
AND coa.is_deleted = FALSE
45
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
59
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
60
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
46
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
61
AND je.entry_type = 'D'
47
AND je.entry_type = 'D'
62
GROUP BY months.year, months.month_num, months.period, coa.id, coa.name
48
AND je.is_deleted = FALSE
49
GROUP BY months.year, months.month_num, months.period, coa.id, coa.name -- 🔹 Added `coa.id` to GROUP BY
63
ORDER BY months.year, months.month_num, coa.name;
50
ORDER BY months.year, months.month_num, coa.name;
64
51
65
ELSIF p_period_type = CONST_QUARTERLY THEN
52
53
ELSIF p_period_type = 2 THEN -- Quarterly Categorization
66
RETURN QUERY
54
RETURN QUERY
67
WITH quarters AS (
55
WITH quarters AS (
68
SELECT 'Q1' AS period, v_financial_year_start AS start_date, v_financial_year_start + interval '2 month' AS end_date
56
SELECT 'Q1' AS period, v_financial_year_start AS start_date, v_financial_year_start + interval '2 month' AS end_date
69
UNION ALL
57
UNION ALL
70
SELECT 'Q2', v_financial_year_start + interval '3 month', v_financial_year_start + interval '5 month'
58
SELECT 'Q2', v_financial_year_start + interval '3 month', v_financial_year_start + interval '5 month'
71
UNION ALL
59
UNION ALL
72
SELECT 'Q3', v_financial_year_start + interval '6 month', v_financial_year_start + interval '8 month'
60
SELECT 'Q3', v_financial_year_start + interval '6 month', v_financial_year_start + interval '8 month'
73
UNION ALL
61
UNION ALL
74
SELECT 'Q4', v_financial_year_start + interval '9 month', v_financial_year_end
62
SELECT 'Q4', v_financial_year_start + interval '9 month', v_financial_year_end
75
)
63
)
76
SELECT
64
SELECT
77
quarters.period,
65
quarters.period,
78
EXTRACT(YEAR FROM quarters.start_date) AS year,
66
EXTRACT(YEAR FROM quarters.start_date) AS year,
79
coa.id AS account_id,
67
coa.id AS account_id, -- Added account_id
80
coa.name AS account_name,
68
coa.name AS account_name,
81
COALESCE(SUM(je.amount), 0) AS total_expense
69
COALESCE(SUM(je.amount), 0) AS total_expense
82
FROM quarters
70
FROM quarters
83
LEFT JOIN public.journal_entries je
71
LEFT JOIN public.journal_entries je
84
ON je.transaction_date BETWEEN quarters.start_date AND quarters.end_date
72
ON je.transaction_date BETWEEN quarters.start_date AND quarters.end_date
85
AND je.is_deleted = FALSE
86
INNER JOIN public.transaction_headers th
73
INNER JOIN public.transaction_headers th
87
ON je.transaction_id = th.id
74
ON je.transaction_id = th.id
88
AND th.company_id = p_company_id
75
AND th.company_id = p_company_id
89
INNER JOIN public.chart_of_accounts coa
76
INNER JOIN public.chart_of_accounts coa
90
ON je.account_id = coa.id
77
ON je.account_id = coa.id
91
AND coa.organization_id = v_organization_id
78
WHERE
92
AND coa.is_deleted = FALSE
79
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
93
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
94
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
80
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
95
AND je.entry_type = 'D'
81
AND je.entry_type = 'D'
82
AND je.is_deleted = FALSE
96
GROUP BY quarters.period, year, coa.id, coa.name
83
GROUP BY quarters.period, year, coa.id, coa.name
97
ORDER BY year, quarters.period, coa.name;
84
ORDER BY year, quarters.period, coa.name;
98
85
99
ELSIF p_period_type = CONST_HALF_YEARLY THEN
86
ELSIF p_period_type = 3 THEN -- Half-Yearly Categorization
100
RETURN QUERY
87
RETURN QUERY
101
WITH half_years AS (
88
WITH half_years AS (
102
SELECT 'H1' AS period, v_financial_year_start AS start_date, v_financial_year_start + interval '5 month' AS end_date
89
SELECT 'H1' AS period, v_financial_year_start AS start_date, v_financial_year_start + interval '5 month' AS end_date
103
UNION ALL
90
UNION ALL
104
SELECT 'H2', v_financial_year_start + interval '6 month', v_financial_year_end
91
SELECT 'H2', v_financial_year_start + interval '6 month', v_financial_year_end
105
)
92
)
106
SELECT
93
SELECT
107
half_years.period,
94
half_years.period,
108
EXTRACT(YEAR FROM half_years.start_date) AS year,
95
EXTRACT(YEAR FROM half_years.start_date) AS year,
109
coa.id AS account_id,
96
coa.id AS account_id, -- Added account_id
110
coa.name AS account_name,
97
coa.name AS account_name,
111
COALESCE(SUM(je.amount), 0) AS total_expense
98
COALESCE(SUM(je.amount), 0) AS total_expense
112
FROM half_years
99
FROM half_years
113
LEFT JOIN public.journal_entries je
100
LEFT JOIN public.journal_entries je
114
ON je.transaction_date BETWEEN half_years.start_date AND half_years.end_date
101
ON je.transaction_date BETWEEN half_years.start_date AND half_years.end_date
115
AND je.is_deleted = FALSE
116
INNER JOIN public.transaction_headers th
102
INNER JOIN public.transaction_headers th
117
ON je.transaction_id = th.id
103
ON je.transaction_id = th.id
118
AND th.company_id = p_company_id
104
AND th.company_id = p_company_id
119
INNER JOIN public.chart_of_accounts coa
105
INNER JOIN public.chart_of_accounts coa
120
ON je.account_id = coa.id
106
ON je.account_id = coa.id
121
AND coa.organization_id = v_organization_id
107
WHERE
122
AND coa.is_deleted = FALSE
108
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
123
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
124
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
109
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
125
AND je.entry_type = 'D'
110
AND je.entry_type = 'D'
111
AND je.is_deleted = FALSE
126
GROUP BY half_years.period, year, coa.id, coa.name
112
GROUP BY half_years.period, year, coa.id, coa.name
127
ORDER BY year, half_years.period, coa.name;
113
ORDER BY year, half_years.period, coa.name;
128
114
129
ELSIF p_period_type = CONST_YEARLY THEN
115
ELSIF p_period_type = 4 THEN -- Yearly Categorization
130
RETURN QUERY
116
RETURN QUERY
131
SELECT
117
SELECT
132
'Year' AS period,
118
'Year' AS period,
133
EXTRACT(YEAR FROM je.transaction_date) AS year,
119
EXTRACT(YEAR FROM je.transaction_date) AS year,
134
coa.id AS account_id,
120
coa.id AS account_id, -- Added account_id
135
coa.name AS account_name,
121
coa.name AS account_name,
136
COALESCE(SUM(je.amount), 0) AS total_expense
122
COALESCE(SUM(je.amount), 0) AS total_expense
137
FROM public.journal_entries je
123
FROM public.journal_entries je
138
INNER JOIN public.transaction_headers th
124
INNER JOIN public.transaction_headers th
139
ON je.transaction_id = th.id
125
ON je.transaction_id = th.id
140
AND th.company_id = p_company_id
126
AND th.company_id = p_company_id
141
INNER JOIN public.chart_of_accounts coa
127
INNER JOIN public.chart_of_accounts coa
142
ON je.account_id = coa.id
128
ON je.account_id = coa.id
143
AND coa.organization_id = v_organization_id
129
WHERE
144
AND coa.is_deleted = FALSE
130
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
145
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
146
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
131
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
147
AND je.entry_type = 'D'
132
AND je.entry_type = 'D'
148
AND je.is_deleted = FALSE
133
AND je.is_deleted = FALSE
149
GROUP BY year, coa.id, coa.name
134
GROUP BY year, coa.id, coa.name
150
ORDER BY year, coa.name;
135
ORDER BY year, coa.name;
151
152
ELSIF p_period_type = CONST_YTD THEN
153
RETURN QUERY
154
WITH ytd_fin_years AS (
155
SELECT fy.start_date, fy.end_date
156
FROM public.finance_year fy
157
WHERE EXTRACT(YEAR FROM fy.start_date) BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 4 AND EXTRACT(YEAR FROM CURRENT_DATE)
158
ORDER BY fy.start_date DESC
159
LIMIT 5
160
)
161
SELECT
162
'YTD' AS period,
163
NULL::numeric AS year,
164
coa.id AS account_id,
165
coa.name AS account_name,
166
COALESCE(SUM(je.amount), 0) AS total_expense
167
FROM ytd_fin_years
168
LEFT JOIN public.journal_entries je
169
ON je.transaction_date BETWEEN ytd_fin_years.start_date AND LEAST(
170
ytd_fin_years.end_date,
171
CASE WHEN CURRENT_DATE BETWEEN ytd_fin_years.start_date AND ytd_fin_years.end_date
172
THEN CURRENT_DATE ELSE ytd_fin_years.end_date END
173
)
174
AND je.is_deleted = FALSE
175
LEFT JOIN public.transaction_headers th ON je.transaction_id = th.id
176
AND th.company_id = p_company_id
177
LEFT JOIN public.chart_of_accounts coa ON je.account_id = coa.id
178
AND coa.organization_id = v_organization_id
179
AND coa.is_deleted = FALSE
180
WHERE coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
181
AND (coa.name IS NULL OR coa.name NOT IN ('Rounding Gain'))
182
GROUP BY coa.id, coa.name
183
ORDER BY total_expense DESC;
184
136
185
END IF;
137
END IF;
186
END;
138
END;
187
$function$
139
$function$
|
|||||
| Function | get_income_expense_monthly_breakdown | Match | ||||||
| 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 | Match | ||||||
| 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_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 DATE;
6
v_financial_year_start date;
7
v_financial_year_end DATE;
7
v_financial_year_end date;
8
v_finance_year_start NUMERIC;
8
v_finance_year_start numeric;
9
v_finance_year_end NUMERIC;
9
v_finance_year_end numeric;
10
10
11
CONST_MONTHLY CONSTANT INTEGER := 1;
11
CONST_MONTHLY CONSTANT INTEGER := 1;
12
CONST_QUARTERLY CONSTANT INTEGER := 2;
12
CONST_QUARTERLY CONSTANT INTEGER := 2;
13
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
13
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
14
CONST_YEARLY CONSTANT INTEGER := 4;
14
CONST_YEARLY CONSTANT INTEGER := 4;
15
CONST_YTD CONSTANT INTEGER := 5;
15
CONST_YTD CONSTANT INTEGER := 5;
16
BEGIN
16
BEGIN
17
IF p_period_type != CONST_YTD THEN
17
IF p_period_type != CONST_YTD THEN
18
SELECT start_date, end_date,
18
SELECT start_date, end_date,
19
EXTRACT(YEAR FROM start_date),
19
EXTRACT(YEAR FROM start_date),
20
EXTRACT(YEAR FROM end_date)
20
EXTRACT(YEAR FROM end_date)
21
INTO v_financial_year_start, v_financial_year_end,
21
INTO v_financial_year_start, v_financial_year_end,
22
v_finance_year_start, v_finance_year_end
22
v_finance_year_start, v_finance_year_end
23
FROM public.finance_year
23
FROM public.finance_year
24
WHERE id = p_finance_id;
24
WHERE id = p_finance_id;
25
END IF;
25
END IF;
26
26
27
IF p_period_type = CONST_YTD THEN
27
IF p_period_type = CONST_YTD THEN
28
-- Returns last 5 years, fiscal quarters (Apr–Jun, Jul–Sep, Oct–Dec, Jan–Mar)
29
RETURN QUERY
28
RETURN QUERY
30
WITH recent_years AS (
29
WITH recent_years AS (
31
SELECT fy.id, fy.start_date, fy.end_date,
30
SELECT fy.id, fy.start_date, fy.end_date,
32
EXTRACT(YEAR FROM fy.start_date) AS fiscal_year
31
EXTRACT(YEAR FROM fy.start_date) AS year_start,
33
FROM public.finance_year fy
32
EXTRACT(YEAR FROM fy.end_date) AS year_end
33
FROM finance_year fy
34
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
35
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)
36
ORDER BY fy.start_date DESC
36
ORDER BY fy.start_date DESC
37
LIMIT 5
37
LIMIT 5
38
),
38
),
39
quarters AS (
39
ytd_periods AS (
40
SELECT id AS finance_id, fiscal_year, 'Q1' AS period,
40
SELECT id AS finance_id, 'YTD H1' AS period,
41
make_date(fiscal_year::int, 4, 1) AS period_start,
41
(start_date + INTERVAL '5 months 29 days')::date AS period_end,
42
make_date(fiscal_year::int, 6, 30) AS period_end
42
EXTRACT(YEAR FROM start_date) AS year,
43
FROM recent_years
43
start_date AS fy_start
44
UNION ALL
45
SELECT id, fiscal_year, 'Q2',
46
make_date(fiscal_year::int, 7, 1),
47
make_date(fiscal_year::int, 9, 30)
48
FROM recent_years
44
FROM recent_years
49
UNION ALL
45
UNION ALL
50
SELECT id, fiscal_year, 'Q3',
46
SELECT id, 'YTD H2', end_date,
51
make_date(fiscal_year::int, 10, 1),
47
EXTRACT(YEAR FROM end_date) AS year,
52
make_date(fiscal_year::int, 12, 31)
48
start_date
53
FROM recent_years
49
FROM recent_years
54
UNION ALL
50
WHERE end_date <= CURRENT_DATE
55
-- For Q4, fiscal_year stays the same, but period_start/period_end move to the next calendar year
51
AND CURRENT_DATE >= (start_date + INTERVAL '6 months') -- skip if it's a future YTD H2
56
SELECT id, fiscal_year, 'Q4',
57
make_date((fiscal_year::int) + 1, 1, 1),
58
make_date((fiscal_year::int) + 1, 3, 31)
59
FROM recent_years
60
),
52
),
61
filtered_je AS (
53
filtered_je AS (
62
SELECT
54
SELECT
63
je.amount,
55
je.amount,
64
je.entry_type,
56
je.entry_type,
65
tr.company_id,
57
tr.company_id,
66
je.transaction_date,
58
tr.transaction_date,
67
coa.account_type_id
59
coa.account_type_id
68
FROM journal_entries je
60
FROM journal_entries je
69
JOIN transaction_headers tr ON je.transaction_id = tr.id
61
JOIN transaction_headers tr ON je.transaction_id = tr.id
70
JOIN chart_of_accounts coa ON je.account_id = coa.id
62
JOIN chart_of_accounts coa ON je.account_id = coa.id
71
WHERE je.is_deleted = FALSE
63
WHERE je.is_deleted = FALSE
72
AND tr.company_id = p_company_id
73
)
64
)
74
SELECT
65
SELECT
75
CONCAT(q.period, ' ', q.fiscal_year) AS period,
66
CONCAT(yp.period, ' ', yp.year) AS period,
76
q.fiscal_year,
67
yp.year,
77
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
68
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
78
AND je.transaction_date BETWEEN q.period_start AND q.period_end THEN je.amount ELSE 0 END), 0) AS total_income,
69
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0),
79
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
70
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
80
AND je.transaction_date BETWEEN q.period_start AND q.period_end THEN je.amount ELSE 0 END), 0) AS total_expense,
71
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0),
81
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
72
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
82
AND je.transaction_date BETWEEN q.period_start AND q.period_end THEN je.amount ELSE 0 END), 0) AS actual_income,
73
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0),
83
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
74
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
84
AND je.transaction_date BETWEEN q.period_start AND q.period_end THEN je.amount ELSE 0 END), 0) AS actual_expense
75
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END), 0)
85
FROM quarters q
76
FROM ytd_periods yp
86
LEFT JOIN filtered_je je
77
LEFT JOIN filtered_je je ON je.company_id = p_company_id
87
ON je.transaction_date BETWEEN q.period_start AND q.period_end
78
GROUP BY yp.period, yp.year, yp.period_end
88
GROUP BY q.period, q.fiscal_year
89
HAVING
79
HAVING
90
SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
80
SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
91
AND je.transaction_date BETWEEN q.period_start AND q.period_end THEN je.amount ELSE 0 END) > 0
81
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
92
OR SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
82
OR SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
93
AND je.transaction_date BETWEEN q.period_start AND q.period_end THEN je.amount ELSE 0 END) > 0
83
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
94
OR SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
84
OR SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
95
AND je.transaction_date BETWEEN q.period_start AND q.period_end THEN je.amount ELSE 0 END) > 0
85
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
96
OR SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
86
OR SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
97
AND je.transaction_date BETWEEN q.period_start AND q.period_end THEN je.amount ELSE 0 END) > 0
87
AND je.transaction_date BETWEEN yp.fy_start AND yp.period_end THEN je.amount ELSE 0 END) > 0
98
ORDER BY q.fiscal_year, q.period;
88
ORDER BY yp.year, yp.period;
99
89
100
ELSIF p_period_type = CONST_MONTHLY THEN
90
ELSIF p_period_type = CONST_MONTHLY THEN
101
RETURN QUERY
91
RETURN QUERY
102
WITH months AS (
92
WITH months AS (
103
SELECT
93
SELECT
104
generate_series::date AS month_start,
94
generate_series::date AS month_start,
105
(generate_series + interval '1 month')::date AS month_end
95
(generate_series + interval '1 month')::date AS month_end
106
FROM generate_series(
96
FROM generate_series(
107
v_financial_year_start,
97
v_financial_year_start,
108
v_financial_year_end,
98
v_financial_year_end,
109
interval '1 month'
99
interval '1 month'
110
)
100
)
111
),
101
),
112
filtered_je AS (
102
filtered_je AS (
113
SELECT
103
SELECT
114
je.amount,
104
je.*,
115
je.entry_type,
116
je.transaction_date,
117
coa.account_type_id
105
coa.account_type_id
118
FROM journal_entries je
106
FROM journal_entries je
119
JOIN transaction_headers tr ON je.transaction_id = tr.id
107
JOIN transaction_headers tr ON je.transaction_id = tr.id
120
JOIN chart_of_accounts coa ON je.account_id = coa.id
108
JOIN chart_of_accounts coa ON je.account_id = coa.id
121
WHERE tr.company_id = p_company_id
109
WHERE tr.company_id = p_company_id
122
AND je.is_deleted = FALSE
110
AND je.is_deleted = FALSE
123
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
111
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
124
)
112
)
125
SELECT
113
SELECT
126
to_char(m.month_start, 'Mon YYYY') AS period,
114
to_char(m.month_start, 'Mon YYYY') AS period,
127
EXTRACT(YEAR FROM m.month_start) AS year,
115
EXTRACT(YEAR FROM m.month_start) AS year,
128
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
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),
129
AND je.transaction_date >= m.month_start AND je.transaction_date < m.month_end THEN je.amount ELSE 0 END), 0) AS total_income,
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),
130
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
118
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
131
AND je.transaction_date >= m.month_start AND je.transaction_date < m.month_end THEN je.amount ELSE 0 END), 0) AS total_expense,
119
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
132
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
133
AND je.transaction_date >= m.month_start AND je.transaction_date < m.month_end THEN je.amount ELSE 0 END), 0) AS actual_income,
134
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
135
AND je.transaction_date >= m.month_start AND je.transaction_date < m.month_end THEN je.amount ELSE 0 END), 0) AS actual_expense
136
FROM months m
120
FROM months m
137
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
138
ON je.transaction_date >= m.month_start AND je.transaction_date < m.month_end
139
GROUP BY m.month_start
122
GROUP BY m.month_start
140
ORDER BY m.month_start;
123
ORDER BY m.month_start;
141
124
142
ELSIF p_period_type = CONST_QUARTERLY THEN
125
ELSIF p_period_type = CONST_QUARTERLY THEN
143
-- Single year, use explicit fiscal quarters
144
RETURN QUERY
126
RETURN QUERY
145
WITH fiscal_quarters AS (
127
WITH quarters AS (
146
SELECT 'Q1' AS period, v_financial_year_start AS period_start, make_date(v_finance_year_start::int, 6, 30) AS period_end, v_finance_year_start AS year
128
SELECT 'Q1' AS period, 4 AS start_month, 6 AS end_month, v_finance_year_start AS year
147
UNION ALL
129
UNION ALL SELECT 'Q2', 7, 9, v_finance_year_start
148
SELECT 'Q2', make_date(v_finance_year_start::int, 7, 1), make_date(v_finance_year_start::int, 9, 30), v_finance_year_start
130
UNION ALL SELECT 'Q3', 10, 12, v_finance_year_start
149
UNION ALL
131
UNION ALL SELECT 'Q4', 1, 3, v_finance_year_end
150
SELECT 'Q3', make_date(v_finance_year_start::int, 10, 1), make_date(v_finance_year_start::int, 12, 31), v_finance_year_start
151
UNION ALL
152
SELECT 'Q4', make_date((v_finance_year_start::int) + 1, 1, 1), v_financial_year_end, v_finance_year_start
153
),
132
),
154
filtered_je AS (
133
filtered_je AS (
155
SELECT
134
SELECT
156
je.amount,
135
je.*,
157
je.entry_type,
158
je.transaction_date,
159
coa.account_type_id
136
coa.account_type_id
160
FROM journal_entries je
137
FROM journal_entries je
161
JOIN transaction_headers tr ON je.transaction_id = tr.id
138
JOIN transaction_headers tr ON je.transaction_id = tr.id
162
JOIN chart_of_accounts coa ON je.account_id = coa.id
139
JOIN chart_of_accounts coa ON je.account_id = coa.id
163
WHERE tr.company_id = p_company_id
140
WHERE tr.company_id = p_company_id
164
AND je.is_deleted = FALSE
141
AND je.is_deleted = FALSE
165
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
142
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
166
)
143
)
167
SELECT
144
SELECT
168
CONCAT(fq.period, ' ', fq.year) AS period,
145
CONCAT(q.period, ' ', q.year),
169
fq.year,
146
q.year,
170
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
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),
171
AND je.transaction_date BETWEEN fq.period_start AND fq.period_end THEN je.amount ELSE 0 END), 0) AS total_income,
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),
172
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
149
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
173
AND je.transaction_date BETWEEN fq.period_start AND fq.period_end THEN je.amount ELSE 0 END), 0) AS total_expense,
150
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
174
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
151
FROM quarters q
175
AND je.transaction_date BETWEEN fq.period_start AND fq.period_end THEN je.amount ELSE 0 END), 0) AS actual_income,
152
JOIN filtered_je je ON EXTRACT(MONTH FROM je.transaction_date) BETWEEN q.start_month AND q.end_month
176
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
153
AND EXTRACT(YEAR FROM je.transaction_date) = q.year
177
AND je.transaction_date BETWEEN fq.period_start AND fq.period_end THEN je.amount ELSE 0 END), 0) AS actual_expense
154
GROUP BY q.period, q.year
178
FROM fiscal_quarters fq
155
ORDER BY q.year, q.period;
179
LEFT JOIN filtered_je je
180
ON je.transaction_date BETWEEN fq.period_start AND fq.period_end
181
GROUP BY fq.period, fq.year
182
ORDER BY fq.year, fq.period;
183
156
184
ELSIF p_period_type = CONST_HALF_YEARLY THEN
157
ELSIF p_period_type = CONST_HALF_YEARLY THEN
185
RETURN QUERY
158
RETURN QUERY
186
WITH half_years AS (
159
WITH half_years AS (
187
SELECT 'H1' AS period, v_financial_year_start AS period_start, make_date(v_finance_year_start::int, 9, 30) AS period_end, v_finance_year_start AS year
160
SELECT 'H1' AS period, 4 AS start_month, 9 AS end_month, v_finance_year_start AS year
188
UNION ALL
161
UNION ALL SELECT 'H2', 10, 12, v_finance_year_start
189
SELECT 'H2', make_date(v_finance_year_start::int, 10, 1), v_financial_year_end, v_finance_year_start
162
UNION ALL SELECT 'H2', 1, 3, v_finance_year_end
190
),
163
),
191
filtered_je AS (
164
filtered_je AS (
192
SELECT
165
SELECT
193
je.amount,
166
je.*,
194
je.entry_type,
195
je.transaction_date,
196
coa.account_type_id
167
coa.account_type_id
197
FROM journal_entries je
168
FROM journal_entries je
198
JOIN transaction_headers tr ON je.transaction_id = tr.id
169
JOIN transaction_headers tr ON je.transaction_id = tr.id
199
JOIN chart_of_accounts coa ON je.account_id = coa.id
170
JOIN chart_of_accounts coa ON je.account_id = coa.id
200
WHERE tr.company_id = p_company_id
171
WHERE tr.company_id = p_company_id
201
AND je.is_deleted = FALSE
172
AND je.is_deleted = FALSE
202
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
173
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
203
)
174
)
204
SELECT
175
SELECT
205
CONCAT(h.period, ' ', h.year) AS period,
176
CONCAT(h.period, ' ', h.year),
206
h.year,
177
h.year,
207
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (4,14,15,19,20)
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),
208
AND je.transaction_date BETWEEN h.period_start AND h.period_end THEN je.amount ELSE 0 END), 0) AS total_income,
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),
209
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (5,16,17,18,21,22)
180
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
210
AND je.transaction_date BETWEEN h.period_start AND h.period_end THEN je.amount ELSE 0 END), 0) AS total_expense,
181
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
211
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
212
AND je.transaction_date BETWEEN h.period_start AND h.period_end THEN je.amount ELSE 0 END), 0) AS actual_income,
213
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
214
AND je.transaction_date BETWEEN h.period_start AND h.period_end THEN je.amount ELSE 0 END), 0) AS actual_expense
215
FROM half_years h
182
FROM half_years h
216
JOIN filtered_je je
183
JOIN filtered_je je ON EXTRACT(MONTH FROM je.transaction_date) BETWEEN h.start_month AND h.end_month
217
ON je.transaction_date BETWEEN h.period_start AND h.period_end
184
AND EXTRACT(YEAR FROM je.transaction_date) = h.year
218
GROUP BY h.period, h.year
185
GROUP BY h.period, h.year
219
ORDER BY h.year, h.period;
186
ORDER BY h.year, h.period;
220
187
221
ELSIF p_period_type = CONST_YEARLY THEN
188
ELSIF p_period_type = CONST_YEARLY THEN
222
RETURN QUERY
189
RETURN QUERY
223
WITH filtered_je AS (
190
WITH filtered_je AS (
224
SELECT
191
SELECT
225
je.amount,
192
je.*,
226
je.entry_type,
227
je.transaction_date,
228
coa.account_type_id
193
coa.account_type_id
229
FROM journal_entries je
194
FROM journal_entries je
230
JOIN transaction_headers tr ON je.transaction_id = tr.id
195
JOIN transaction_headers tr ON je.transaction_id = tr.id
231
JOIN chart_of_accounts coa ON je.account_id = coa.id
196
JOIN chart_of_accounts coa ON je.account_id = coa.id
232
WHERE tr.company_id = p_company_id
197
WHERE tr.company_id = p_company_id
233
AND je.is_deleted = FALSE
198
AND je.is_deleted = FALSE
234
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
199
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
235
)
200
)
236
SELECT
201
SELECT
237
CONCAT('Year ', v_finance_year_start) AS period,
202
CONCAT('Year ', EXTRACT(YEAR FROM je.transaction_date)),
238
v_finance_year_start AS year,
203
EXTRACT(YEAR FROM je.transaction_date),
239
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) AS total_income,
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),
240
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) AS total_expense,
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),
241
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0) AS actual_income,
206
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0),
242
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0) AS actual_expense
207
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9) THEN je.amount ELSE 0 END), 0)
243
FROM filtered_je je;
208
FROM filtered_je je
209
GROUP BY EXTRACT(YEAR FROM je.transaction_date)
210
ORDER BY EXTRACT(YEAR FROM je.transaction_date);
244
211
245
ELSE
212
ELSE
246
RAISE EXCEPTION 'Unsupported period type %', p_period_type;
213
RAISE EXCEPTION 'Unsupported period type %', p_period_type;
247
END IF;
214
END IF;
248
END;
215
END;
249
$function$
216
$function$
|
|||||
| 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 | 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_financial_year_start DATE;
7
v_financial_year_start DATE;
8
v_financial_year_end DATE;
8
v_financial_year_end DATE;
9
v_finance_year TEXT;
9
v_finance_year TEXT;
10
11
-- Period type constants
10
CONST_MONTHLY CONSTANT INTEGER := 1;
12
CONST_MONTHLY CONSTANT INTEGER := 1;
11
CONST_QUARTERLY CONSTANT INTEGER := 2;
13
CONST_QUARTERLY CONSTANT INTEGER := 2;
12
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
14
CONST_HALF_YEARLY CONSTANT INTEGER := 3;
13
CONST_YEARLY CONSTANT INTEGER := 4;
15
CONST_YEARLY CONSTANT INTEGER := 4;
14
CONST_YTD CONSTANT INTEGER := 5;
15
BEGIN
16
BEGIN
16
-- Fetch financial year details
17
-- Fetch financial year details
17
SELECT fy.start_date, fy.end_date, fy.year
18
SELECT fy.start_date, fy.end_date, fy.year
18
INTO v_financial_year_start, v_financial_year_end, v_finance_year
19
INTO v_financial_year_start, v_financial_year_end, v_finance_year
19
FROM public.finance_year fy
20
FROM public.finance_year fy
20
WHERE fy.id = p_finance_id;
21
WHERE fy.id = p_finance_id;
21
22
23
-- Validate financial year
22
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
24
IF v_financial_year_start IS NULL OR v_financial_year_end IS NULL THEN
23
RAISE EXCEPTION 'Invalid financial year ID: %', p_finance_id;
25
RAISE EXCEPTION 'Invalid financial year ID: %', p_finance_id;
24
END IF;
26
END IF;
25
27
28
-- Period-based filtering
26
IF p_period_type = CONST_MONTHLY THEN
29
IF p_period_type = CONST_MONTHLY THEN
27
RETURN QUERY
30
RETURN QUERY
28
SELECT
31
SELECT
29
coa.id AS account_id,
32
coa.id AS account_id,
30
coa.name AS account_name,
33
coa.name AS account_name,
31
coa.account_number,
34
coa.account_number,
32
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
35
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
33
v_finance_year AS financial_year
36
v_finance_year AS financial_year
34
FROM public.journal_entries je
37
FROM
38
public.journal_entries je
35
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
39
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
36
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
40
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
37
WHERE th.company_id = p_company_id
41
WHERE
42
th.company_id = p_company_id
38
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
43
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
39
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
44
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
40
AND EXTRACT(MONTH FROM je.transaction_date) = (p_period + 3) % 12 + 1
45
AND EXTRACT(MONTH FROM je.transaction_date) = (p_period + 3) % 12 + 1
41
AND je.is_deleted = FALSE
46
AND je.is_deleted = FALSE
42
AND coa.name NOT IN ('Rounding Gain')
47
AND coa.name NOT IN ('Rounding Gain')
43
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
48
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
44
ORDER BY total_expense DESC;
49
ORDER BY total_expense DESC;
45
50
46
ELSIF p_period_type = CONST_QUARTERLY THEN
51
ELSIF p_period_type = CONST_QUARTERLY THEN
47
RETURN QUERY
52
RETURN QUERY
48
SELECT
53
SELECT
49
coa.id AS account_id,
54
coa.id AS account_id,
50
coa.name AS account_name,
55
coa.name AS account_name,
51
coa.account_number,
56
coa.account_number,
52
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
57
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
53
v_finance_year AS financial_year
58
v_finance_year AS financial_year
54
FROM public.journal_entries je
59
FROM
60
public.journal_entries je
55
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
61
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
56
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
62
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
57
WHERE th.company_id = p_company_id
63
WHERE
64
th.company_id = p_company_id
58
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
65
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
59
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
66
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
60
AND (
67
AND (
61
(p_period = 1 AND EXTRACT(MONTH FROM je.transaction_date) IN (4, 5, 6)) OR
68
(p_period = 1 AND EXTRACT(MONTH FROM je.transaction_date) IN (4, 5, 6)) OR
62
(p_period = 2 AND EXTRACT(MONTH FROM je.transaction_date) IN (7, 8, 9)) OR
69
(p_period = 2 AND EXTRACT(MONTH FROM je.transaction_date) IN (7, 8, 9)) OR
63
(p_period = 3 AND EXTRACT(MONTH FROM je.transaction_date) IN (10, 11, 12)) OR
70
(p_period = 3 AND EXTRACT(MONTH FROM je.transaction_date) IN (10, 11, 12)) OR
64
(p_period = 4 AND EXTRACT(MONTH FROM je.transaction_date) IN (1, 2, 3))
71
(p_period = 4 AND EXTRACT(MONTH FROM je.transaction_date) IN (1, 2, 3))
65
)
72
)
66
AND je.is_deleted = FALSE
73
AND je.is_deleted = FALSE
67
AND coa.name NOT IN ('Rounding Gain')
74
AND coa.name NOT IN ('Rounding Gain')
68
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
75
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
69
ORDER BY total_expense DESC;
76
ORDER BY total_expense DESC;
70
77
71
ELSIF p_period_type = CONST_HALF_YEARLY THEN
78
ELSIF p_period_type = CONST_HALF_YEARLY THEN
72
RETURN QUERY
79
RETURN QUERY
73
SELECT
80
SELECT
74
coa.id AS account_id,
81
coa.id AS account_id,
75
coa.name AS account_name,
82
coa.name AS account_name,
76
coa.account_number,
83
coa.account_number,
77
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
84
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
78
v_finance_year AS financial_year
85
v_finance_year AS financial_year
79
FROM public.journal_entries je
86
FROM
87
public.journal_entries je
80
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
88
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
81
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
89
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
82
WHERE th.company_id = p_company_id
90
WHERE
91
th.company_id = p_company_id
83
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
92
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
84
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
93
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
85
AND (
94
AND (
86
(p_period = 1 AND EXTRACT(MONTH FROM je.transaction_date) BETWEEN 4 AND 9) OR
95
(p_period = 1 AND EXTRACT(MONTH FROM je.transaction_date) BETWEEN 4 AND 9) OR
87
(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))
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))
88
)
97
)
89
AND je.is_deleted = FALSE
98
AND je.is_deleted = FALSE
90
AND coa.name NOT IN ('Rounding Gain')
99
AND coa.name NOT IN ('Rounding Gain')
91
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
100
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
92
ORDER BY total_expense DESC;
101
ORDER BY total_expense DESC;
93
102
94
ELSIF p_period_type = CONST_YEARLY THEN
103
ELSIF p_period_type = CONST_YEARLY THEN
95
RETURN QUERY
104
RETURN QUERY
96
SELECT
105
SELECT
97
coa.id AS account_id,
106
coa.id AS account_id,
98
coa.name AS account_name,
107
coa.name AS account_name,
99
coa.account_number,
108
coa.account_number,
100
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
109
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
101
v_finance_year AS financial_year
110
v_finance_year AS financial_year
102
FROM public.journal_entries je
111
FROM
112
public.journal_entries je
103
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
113
INNER JOIN public.transaction_headers th ON je.transaction_id = th.id
104
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
114
INNER JOIN public.chart_of_accounts coa ON je.account_id = coa.id
105
WHERE th.company_id = p_company_id
115
WHERE
116
th.company_id = p_company_id
106
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
117
AND coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
107
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
118
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
108
AND je.is_deleted = FALSE
119
AND je.is_deleted = FALSE
109
AND coa.name NOT IN ('Rounding Gain')
120
AND coa.name NOT IN ('Rounding Gain')
110
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
121
GROUP BY coa.id, coa.name, coa.account_number, v_finance_year
111
ORDER BY total_expense DESC;
122
ORDER BY total_expense DESC;
112
113
ELSIF p_period_type = CONST_YTD THEN
114
RETURN QUERY
115
WITH recent_years AS (
116
SELECT fy.id, fy.start_date, fy.end_date,
117
fy.year AS financial_year_text
118
FROM public.finance_year fy
119
WHERE EXTRACT(YEAR FROM fy.start_date) >= EXTRACT(YEAR FROM CURRENT_DATE) - 4
120
AND EXTRACT(YEAR FROM fy.start_date) <= EXTRACT(YEAR FROM CURRENT_DATE)
121
ORDER BY fy.start_date DESC
122
LIMIT 5
123
),
124
base AS (
125
SELECT
126
coa.id AS account_id,
127
coa.name AS account_name,
128
coa.account_number,
129
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
130
ry.financial_year_text AS financial_year
131
FROM recent_years ry
132
LEFT JOIN public.journal_entries je
133
ON je.transaction_date BETWEEN ry.start_date AND LEAST(
134
ry.end_date,
135
CASE
136
WHEN CURRENT_DATE BETWEEN ry.start_date AND ry.end_date
137
THEN CURRENT_DATE
138
ELSE ry.end_date
139
END
140
)
141
LEFT JOIN public.transaction_headers th
142
ON je.transaction_id = th.id
143
AND th.company_id = p_company_id
144
LEFT JOIN public.chart_of_accounts coa
145
ON je.account_id = coa.id
146
WHERE
147
coa.account_type_id IN (SELECT id FROM get_account_type_hierarchy(5))
148
AND (je.id IS NULL OR je.is_deleted = FALSE)
149
AND (coa.name IS NULL OR coa.name NOT IN ('Rounding Gain'))
150
GROUP BY coa.id, coa.name, coa.account_number, ry.financial_year_text
151
)
152
SELECT
153
base.account_id,
154
base.account_name,
155
base.account_number,
156
SUM(base.total_expense) AS total_expense,
157
base.financial_year
158
FROM base
159
GROUP BY base.account_id, base.account_name, base.account_number, base.financial_year
160
ORDER BY base.financial_year DESC, total_expense DESC;
161
123
162
ELSE
124
ELSE
163
RAISE EXCEPTION 'Invalid period type: %', p_period_type;
125
RAISE EXCEPTION 'Invalid period type: %', p_period_type;
164
END IF;
126
END IF;
165
END;
127
END;
166
$function$
128
$function$
|
|||||
| 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_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
v_financial_year_start DATE;
7
v_financial_year_start DATE;
8
v_financial_year_end DATE;
8
v_financial_year_end DATE;
9
v_prev_financial_year_start DATE;
9
v_prev_financial_year_start DATE;
10
v_prev_financial_year_end DATE;
10
v_prev_financial_year_end DATE;
11
v_finance_year INTEGER;
11
v_finance_year INTEGER;
12
v_today DATE := CURRENT_DATE;
12
v_today DATE := CURRENT_DATE;
13
BEGIN
13
BEGIN
14
-- Fetch organization ID
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;
21
END IF;
22
22
23
-- Get financial year integer from 'YYYY-YY'
23
-- Get financial year integer from 'YYYY-YY'
24
SELECT LEFT(year, 4)::INTEGER INTO v_finance_year
24
SELECT LEFT(year, 4)::INTEGER INTO v_finance_year
25
FROM public.finance_year
25
FROM public.finance_year
26
WHERE id = p_finance_year_id
26
WHERE id = p_finance_year_id
27
LIMIT 1;
27
LIMIT 1;
28
28
29
IF NOT FOUND THEN
29
IF NOT FOUND THEN
30
RAISE EXCEPTION 'Invalid finance year ID: %', p_finance_year_id;
30
RAISE EXCEPTION 'Invalid finance year ID: %', p_finance_year_id;
31
END IF;
31
END IF;
32
32
33
-- Define financial year range
33
-- Define financial year range
34
v_financial_year_start := MAKE_DATE(v_finance_year, 4, 1);
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);
35
v_financial_year_end := MAKE_DATE(v_finance_year + 1, 3, 31);
36
36
37
-- Define previous financial year range
37
-- Define previous financial year range
38
v_prev_financial_year_start := MAKE_DATE(v_finance_year - 1, 4, 1);
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);
39
v_prev_financial_year_end := MAKE_DATE(v_finance_year, 3, 31);
40
40
41
-- Adjust financial year ends if the year is ongoing
41
-- Adjust financial year ends if the year is ongoing
42
IF v_today < v_financial_year_end THEN
42
IF v_today < v_financial_year_end THEN
43
v_financial_year_end := v_today;
43
v_financial_year_end := v_today;
44
END IF;
44
END IF;
45
45
46
IF v_today < v_prev_financial_year_end THEN
46
IF v_today < v_prev_financial_year_end THEN
47
v_prev_financial_year_end := v_today - INTERVAL '1 year';
47
v_prev_financial_year_end := v_today - INTERVAL '1 year';
48
END IF;
48
END IF;
49
49
50
-- Align both end dates to same day/month by limiting to shorter period
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');
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';
52
v_prev_financial_year_end := v_financial_year_end - INTERVAL '1 year';
53
53
54
-- Return calculated data
54
-- Return calculated data
55
RETURN QUERY
55
RETURN QUERY
56
SELECT
56
SELECT
57
-- Current Year Income
57
-- Current Year Income
58
ROUND(COALESCE((
58
ROUND(COALESCE((
59
SELECT SUM(je.amount)
59
SELECT SUM(je.amount)
60
FROM journal_entries je
60
FROM journal_entries je
61
JOIN chart_of_accounts ca ON je.account_id = ca.id
61
JOIN chart_of_accounts ca ON je.account_id = ca.id
62
JOIN account_types at ON ca.account_type_id = at.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
63
JOIN account_categories ac ON at.account_category_id = ac.id
64
JOIN transaction_headers tr ON je.transaction_id = tr.id
64
JOIN transaction_headers tr ON je.transaction_id = tr.id
65
WHERE ac.id = 4
65
WHERE ac.id = 4
66
AND tr.company_id = p_company_id
66
AND tr.company_id = p_company_id
67
AND ca.organization_id = v_organization_id
67
AND ca.organization_id = v_organization_id
68
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
68
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
69
AND je.is_deleted = FALSE
69
AND je.is_deleted = FALSE
70
), 0), 2),
70
), 0), 2),
71
71
72
-- Current Year Expense
72
-- Current Year Expense
73
ROUND(COALESCE((
73
ROUND(COALESCE((
74
SELECT SUM(je.amount)
74
SELECT SUM(je.amount)
75
FROM journal_entries je
75
FROM journal_entries je
76
JOIN chart_of_accounts ca ON je.account_id = ca.id
76
JOIN chart_of_accounts ca ON je.account_id = ca.id
77
JOIN account_types at ON ca.account_type_id = at.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
78
JOIN account_categories ac ON at.account_category_id = ac.id
79
JOIN transaction_headers tr ON je.transaction_id = tr.id
79
JOIN transaction_headers tr ON je.transaction_id = tr.id
80
WHERE ac.id = 5
80
WHERE ac.id = 5
81
AND tr.transaction_source_type = 2
81
AND tr.company_id = p_company_id
82
AND tr.company_id = p_company_id
82
AND ca.organization_id = v_organization_id
83
AND ca.organization_id = v_organization_id
83
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
84
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
84
AND je.is_deleted = FALSE
85
AND je.is_deleted = FALSE
85
), 0), 2),
86
), 0), 2),
86
87
87
-- Pending Dues
88
-- Pending Dues
88
ROUND(COALESCE((
89
ROUND(COALESCE((
89
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) -
90
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)
91
FROM journal_entries je
92
FROM journal_entries je
92
JOIN chart_of_accounts ca ON je.account_id = ca.id
93
JOIN chart_of_accounts ca ON je.account_id = ca.id
93
JOIN account_types at ON ca.account_type_id = at.id
94
JOIN account_types at ON ca.account_type_id = at.id
94
JOIN transaction_headers tr ON je.transaction_id = tr.id
95
JOIN transaction_headers tr ON je.transaction_id = tr.id
95
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
96
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
96
AND tr.company_id = p_company_id
97
AND tr.company_id = p_company_id
97
AND ca.organization_id = v_organization_id
98
AND ca.organization_id = v_organization_id
98
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
99
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
99
AND je.is_deleted = FALSE
100
AND je.is_deleted = FALSE
100
), 0), 2),
101
), 0), 2),
101
102
102
-- Pending Payments
103
-- Pending Payments
103
ROUND(COALESCE((
104
ROUND(COALESCE((
104
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) -
105
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)
106
FROM journal_entries je
107
FROM journal_entries je
107
JOIN chart_of_accounts ca ON je.account_id = ca.id
108
JOIN chart_of_accounts ca ON je.account_id = ca.id
108
JOIN account_types at ON ca.account_type_id = at.id
109
JOIN account_types at ON ca.account_type_id = at.id
109
JOIN transaction_headers tr ON je.transaction_id = tr.id
110
JOIN transaction_headers tr ON je.transaction_id = tr.id
110
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))
111
AND tr.company_id = p_company_id
112
AND tr.company_id = p_company_id
112
AND ca.organization_id = v_organization_id
113
AND ca.organization_id = v_organization_id
113
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
114
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
114
AND je.is_deleted = FALSE
115
AND je.is_deleted = FALSE
115
), 0), 2),
116
), 0), 2),
116
117
117
-- Previous Year Income
118
-- Previous Year Income
118
ROUND(COALESCE((
119
ROUND(COALESCE((
119
SELECT SUM(je.amount)
120
SELECT SUM(je.amount)
120
FROM journal_entries je
121
FROM journal_entries je
121
JOIN chart_of_accounts ca ON je.account_id = ca.id
122
JOIN chart_of_accounts ca ON je.account_id = ca.id
122
JOIN account_types at ON ca.account_type_id = at.id
123
JOIN account_types at ON ca.account_type_id = at.id
123
JOIN account_categories ac ON at.account_category_id = ac.id
124
JOIN account_categories ac ON at.account_category_id = ac.id
124
JOIN transaction_headers tr ON je.transaction_id = tr.id
125
JOIN transaction_headers tr ON je.transaction_id = tr.id
125
WHERE ac.id = 4
126
WHERE ac.id = 4
126
AND tr.company_id = p_company_id
127
AND tr.company_id = p_company_id
127
AND ca.organization_id = v_organization_id
128
AND ca.organization_id = v_organization_id
128
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
129
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
129
AND je.is_deleted = FALSE
130
AND je.is_deleted = FALSE
130
), 0), 2),
131
), 0), 2),
131
132
132
-- Previous Year Expense
133
-- Previous Year Expense
133
ROUND(COALESCE((
134
ROUND(COALESCE((
134
SELECT SUM(je.amount)
135
SELECT SUM(je.amount)
135
FROM journal_entries je
136
FROM journal_entries je
136
JOIN chart_of_accounts ca ON je.account_id = ca.id
137
JOIN chart_of_accounts ca ON je.account_id = ca.id
137
JOIN account_types at ON ca.account_type_id = at.id
138
JOIN account_types at ON ca.account_type_id = at.id
138
JOIN account_categories ac ON at.account_category_id = ac.id
139
JOIN account_categories ac ON at.account_category_id = ac.id
139
JOIN transaction_headers tr ON je.transaction_id = tr.id
140
JOIN transaction_headers tr ON je.transaction_id = tr.id
140
WHERE ac.id = 5
141
WHERE ac.id = 5
141
AND tr.company_id = p_company_id
142
AND tr.company_id = p_company_id
142
AND ca.organization_id = v_organization_id
143
AND ca.organization_id = v_organization_id
143
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
144
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
144
AND je.is_deleted = FALSE
145
AND je.is_deleted = FALSE
145
), 0), 2),
146
), 0), 2),
146
147
147
-- Previous Year Pending Dues
148
-- Previous Year Pending Dues
148
ROUND(COALESCE((
149
ROUND(COALESCE((
149
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) -
150
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)
151
FROM journal_entries je
152
FROM journal_entries je
152
JOIN chart_of_accounts ca ON je.account_id = ca.id
153
JOIN chart_of_accounts ca ON je.account_id = ca.id
153
JOIN account_types at ON ca.account_type_id = at.id
154
JOIN account_types at ON ca.account_type_id = at.id
154
JOIN transaction_headers tr ON je.transaction_id = tr.id
155
JOIN transaction_headers tr ON je.transaction_id = tr.id
155
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
156
WHERE at.name IN ('Accounts Receivable', 'Current Assets')
156
AND tr.company_id = p_company_id
157
AND tr.company_id = p_company_id
157
AND ca.organization_id = v_organization_id
158
AND ca.organization_id = v_organization_id
158
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
159
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
159
AND je.is_deleted = FALSE
160
AND je.is_deleted = FALSE
160
), 0), 2),
161
), 0), 2),
161
162
162
-- Previous Year Pending Payments
163
-- Previous Year Pending Payments
163
ROUND(COALESCE((
164
ROUND(COALESCE((
164
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) -
165
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)
166
FROM journal_entries je
167
FROM journal_entries je
167
JOIN chart_of_accounts ca ON je.account_id = ca.id
168
JOIN chart_of_accounts ca ON je.account_id = ca.id
168
JOIN account_types at ON ca.account_type_id = at.id
169
JOIN account_types at ON ca.account_type_id = at.id
169
JOIN transaction_headers tr ON je.transaction_id = tr.id
170
JOIN transaction_headers tr ON je.transaction_id = tr.id
170
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))
171
AND tr.company_id = p_company_id
172
AND tr.company_id = p_company_id
172
AND ca.organization_id = v_organization_id
173
AND ca.organization_id = v_organization_id
173
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
174
AND je.transaction_date BETWEEN v_prev_financial_year_start AND v_prev_financial_year_end
174
AND je.is_deleted = FALSE
175
AND je.is_deleted = FALSE
175
), 0), 2);
176
), 0), 2);
176
END;
177
END;
177
$function$
178
$function$
|
|||||
| 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_top_expense_categories | 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 | 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
DECLARE
5
DECLARE
6
v_transaction_id BIGINT;
6
v_transaction_id BIGINT;
7
v_transaction_record JSONB;
7
v_transaction_record JSONB;
8
v_journal_entry JSONB;
8
v_journal_entry JSONB;
9
v_narration_id BIGINT;
10
v_reference TEXT;
11
BEGIN
9
BEGIN
12
-- Loop through each transaction in the input array
10
-- Loop through each transaction in the input array
13
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)
14
LOOP
12
LOOP
15
-- Insert into transaction_headers
13
-- Insert into transaction_headers
16
INSERT INTO public.transaction_headers (
14
INSERT INTO public.transaction_headers (
17
company_id,
15
company_id,
18
customer_id,
16
customer_id,
19
vendor_id,
17
vendor_id,
20
employee_id,
18
employee_id,
21
transaction_date,
19
transaction_date,
22
transaction_source_type,
20
transaction_source_type,
23
status_id,
21
status_id,
24
document_id,
22
document_id,
25
document_number,
23
document_number,
26
description,
24
description,
27
created_by,
25
created_by,
28
created_on_utc
26
created_on_utc
29
)
27
)
30
VALUES(
28
VALUES(
31
(v_transaction_record->>'company_id')::uuid,
29
(v_transaction_record->>'company_id')::uuid,
32
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'),
33
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'),
34
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'),
35
(v_transaction_record->>'transaction_date')::date,
33
(v_transaction_record->>'transaction_date')::date,
36
(v_transaction_record->>'transaction_source_type')::integer,
34
(v_transaction_record->>'transaction_source_type')::integer,
37
(v_transaction_record->>'status_id')::integer,
35
(v_transaction_record->>'status_id')::integer,
38
(v_transaction_record->>'document_id')::uuid,
36
(v_transaction_record->>'document_id')::uuid,
39
v_transaction_record->>'document_number',
37
v_transaction_record->>'document_number',
40
v_transaction_record->>'description',
38
v_transaction_record->>'description',
41
(v_transaction_record->>'user_id')::uuid,
39
(v_transaction_record->>'user_id')::uuid,
42
CURRENT_TIMESTAMP
40
CURRENT_TIMESTAMP
43
)
41
)
44
RETURNING id INTO v_transaction_id;
42
RETURNING id INTO v_transaction_id;
45
43
46
-- ✅ Insert into payment_references if reference exists and not empty
44
47
v_reference := NULLIF(TRIM(v_transaction_record->>'reference'), '');
48
IF v_reference IS NOT NULL THEN
49
INSERT INTO public.payment_references (
50
transaction_id,
51
reference
52
)
53
VALUES (
54
v_transaction_id,
55
v_reference
56
);
57
END IF;
58
45
59
-- Loop through each journal entry associated with the transaction
46
-- Loop through each journal entry associated with the transaction
60
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')
61
LOOP
48
LOOP
62
-- Insert narration first
63
INSERT INTO public.journal_narrations (
64
transaction_date,
65
description_template_id,
66
dynamic_data,
67
created_by,
68
created_on_utc
69
)
70
VALUES(
71
(v_journal_entry->>'transaction_date')::timestamp,
72
(v_journal_entry->>'description_template_id')::integer,
73
COALESCE((v_journal_entry->>'dynamic_data'), ''),
74
(v_transaction_record->>'user_id')::uuid,
75
CURRENT_TIMESTAMP
76
)
77
RETURNING id INTO v_narration_id;
78
79
-- Insert journal entry
80
INSERT INTO public.journal_entries (
49
INSERT INTO public.journal_entries (
81
transaction_id,
50
transaction_id,
82
account_id,
51
account_id,
83
transaction_date,
52
transaction_date,
84
amount,
53
amount,
85
entry_type,
54
entry_type,
86
entry_source_id,
55
description_template_id,
87
journal_narration_id
56
dynamic_data,
57
entry_source_id
88
)
58
)
89
VALUES(
59
VALUES(
90
v_transaction_id,
60
v_transaction_id,
91
(v_journal_entry->>'account_id')::uuid,
61
(v_journal_entry->>'account_id')::uuid,
92
(v_journal_entry->>'transaction_date')::date,
62
(v_journal_entry->>'transaction_date')::date,
93
(v_journal_entry->>'amount')::numeric,
63
(v_journal_entry->>'amount')::numeric,
94
(v_journal_entry->>'entry_type')::char,
64
(v_journal_entry->>'entry_type')::char,
95
(v_journal_entry->>'entry_source_id')::integer,
65
(v_journal_entry->>'description_template_id')::integer,
96
v_narration_id
66
(v_journal_entry->>'dynamic_data')::jsonb,
67
(v_journal_entry->>'entry_source_id')::integer
97
);
68
);
98
END LOOP;
69
END LOOP;
99
70
100
-- Return transaction ID, document ID, and reference for this transaction
71
-- Return transaction ID and document ID for this transaction
101
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;
102
END LOOP;
73
END LOOP;
103
104
EXCEPTION
74
EXCEPTION
75
-- Catch any error and raise an exception
105
WHEN OTHERS THEN
76
WHEN OTHERS THEN
106
RAISE EXCEPTION 'Error occurred: %', SQLERRM;
77
RAISE EXCEPTION 'Error occurred: %', SQLERRM;
107
END;
78
END;
108
$function$
79
$function$
|
|||||
| 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 | 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$
|
|||||
| Procedure | delete_journal_entries_by_accounts | Match | ||||||
| Procedure | copy_user_permissions | Match | ||||||
| Procedure | create_company1 | Match | ||||||
| Procedure | delete_journal_entries_by_account | Match | ||||||
| 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 | create_organization | Match | ||||||
| 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_company | Match | ||||||
| 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
25
26
-- Get city_id
26
-- Get city_id
27
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);
28
28
29
-- Get address_id
29
-- Get address_id
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);
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);
31
31
32
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
33
33
34
-- Create and get user_id
34
-- Create and get user_id
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);
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);
36
RAISE NOTICE 'Generated User ID: %',v_user_company_id;
36
RAISE NOTICE 'Generated User ID: %',v_user_company_id;
37
37
38
-- 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
39
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;
40
40
41
-- Check if organization exists by any unique identifier
41
-- Check if organization exists by any unique identifier
42
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
43
-- Only create organization if no matching record exists
43
-- Only create organization if no matching record exists
44
CALL public.create_organization(
44
CALL public.create_organization(
45
p_id,
45
p_id,
46
p_name,
46
p_name,
47
p_description,
47
p_description,
48
p_phone_number,
48
p_phone_number,
49
p_email,
49
p_email,
50
p_address_line1,
50
p_address_line1,
51
p_gstin,
51
p_gstin,
52
p_tag_line,
52
p_tag_line,
53
v_city_id,
53
v_city_id,
54
p_short_name,
54
p_short_name,
55
p_pan,
55
p_pan,
56
p_tan,
56
p_tan,
57
p_created_by
57
p_created_by
58
);
58
);
59
59
60
RAISE NOTICE 'Organization ID % successfully inserted.', p_id;
60
RAISE NOTICE 'Organization ID % successfully inserted.', p_id;
61
ELSE
61
ELSE
62
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;
63
END IF;
63
END IF;
64
64
65
-- 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
66
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);
67
67
68
-- Fetch the permission_id for 'Dhanman.Admin'
68
-- Fetch the permission_id for 'Dhanman.Admin'
69
SELECT array_agg(id) INTO v_permission_ids
69
SELECT id INTO v_permission_id
70
FROM permissions
70
FROM permissions
71
WHERE name = ADMIN_PERMISSION;
71
WHERE name = ADMIN_PERMISSION;
72
72
73
CALL public.insert_user_permission(
73
CALL public.insert_user_permission(
74
v_user_id ,
74
v_user_id ,
75
p_id ,
75
p_id ,
76
v_permission_ids,
76
v_permission_id,
77
p_created_by
77
p_created_by
78
);
78
);
79
79
80
-- Assign all default users and default permissions as admin
80
-- Assign all default users and default permissions as admin
81
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);
82
82
83
-- Iterate through the company names and GUIDs
83
-- Iterate through the company names and GUIDs
84
FOR i IN 1..array_length(v_company_names, 1) LOOP
84
FOR i IN 1..array_length(v_company_names, 1) LOOP
85
v_company_name := v_company_names[i];
85
v_company_name := v_company_names[i];
86
v_company_guid := v_company_guids[i];
86
v_company_guid := v_company_guids[i];
87
87
88
-- Call initialize_company for each company
88
-- Call initialize_company for each company
89
CALL public.initialize_company(
89
CALL public.initialize_company(
90
v_company_guid,
90
v_company_guid,
91
p_id,
91
p_id,
92
v_company_name,
92
v_company_name,
93
p_description,
93
p_description,
94
p_gstin,
94
p_gstin,
95
p_pan,
95
p_pan,
96
p_tan,
96
p_tan,
97
'INR',
97
'INR',
98
p_short_name,
98
p_short_name,
99
p_tag_line,
99
p_tag_line,
100
p_name,
100
p_name,
101
p_phone_number,
101
p_phone_number,
102
p_email,
102
p_email,
103
p_created_by,
103
p_created_by,
104
v_user_id,
104
v_user_id,
105
p_default_company_id
105
p_default_company_id
106
);
106
);
107
107
108
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;
109
END LOOP;
109
END LOOP;
110
110
111
-- 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
112
CALL public.initialize_org_coa(
112
CALL public.initialize_org_coa(
113
DEFAULT_ORGANIZATION_ID, -- Old organization ID
113
DEFAULT_ORGANIZATION_ID, -- Old organization ID
114
p_id, -- New organization ID
114
p_id, -- New organization ID
115
p_company_guids, -- New Company Id
115
p_company_guids, -- New Company Id
116
p_created_by
116
p_created_by
117
);
117
);
118
118
119
-- Print a confirmation after the chart_of_accounts is copied
119
-- Print a confirmation after the chart_of_accounts is copied
120
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;
121
121
122
END;
122
END;
123
$procedure$
123
$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 | 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
BEGIN
8
BEGIN
8
-- Get 'System' user or fallback
9
-- Use system user or default to a fallback ID if required
9
SELECT id INTO v_created_by
10
SELECT id INTO v_created_by
10
FROM public.users
11
FROM users
11
WHERE first_name = 'System'
12
WHERE first_name = 'System'
12
LIMIT 1;
13
LIMIT 1;
13
14
14
IF v_created_by IS NULL THEN
15
-- Loop through each bank statement in the JSONB array
15
RAISE NOTICE '⚠️ No System user found. Using NULL created_by.';
16
END IF;
17
18
-- Loop through input JSON
19
FOR stmt IN
16
FOR stmt IN
20
SELECT * FROM jsonb_to_recordset(p_statements) AS (
17
SELECT * FROM jsonb_to_recordset(p_statements) AS (
21
organization_id UUID,
18
company_id UUID,
22
txn_date TIMESTAMP,
19
txn_date DATE,
23
cheque_number TEXT,
20
cheque_number TEXT,
24
description TEXT,
21
description TEXT,
25
value_date TIMESTAMP,
22
value_date DATE,
26
branch_code TEXT,
23
branch_code TEXT,
27
debit_amount NUMERIC,
24
debit_amount NUMERIC,
28
credit_amount NUMERIC,
25
credit_amount NUMERIC,
29
balance NUMERIC,
26
balance NUMERIC,
30
bank_id UUID,
27
bank_id UUID
31
created_by UUID
32
)
28
)
33
LOOP
29
LOOP
34
BEGIN
30
BEGIN
35
-- Check for existing record (ORG + BANK scoped)
31
-- Check for existing bank statement with same txn_date and description
36
IF NOT EXISTS (
32
IF NOT EXISTS (
37
SELECT 1
33
SELECT 1
38
FROM public.bank_statements bs
34
FROM public.bank_statements
39
WHERE bs.organization_id = stmt.organization_id
35
WHERE company_id = stmt.company_id
40
AND bs.bank_id = stmt.bank_id
36
AND bank_id = stmt.bank_id
41
AND bs.is_deleted = FALSE
37
AND txn_date = stmt.txn_date
42
AND (
38
AND description = stmt.description
43
(stmt.txn_date::time <> '00:00:00'
39
AND is_deleted = false
44
AND bs.txn_date = stmt.txn_date)
45
OR
46
(stmt.txn_date::time = '00:00:00'
47
AND bs.txn_date::date = stmt.txn_date::date)
48
)
49
AND COALESCE(bs.debit_amount, 0) = COALESCE(stmt.debit_amount, 0)
50
AND COALESCE(bs.credit_amount, 0) = COALESCE(stmt.credit_amount, 0)
51
AND COALESCE(bs.cheque_number, '') = COALESCE(stmt.cheque_number, '')
52
AND COALESCE(bs.description, '') = COALESCE(stmt.description, '')
53
) THEN
40
) THEN
54
-- INSERT
41
-- Insert new bank statement
55
INSERT INTO public.bank_statements (
42
INSERT INTO public.bank_statements (
56
organization_id,
43
company_id,
57
bank_id,
58
txn_date,
44
txn_date,
59
cheque_number,
45
cheque_number,
60
description,
46
description,
61
value_date,
47
value_date,
62
branch_code,
48
branch_code,
63
debit_amount,
49
debit_amount,
64
credit_amount,
50
credit_amount,
65
balance,
51
balance,
66
has_reconciled,
52
bank_id,
67
created_by,
53
created_by,
68
created_on_utc,
54
created_on_utc,
69
is_deleted
55
is_deleted,
70
)
56
has_reconciled
71
VALUES (
57
) VALUES (
72
stmt.organization_id,
58
stmt.company_id,
73
stmt.bank_id,
74
stmt.txn_date,
59
stmt.txn_date,
75
stmt.cheque_number,
60
stmt.cheque_number,
76
stmt.description,
61
stmt.description,
77
stmt.value_date,
62
stmt.value_date,
78
stmt.branch_code,
63
stmt.branch_code,
79
stmt.debit_amount,
64
stmt.debit_amount,
80
stmt.credit_amount,
65
stmt.credit_amount,
81
stmt.balance,
66
stmt.balance,
82
FALSE,
67
stmt.bank_id,
83
COALESCE(stmt.created_by, v_created_by),
68
v_created_by,
84
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
69
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
85
FALSE
70
false,
71
false
86
);
72
);
87
73
88
RAISE NOTICE '✅ Inserted: %, %', stmt.txn_date, stmt.description;
74
RAISE NOTICE 'Inserted bank statement for date: %, company: %', stmt.txn_date, stmt.company_id;
89
90
ELSE
75
ELSE
91
-- UPDATE existing
76
-- Update the existing statement
92
UPDATE public.bank_statements
77
UPDATE public.bank_statements
93
SET
78
SET
94
cheque_number = stmt.cheque_number,
79
cheque_number = stmt.cheque_number,
95
value_date = stmt.value_date,
80
value_date = stmt.value_date,
96
branch_code = stmt.branch_code,
81
branch_code = stmt.branch_code,
82
debit_amount = stmt.debit_amount,
83
credit_amount = stmt.credit_amount,
97
balance = stmt.balance,
84
balance = stmt.balance,
98
modified_by = COALESCE(stmt.created_by, v_created_by),
85
modified_by = v_created_by,
99
modified_on_utc = (CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp
86
modified_on_utc = (CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp
100
WHERE organization_id = stmt.organization_id
87
WHERE company_id = stmt.company_id
101
AND bank_id = stmt.bank_id
88
AND bank_id = stmt.bank_id
102
AND is_deleted = FALSE
89
AND txn_date = stmt.txn_date
103
AND (
90
AND description = stmt.description
104
(stmt.txn_date::time <> '00:00:00'
91
AND is_deleted = false;
105
AND txn_date = stmt.txn_date)
106
OR
107
(stmt.txn_date::time = '00:00:00'
108
AND txn_date::date = stmt.txn_date::date)
109
)
110
AND COALESCE(debit_amount, 0) = COALESCE(stmt.debit_amount, 0)
111
AND COALESCE(credit_amount, 0) = COALESCE(stmt.credit_amount, 0)
112
AND COALESCE(cheque_number, '') = COALESCE(stmt.cheque_number, '')
113
AND COALESCE(description, '') = COALESCE(stmt.description, '');
114
92
115
RAISE NOTICE '🔄 Updated: %, %', stmt.txn_date, stmt.description;
93
RAISE NOTICE 'Updated existing bank statement for date: %, company: %', stmt.txn_date, stmt.company_id;
116
END IF;
94
END IF;
117
95
118
EXCEPTION
96
EXCEPTION
119
WHEN OTHERS THEN
97
WHEN OTHERS THEN
120
RAISE EXCEPTION
98
RAISE EXCEPTION 'Error in processing statement for txn_date: %, description: %, Error: %', stmt.txn_date, stmt.description, SQLERRM;
121
'❌ Error for org=%, txn_date=%, desc=%, Error: %',
122
stmt.organization_id,
123
stmt.txn_date,
124
stmt.description,
125
SQLERRM;
126
END;
99
END;
127
END LOOP;
100
END LOOP;
128
101
129
RAISE NOTICE '🎉 All bank statements processed successfully.';
102
RAISE NOTICE 'All bank statements processed successfully';
130
END;
103
END;
131
$procedure$
104
$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$
|
|||||
| 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: 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
-- 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: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"));
-- Indexes: MissingInTarget
CREATE UNIQUE INDEX uq_users_email ON public.users USING btree (email)
-- Table: users_bk
Exists in source, missing in target
-- Table: v_permission_id
Exists in source, missing in target
-- 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: bank_reconciliation_links
Exists in source, missing in target
-- Table: temp_paymnet_data
Exists in source, missing in target
-- Table: bank_reconciliation_stagings
Exists in source, missing in target
-- Table: payment_references
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: 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:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.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:
-- 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_description_template_id" FOREIGN KEY (description_template_id) REFERENCES description_templates(id);
-- 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: bank_reconciliations
Exists in source, missing in target
-- Table: feature_toggles
Exists in source, missing in target
-- Table: user_permissions_backup
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:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.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:
-- 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_description_template_id" FOREIGN KEY (description_template_id) REFERENCES description_templates(id);
-- Table: feature_toggle_overrides
Exists in source, missing in target
-- Table: user_permission_sync_runs
Exists in source, missing in target
-- Table: user_permission_sync_deltas
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:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.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:
-- 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_description_template_id" FOREIGN KEY (description_template_id) REFERENCES description_templates(id);
-- 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:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.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:
-- 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_description_template_id" FOREIGN KEY (description_template_id) REFERENCES description_templates(id);
-- 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:entry_source_id→entry_sources.id|FK:journal_narration_id→journal_narrations.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:
-- 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_description_template_id" FOREIGN KEY (description_template_id) REFERENCES description_templates(id);
-- Table: actions
Exists in source, missing in target
-- Table: feature_toggle_audits
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::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:True:::False|COL:customer_advance_account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:customer_security_deposit_account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid: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::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:employee_loan_account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:esi_payable_employee_contribution_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:esi_payable_employer_contribution_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid: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:lwf_payable_employee_contribution_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:lwf_payable_employer_contribution_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:opening_balance_equity_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid: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:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:pf_payable_employer_contribution_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_on_contractors_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:tds_on_salaries_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid: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|COL:vendor_advance_account_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:vendor_security_deposit_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:
-- 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 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, "customer_advance_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "customer_security_deposit_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "employee_advance_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "employee_loan_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "vendor_advance_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "vendor_security_deposit_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "esi_payable_employee_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "esi_payable_employer_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "lwf_payable_employee_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "lwf_payable_employer_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "pf_payable_employee_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "pf_payable_employer_contribution_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "tds_on_contractors_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "opening_balance_equity_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "tds_on_salaries_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, 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 NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "esi_payable_employer_contribution_account_id" uuid NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "lwf_payable_employee_contribution_account_id" uuid NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "lwf_payable_employer_contribution_account_id" uuid NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "pf_payable_employee_contribution_account_id" uuid NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "pf_payable_employer_contribution_account_id" uuid NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "tds_on_contractors_account_id" uuid NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "opening_balance_equity_account_id" uuid NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "organization_accounts" ADD COLUMN "tds_on_salaries_account_id" uuid NOT NULL;
-- 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:|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;
-- Table: audit_queries
Exists in source, missing in target
-- Table: audit_query_responses
Exists in source, missing in target
-- Table: audit_query_statuses
Exists in source, missing in target
-- 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: reconciliation_statuses
Exists in source, missing in target
-- 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: employees
Exists in source, missing in target
-- Table: email_notification_rules
Exists in source, missing in target
-- Table: email_audit_logs
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_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: get_bank_statements
CREATE OR REPLACE FUNCTION public.get_bank_statements(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 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.company_id,
bs.bank_id,
coa.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,
bs.has_reconciled,
bs.created_by,
bs.created_on_utc,
bs.modified_by,
bs.modified_on_utc,
bs.deleted_on_utc,
bs.is_deleted,
-- ✅ Finalized Reconciliation Status
COALESCE(br.reconciliation_status_id, 0) AS rec_status_id,
-- Determine rec_status based on staging status and confidence score
CASE
WHEN br.reconciliation_status_id IS NOT NULL THEN
COALESCE(rs.status::text, 'unmatched')
WHEN brs.confidence_score >= 70.99 THEN
'matched' -- Auto-match based on high confidence score
WHEN brs.status = 'pending' THEN
'pending' -- Pending review status
ELSE
'unmatched'
END AS rec_status,
-- Latest staging details (AI / manual 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
INNER JOIN public.chart_of_accounts coa
ON coa.id = bs.bank_id
AND coa.account_type_id = 9
AND coa.is_deleted = FALSE
INNER JOIN public.finance_year fy
ON fy.id = p_finyear_id
-- 🟢 Latest staging entry (AI / manual reconciliation suggestion)
LEFT JOIN LATERAL (
SELECT brs_inner.*
FROM public.bank_reconciliation_stagings brs_inner
WHERE brs_inner.company_id = bs.company_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 reconciliations
LEFT JOIN public.bank_reconciliations br
ON br.bank_statement_id = bs.id
AND br.company_id = bs.company_id
-- 🟢 Status lookup
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: 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: 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
-- 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;
-- Get organization_id for the given company_id
SELECT organization_id INTO v_organization_id
FROM public.companies
WHERE id = p_company_id;
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;
IF v_organization_id IS NULL THEN
RAISE EXCEPTION 'Company % not found or missing organization_id', p_company_id;
END IF;
IF p_period_type = CONST_MONTHLY THEN
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,
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
AND je.is_deleted = FALSE
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
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.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.entry_type = 'D'
GROUP BY months.year, months.month_num, months.period, coa.id, coa.name
ORDER BY months.year, months.month_num, coa.name;
ELSIF p_period_type = CONST_QUARTERLY THEN
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,
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
AND je.is_deleted = FALSE
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
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.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.entry_type = 'D'
GROUP BY quarters.period, year, coa.id, coa.name
ORDER BY year, quarters.period, coa.name;
ELSIF p_period_type = CONST_HALF_YEARLY THEN
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,
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
AND je.is_deleted = FALSE
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
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.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
AND je.entry_type = 'D'
GROUP BY half_years.period, year, coa.id, coa.name
ORDER BY year, half_years.period, coa.name;
ELSIF p_period_type = CONST_YEARLY THEN
RETURN QUERY
SELECT
'Year' AS period,
EXTRACT(YEAR FROM je.transaction_date) AS year,
coa.id AS 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
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.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;
ELSIF p_period_type = CONST_YTD THEN
RETURN QUERY
WITH ytd_fin_years AS (
SELECT fy.start_date, fy.end_date
FROM public.finance_year fy
WHERE EXTRACT(YEAR FROM fy.start_date) BETWEEN EXTRACT(YEAR FROM CURRENT_DATE) - 4 AND EXTRACT(YEAR FROM CURRENT_DATE)
ORDER BY fy.start_date DESC
LIMIT 5
)
SELECT
'YTD' AS period,
NULL::numeric AS year,
coa.id AS account_id,
coa.name AS account_name,
COALESCE(SUM(je.amount), 0) AS total_expense
FROM ytd_fin_years
LEFT JOIN public.journal_entries je
ON je.transaction_date BETWEEN ytd_fin_years.start_date AND LEAST(
ytd_fin_years.end_date,
CASE WHEN CURRENT_DATE BETWEEN ytd_fin_years.start_date AND ytd_fin_years.end_date
THEN CURRENT_DATE ELSE ytd_fin_years.end_date 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
LEFT JOIN public.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 (coa.name IS NULL OR coa.name NOT IN ('Rounding Gain'))
GROUP BY coa.id, coa.name
ORDER BY total_expense DESC;
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: 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: 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_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 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
-- Returns last 5 years, fiscal quarters (Apr–Jun, Jul–Sep, Oct–Dec, Jan–Mar)
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
-- For Q4, fiscal_year stays the same, but period_start/period_end move to the next calendar year
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 je.is_deleted = FALSE
AND tr.company_id = p_company_id
)
SELECT
CONCAT(q.period, ' ', q.fiscal_year) AS period,
q.fiscal_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) 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) 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) 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) 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
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
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 q.period_start AND q.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 q.period_start AND q.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 q.period_start AND q.period_end THEN je.amount ELSE 0 END) > 0
ORDER BY q.fiscal_year, q.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.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 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)
AND je.transaction_date >= m.month_start AND je.transaction_date < m.month_end THEN je.amount ELSE 0 END), 0) 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 >= m.month_start AND je.transaction_date < m.month_end THEN je.amount ELSE 0 END), 0) AS total_expense,
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
AND je.transaction_date >= m.month_start AND je.transaction_date < m.month_end THEN je.amount ELSE 0 END), 0) AS actual_income,
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
AND je.transaction_date >= m.month_start AND je.transaction_date < m.month_end THEN je.amount ELSE 0 END), 0) 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;
ELSIF p_period_type = CONST_QUARTERLY THEN
-- Single year, use explicit fiscal quarters
RETURN QUERY
WITH fiscal_quarters AS (
SELECT 'Q1' AS period, v_financial_year_start AS period_start, make_date(v_finance_year_start::int, 6, 30) AS period_end, v_finance_year_start AS year
UNION ALL
SELECT 'Q2', make_date(v_finance_year_start::int, 7, 1), make_date(v_finance_year_start::int, 9, 30), v_finance_year_start
UNION ALL
SELECT 'Q3', make_date(v_finance_year_start::int, 10, 1), make_date(v_finance_year_start::int, 12, 31), v_finance_year_start
UNION ALL
SELECT 'Q4', make_date((v_finance_year_start::int) + 1, 1, 1), v_financial_year_end, v_finance_year_start
),
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 je.is_deleted = FALSE
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
)
SELECT
CONCAT(fq.period, ' ', fq.year) AS period,
fq.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 fq.period_start AND fq.period_end THEN je.amount ELSE 0 END), 0) 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 fq.period_start AND fq.period_end THEN je.amount ELSE 0 END), 0) AS total_expense,
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN fq.period_start AND fq.period_end THEN je.amount ELSE 0 END), 0) AS actual_income,
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN fq.period_start AND fq.period_end THEN je.amount ELSE 0 END), 0) AS actual_expense
FROM fiscal_quarters fq
LEFT JOIN filtered_je je
ON je.transaction_date BETWEEN fq.period_start AND fq.period_end
GROUP BY fq.period, fq.year
ORDER BY fq.year, fq.period;
ELSIF p_period_type = CONST_HALF_YEARLY THEN
RETURN QUERY
WITH half_years AS (
SELECT 'H1' AS period, v_financial_year_start AS period_start, make_date(v_finance_year_start::int, 9, 30) AS period_end, v_finance_year_start AS year
UNION ALL
SELECT 'H2', make_date(v_finance_year_start::int, 10, 1), v_financial_year_end, v_finance_year_start
),
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 je.is_deleted = FALSE
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
)
SELECT
CONCAT(h.period, ' ', h.year) AS period,
h.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 h.period_start AND h.period_end THEN je.amount ELSE 0 END), 0) 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 h.period_start AND h.period_end THEN je.amount ELSE 0 END), 0) AS total_expense,
COALESCE(SUM(CASE WHEN je.entry_type = 'D' AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN h.period_start AND h.period_end THEN je.amount ELSE 0 END), 0) AS actual_income,
COALESCE(SUM(CASE WHEN je.entry_type = 'C' AND je.account_type_id IN (8,9)
AND je.transaction_date BETWEEN h.period_start AND h.period_end THEN je.amount ELSE 0 END), 0) AS actual_expense
FROM half_years h
JOIN filtered_je je
ON je.transaction_date BETWEEN h.period_start AND h.period_end
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.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 je.is_deleted = FALSE
AND je.transaction_date BETWEEN v_financial_year_start AND v_financial_year_end
)
SELECT
CONCAT('Year ', v_finance_year_start) AS period,
v_finance_year_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) 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) 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) 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) AS actual_expense
FROM filtered_je je;
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_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: 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_financial_year_start DATE;
v_financial_year_end DATE;
v_finance_year TEXT;
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
-- 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;
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;
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;
ELSIF p_period_type = CONST_YTD THEN
RETURN QUERY
WITH recent_years AS (
SELECT fy.id, fy.start_date, fy.end_date,
fy.year AS financial_year_text
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
),
base AS (
SELECT
coa.id AS account_id,
coa.name AS account_name,
coa.account_number,
ROUND(COALESCE(SUM(je.amount), 0), 2) AS total_expense,
ry.financial_year_text AS financial_year
FROM recent_years ry
LEFT JOIN public.journal_entries je
ON je.transaction_date BETWEEN ry.start_date AND LEAST(
ry.end_date,
CASE
WHEN CURRENT_DATE BETWEEN ry.start_date AND ry.end_date
THEN CURRENT_DATE
ELSE ry.end_date
END
)
LEFT JOIN public.transaction_headers th
ON je.transaction_id = th.id
AND th.company_id = p_company_id
LEFT 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.id IS NULL OR je.is_deleted = FALSE)
AND (coa.name IS NULL OR coa.name NOT IN ('Rounding Gain'))
GROUP BY coa.id, coa.name, coa.account_number, ry.financial_year_text
)
SELECT
base.account_id,
base.account_name,
base.account_number,
SUM(base.total_expense) AS total_expense,
base.financial_year
FROM base
GROUP BY base.account_id, base.account_name, base.account_number, base.financial_year
ORDER BY base.financial_year DESC, 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: 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_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_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.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$
-- 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_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: 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;
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;
-- ✅ 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: 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: 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$
-- 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, ',');
-- 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: 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: 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;
BEGIN
-- Get 'System' user or fallback
SELECT id INTO v_created_by
FROM public.users
WHERE first_name = 'System'
LIMIT 1;
IF v_created_by IS NULL THEN
RAISE NOTICE '⚠️ No System user found. Using NULL created_by.';
END IF;
-- Loop through input JSON
FOR stmt IN
SELECT * FROM jsonb_to_recordset(p_statements) AS (
organization_id UUID,
txn_date TIMESTAMP,
cheque_number TEXT,
description TEXT,
value_date TIMESTAMP,
branch_code TEXT,
debit_amount NUMERIC,
credit_amount NUMERIC,
balance NUMERIC,
bank_id UUID,
created_by UUID
)
LOOP
BEGIN
-- Check for existing record (ORG + BANK scoped)
IF NOT EXISTS (
SELECT 1
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, '')
) THEN
-- INSERT
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,
COALESCE(stmt.created_by, v_created_by),
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
FALSE
);
RAISE NOTICE '✅ Inserted: %, %', stmt.txn_date, stmt.description;
ELSE
-- UPDATE existing
UPDATE public.bank_statements
SET
cheque_number = stmt.cheque_number,
value_date = stmt.value_date,
branch_code = stmt.branch_code,
balance = stmt.balance,
modified_by = COALESCE(stmt.created_by, v_created_by),
modified_on_utc = (CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp
WHERE organization_id = stmt.organization_id
AND bank_id = stmt.bank_id
AND is_deleted = FALSE
AND (
(stmt.txn_date::time <> '00:00:00'
AND txn_date = stmt.txn_date)
OR
(stmt.txn_date::time = '00:00:00'
AND txn_date::date = stmt.txn_date::date)
)
AND COALESCE(debit_amount, 0) = COALESCE(stmt.debit_amount, 0)
AND COALESCE(credit_amount, 0) = COALESCE(stmt.credit_amount, 0)
AND COALESCE(cheque_number, '') = COALESCE(stmt.cheque_number, '')
AND COALESCE(description, '') = COALESCE(stmt.description, '');
RAISE NOTICE '🔄 Updated: %, %', stmt.txn_date, stmt.description;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION
'❌ Error for org=%, txn_date=%, desc=%, Error: %',
stmt.organization_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$
-- 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$
-- 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()