| Type | Name | Status | PK | FK | Columns | Index | Script | Diff Script |
|---|---|---|---|---|---|---|---|---|
| Table | company_defaultor_configurations | Match | ||||||
| Table | draft_invoice_details | Match | ||||||
| Table | draft_invoice_header_ids | Match | ||||||
| Table | invoice_approval_user_company | Mismatch |
|
|||||
| Table | invoice_payment_header_ids | Match | ||||||
| Table | invoice_penalties | Match | ||||||
| Table | invoice_status_company_configs | Match | ||||||
| Table | invoice_header_ids | Mismatch |
|
|||||
| Table | penalty_processing_logs | Match | ||||||
| Table | schedule_execution_logs | Match | ||||||
| Table | customer_note_statuses | Mismatch |
Source Script
Target Script
1
CREATE TABLE "customer_note_statuses" ("id" integer NOT NULL, "name" text 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"));
1
CREATE TABLE "customer_note_statuses" ("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 | customer_upis | Mismatch |
|
|||||
| Table | record_exists | Match | ||||||
| Table | temp_invoice_next_status | Match | ||||||
| Table | invoice_statuses | Mismatch |
|
|||||
| Table | invoice_voucher_ids | Match | ||||||
| Table | customers | Mismatch |
Source Script
Target Script
1
CREATE TABLE "customers" ("id" uuid NOT NULL, "name" varchar(100) NOT NULL, "company_id" uuid NOT NULL, "billing_address_id" uuid, "shipping_address_id" uuid, "gstin" text, "pan" text, "tan" text, "short_name" text, "proprietor_name" text, "outstanding_limit" numeric(,), "is_non_work" boolean, "interest_percentage" numeric(,), "has_gstin" boolean DEFAULT false, "opening_balance" numeric(,), "balance_type" varchar(), "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, "member_type_id" integer DEFAULT 0 NOT NULL, "default_account_id" uuid, PRIMARY KEY ("id"));
1
CREATE TABLE "customers" ("id" uuid NOT NULL, "name" varchar(100) NOT NULL, "company_id" uuid NOT NULL, "billing_address_id" uuid, "shipping_address_id" uuid, "gstin" text, "short_name" text, "pan" text, "tan" text, "proprietor_name" text, "outstanding_limit" numeric(,), "is_non_work" boolean, "interest_percentage" numeric(,), "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, "has_gstin" boolean DEFAULT false, "opening_balance" numeric(9,2), "balance_type" varchar(), "member_type_id" integer DEFAULT 0 NOT NULL, "default_account_id" uuid, PRIMARY KEY ("id"));
|
|||||
| Table | draft_invoice_headers | Mismatch |
Source Script
Target Script
1
CREATE TABLE "draft_invoice_headers" ("id" uuid NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "currency_id" integer DEFAULT 0 NOT NULL, "invoice_voucher" text, "invoice_date" timestamp without time zone NOT NULL, "payment_term" integer NOT NULL, "invoice_status_id" integer NOT NULL, "due_date" timestamp without time zone NOT NULL, "total_amount" numeric(,) NOT NULL, "settled_amount" numeric(,) DEFAULT 0.0, "taxable_amount" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,), "sgst_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "round_off" numeric(,) NOT NULL, "note" text NOT NULL, "debit_account_id" uuid NOT NULL, "credit_account_id" uuid NOT NULL, "so_no" text, "so_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "source_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "destination_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::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, "invoice_number" text NOT NULL, "type" integer NOT NULL, "is_editable" boolean, "penalty_config_id" integer, "is_created_by_scheduler" boolean DEFAULT false NOT NULL, "fin_entry_status_id" integer DEFAULT 1, "is_gate_pass_created" boolean DEFAULT false, "current_approval_level" integer DEFAULT 0 NOT NULL, "payment_status_id" integer DEFAULT 0 NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "draft_invoice_headers" ("id" uuid NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "currency_id" integer DEFAULT 0 NOT NULL, "invoice_number" text NOT NULL, "invoice_voucher" text, "invoice_date" timestamp without time zone NOT NULL, "payment_term" integer NOT NULL, "invoice_status_id" integer NOT NULL, "due_date" timestamp without time zone NOT NULL, "total_amount" numeric(,) NOT NULL, "setteled_amount" numeric(,) DEFAULT 0.0, "taxable_amount" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,), "sgst_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "round_off" numeric(,) NOT NULL, "note" text NOT NULL, "debit_account_id" uuid NOT NULL, "credit_account_id" uuid NOT NULL, "so_no" text, "so_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "source_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "destination_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::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, "type" integer NOT NULL, "is_editable" boolean, "penalty_config_id" integer, "is_created_by_scheduler" boolean DEFAULT false NOT NULL, "fin_entry_status_id" integer DEFAULT 1, "is_gate_pass_created" boolean DEFAULT false, "current_approval_level" integer DEFAULT 0 NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | organizations | Match | ||||||
| Table | schema_versions | Mismatch |
|
|||||
| Table | audit_queue_invoice_payment_headers | Match | ||||||
| Table | upis | Mismatch |
Source Script
Target Script
1
CREATE TABLE "upis" ("id" uuid NOT NULL, "upi_id" uuid NOT NULL, "rx_fund_account_id" text, "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"));
1
CREATE TABLE "upis" ("id" uuid NOT NULL, "upi_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, "rx_fund_account_id" text, PRIMARY KEY ("id"));
|
|||||
| 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, "phone_number" varchar(256) DEFAULT ''::character varying NOT NULL, "password_hash" text, "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, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid 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, "phone_number" varchar(256) DEFAULT ''::character varying NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | v_created_by | Match | ||||||
| Table | v_invoice_status_id | Match | ||||||
| Table | warehouses | Mismatch |
Source Script
Target Script
1
CREATE TABLE "warehouses" ("id" uuid NOT NULL, "name" text NOT NULL, "customer_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "address_id" uuid NOT NULL, "gstin" text 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"));
1
CREATE TABLE "warehouses" ("id" uuid NOT NULL, "name" text NOT NULL, "address_id" uuid NOT NULL, "gstin" text 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, "customer_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | invoice_approval_users_account | Mismatch |
|
|||||
| Table | invoice_approval_logs | Match | ||||||
| Table | addresses | Mismatch |
|
|||||
| Table | audit_queue_invoice_details | Match | ||||||
| Table | customer_contacts | Mismatch |
|
|||||
| Table | customer_note_headers | Mismatch |
Source Script
Target Script
1
CREATE TABLE "customer_note_headers" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "company_id" uuid NOT NULL, "credit_account_id" uuid NOT NULL, "debit_account_id" uuid NOT NULL, "customer_note_status_id" integer NOT NULL, "note_date" timestamp without time zone NOT NULL, "invoice_id" uuid NOT NULL, "is_debit_note" boolean NOT NULL, "fees" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "sgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "note" text, "round_off" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "discount" numeric(,) NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "customer_note_headers" ("id" uuid NOT NULL, "is_debit_note" boolean NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "note_date" timestamp without time zone NOT NULL, "invoice_id" uuid NOT NULL, "credit_account_id" uuid NOT NULL, "debit_account_id" uuid NOT NULL, "customer_note_status_id" integer NOT NULL, "fees" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "sgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "round_off" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "note" text, "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 | customers_audit | Mismatch |
|
|||||
| Table | gate_pass_details | Match | ||||||
| Table | penalty_frequencies | Match | ||||||
| Table | payment_statuses | Missing in Target |
|
|||||
| Table | group_invoice_headers | Match | ||||||
| Table | invoice_account_approval_levels | Match | ||||||
| Table | invoice_workflow | Mismatch |
|
|||||
| Table | invoice_payment_details | Mismatch |
Source Script
Target Script
1
CREATE TABLE "invoice_payment_details" ("id" uuid NOT NULL, "invoice_payment_header_id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "received_amount" numeric(,) NOT NULL, "serial_number" integer DEFAULT 0 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, "tds_amount" numeric(,) DEFAULT 0.0 NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "invoice_payment_details" ("id" uuid NOT NULL, "invoice_payment_header_id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "received_amount" numeric(,) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "created_by" uuid NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "serial_number" integer DEFAULT 0 NOT NULL, "modified_by" uuid, "tds_amount" numeric(,) DEFAULT 0.0 NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | penalty_configs | Match | ||||||
| Table | roles | Mismatch |
|
|||||
| Table | states | Mismatch |
|
|||||
| Table | user_roles | Mismatch |
|
|||||
| Table | audit_queue_invoice_headers | Match | ||||||
| Table | recurring_sales_schedules | Mismatch |
Source Script
Target Script
1
CREATE TABLE "recurring_sales_schedules" ("id" uuid NOT NULL, "invoice_header_id" uuid, "schedule_status" text NOT NULL, "starts_from" timestamp without time zone NOT NULL, "end_date" timestamp without time zone 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, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "default_status" integer DEFAULT 1 NOT NULL, "frequency_cycle" text NOT NULL, "draft_invoice_header_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, PRIMARY KEY ("id"));
1
CREATE TABLE "recurring_sales_schedules" ("id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "frequency_cycle" text NOT NULL, "schedule_status" text NOT NULL, "starts_from" timestamp without time zone NOT NULL, "end_date" timestamp without time zone 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, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "default_status" integer DEFAULT 1 NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | invoice_payment_headers | Mismatch |
Source Script
Target Script
1
CREATE TABLE "invoice_payment_headers" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "mode_of_payment" text DEFAULT ''::text NOT NULL, "reference" text DEFAULT ''::text NOT NULL, "credit_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "received_date" timestamp without time zone NOT NULL, "received_amount" numeric(,) NOT NULL, "grand_total_amount" numeric(,) NOT NULL, "advance_amount" numeric(,) NOT NULL, "description" text NOT NULL, "debit_account_id" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "created_by" uuid NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "modified_by" uuid, "tds_amount" numeric(,) DEFAULT 0.0 NOT NULL, "payment_number" text DEFAULT ''::text NOT NULL, "is_posted" boolean DEFAULT false NOT NULL, "transaction_id" bigint, "payment_status_id" integer DEFAULT 0 NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "invoice_payment_headers" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "received_date" timestamp without time zone NOT NULL, "received_amount" numeric(,) NOT NULL, "grand_total_amount" numeric(,) NOT NULL, "advance_amount" numeric(,) NOT NULL, "description" text NOT NULL, "debit_account_id" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "created_by" uuid NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "mode_of_payment" text DEFAULT ''::text NOT NULL, "reference" text DEFAULT ''::text NOT NULL, "credit_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "modified_by" uuid, "tds_amount" numeric(,) DEFAULT 0.0 NOT NULL, "payment_number" text DEFAULT ''::text NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | invoice_headers | Mismatch |
Source Script
Target Script
1
CREATE TABLE "invoice_headers" ("id" uuid NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "destination_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "source_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "invoice_number" text NOT NULL, "invoice_voucher" text, "invoice_date" timestamp without time zone NOT NULL, "payment_term" integer NOT NULL, "invoice_status_id" integer NOT NULL, "due_date" timestamp without time zone NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,), "sgst_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "currency_id" integer DEFAULT 0 NOT NULL, "so_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone, "so_no" text, "round_off" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "settled_amount" numeric(,) DEFAULT 0.0, "note" text NOT NULL, "debit_account_id" uuid NOT NULL, "credit_account_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, "type" integer NOT NULL, "is_editable" boolean, "penalty_config_id" integer, "is_created_by_scheduler" boolean DEFAULT false NOT NULL, "is_gate_pass_created" boolean DEFAULT false NOT NULL, "fin_entry_status_id" integer DEFAULT 1, "current_approval_level" integer DEFAULT 0 NOT NULL, "payment_status_id" integer DEFAULT 1 NOT NULL, "is_posted" boolean DEFAULT false NOT NULL, "transaction_id" bigint, PRIMARY KEY ("id"));
1
CREATE TABLE "invoice_headers" ("id" uuid NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "invoice_number" text NOT NULL, "invoice_voucher" text, "invoice_date" timestamp without time zone NOT NULL, "payment_term" integer NOT NULL, "invoice_status_id" integer NOT NULL, "due_date" timestamp without time zone NOT NULL, "total_amount" numeric(,) NOT NULL, "setteled_amount" numeric(,) DEFAULT 0.0, "taxable_amount" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,), "sgst_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "round_off" numeric(,) NOT NULL, "note" text NOT NULL, "debit_account_id" uuid NOT NULL, "credit_account_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, "currency_id" integer DEFAULT 0 NOT NULL, "so_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone, "so_no" text, "destination_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "source_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "type" integer NOT NULL, "is_editable" boolean, "penalty_config_id" integer, "is_created_by_scheduler" boolean DEFAULT false NOT NULL, "is_gate_pass_created" boolean DEFAULT false NOT NULL, "fin_entry_status_id" integer DEFAULT 1, "current_approval_level" integer DEFAULT 0 NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | audit_queue | Match | ||||||
| Table | customer_bank_accounts | Mismatch |
|
|||||
| Table | bank_accounts | Match | ||||||
| Table | contacts | Mismatch |
Source Script
Target Script
1
CREATE TABLE "contacts" ("id" uuid NOT NULL, "salutation" text NOT NULL, "first_name" text NOT NULL, "last_name" text, "email" text, "phone_number" text, "mobile_number" text, "rx_contact_id" text, "is_primary" boolean DEFAULT false 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"));
1
CREATE TABLE "contacts" ("id" uuid NOT NULL, "salutation" text NOT NULL, "first_name" text NOT NULL, "last_name" text, "email" text, "phone_number" text, "mobile_number" text, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "is_primary" boolean DEFAULT false NOT NULL, "rx_contact_id" text, PRIMARY KEY ("id"));
|
|||||
| Table | batch_schedules | Mismatch |
Source Script
Target Script
1
CREATE TABLE "batch_schedules" ("id" integer NOT NULL, "group_invoice_template_id" uuid NOT NULL, "billing_cycle" text NOT NULL, "day_of_week" integer, "day_of_month" integer, "month_of_year" integer, "time_of_day" interval NOT NULL, "last_executed_at" timestamp without time zone, "half_year" integer, "quarters_of_month" integer, "bi_month" integer, "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, PRIMARY KEY ("id"));
1
CREATE TABLE "batch_schedules" ("id" integer NOT NULL, "group_invoice_template_id" uuid NOT NULL, "billing_cycle" text NOT NULL, "day_of_week" integer, "day_of_month" integer, "month_of_year" integer, "time_of_day" interval NOT NULL, "last_executed_at" timestamp without time zone, "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, "half_year" integer, "quarters_of_month" integer, "bi_month" integer, PRIMARY KEY ("id"));
|
|||||
| Table | __EFMigrationsHistory | Mismatch |
Source Script
Target Script
1
CREATE TABLE "__EFMigrationsHistory" ("migration_id" varchar(150) NOT NULL, "product_version" varchar(32) NOT NULL);
1
CREATE TABLE "__EFMigrationsHistory" ("migration_id" varchar(150) NOT NULL, "product_version" varchar(32) NOT NULL, PRIMARY KEY ("migration_id"));
|
|||||
| Table | cities | Mismatch |
|
|||||
| Table | countries | Mismatch |
Source Script
Target Script
1
CREATE TABLE "countries" ("id" uuid NOT NULL, "name" text NOT NULL, "iso_alpha_code" text 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"));
1
CREATE TABLE "countries" ("id" uuid NOT NULL, "name" text NOT NULL, "iso_alpha_code" 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 | banks | Mismatch |
|
|||||
| Table | companies | Match | ||||||
| Table | customer_default_accounts | Match | ||||||
| Table | customer_note_details | Mismatch |
Source Script
Target Script
1
CREATE TABLE "customer_note_details" ("id" uuid NOT NULL, "customer_note_header_id" uuid NOT NULL, "product_id" uuid NOT NULL, "quantity" integer NOT NULL, "price" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "sgst_amount" numeric(,) NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "customer_note_details" ("id" uuid NOT NULL, "customer_note_header_id" uuid NOT NULL, "product_id" uuid NOT NULL, "quantity" integer NOT NULL, "price" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "sgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | customer_note_work_flows | Mismatch |
|
|||||
| Table | gate_pass_headers | Match | ||||||
| Table | gate_pass_statuses | Match | ||||||
| Table | delinquency_cycles | Missing in Target |
|
|||||
| Table | delinquency_snapshots | Missing in Target |
|
|||||
| Table | collection_policies | Missing in Target |
|
|||||
| Table | delinquency_discussion_messages | Missing in Target |
|
|||||
| Table | group_invoice_header_ids | Match | ||||||
| Table | group_invoice_details | Mismatch |
|
|||||
| Table | group_invoice_template_customers | Match | ||||||
| Table | v_user_id | Missing in Target |
|
|||||
| Table | delinquency_actions | Missing in Target |
|
|||||
| Table | delinquency_resolution_window | Missing in Target |
|
|||||
| Table | delinquency_snapshot_invoices | Missing in Target |
|
|||||
| Table | group_invoice_templates | Match | ||||||
| Table | invoice_approval_issue_log | Mismatch |
|
|||||
| Table | invoice_details | Mismatch |
Source Script
Target Script
1
CREATE TABLE "invoice_details" ("id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "price" numeric(,) NOT NULL, "quantity" numeric(,) NOT NULL, "product_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "cgst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "discount" numeric(,), "fees" numeric(,), "igst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "sgst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "taxable_amount" numeric(,) DEFAULT 0.0 NOT NULL, "total_amount" numeric(,) DEFAULT 0.0 NOT NULL, "serial_number" integer DEFAULT 0 NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
1
CREATE TABLE "invoice_details" ("id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "price" numeric(,) NOT NULL, "quantity" numeric(,) NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "cgst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "discount" numeric(,), "fees" numeric(,), "igst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "sgst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "taxable_amount" numeric(,) DEFAULT 0.0 NOT NULL, "total_amount" numeric(,) DEFAULT 0.0 NOT NULL, "serial_number" integer DEFAULT 0 NOT NULL, "product_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, PRIMARY KEY ("id"));
|
|||||
| Table | recurrence_schedule_details | Mismatch |
|
|||||
| Table | audit_queue_invoice_payment_details | Match | ||||||
| Table | delinquency_action_types | Missing in Target |
|
|||||
| Table | delinquency_resolution_states | Missing in Target |
|
|||||
| Table | v_contact_number | Missing in Target |
|
|||||
| Table | v_invoice_ids_draft | Missing in Source |
|
|||||
| Function | check_invoice_approval_permissions | Match | ||||||
| Function | delete_bulk_invoices | Match | ||||||
| Function | generate_group_invoices_return_header_ids | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.generate_group_invoices_return_header_ids(p_template_id uuid)
2
RETURNS uuid[]
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_ts timestamptz := now();
7
v_header_ids uuid[];
8
BEGIN
9
-- Call your existing (unchanged) procedure
10
CALL public.run_grouped_invoices(p_template_id);
11
12
-- Collect headers created in this invocation window
13
SELECT COALESCE(array_agg(h.id), '{}') INTO v_header_ids
14
FROM public.group_invoice_headers h
15
WHERE h.group_invoice_template_id = p_template_id
16
AND h.is_deleted = FALSE
17
AND h.created_on_utc >= v_start_ts; -- created_on_utc is already saved via NOW() in proc
18
19
RETURN v_header_ids;
20
END;
21
$function$
|
|||||
| Function | get_all_customers | Match | ||||||
| Function | get_executed_grouped_invoice | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_executed_grouped_invoice(p_company_id uuid)
2
RETURNS TABLE(id uuid, template_id uuid, execution_date timestamp without time zone, error_message text, success_count integer, total_count integer, invoice_description text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
executions.id,
9
executions.template_id,
10
executions.execution_date,
11
executions.error_message,
12
executions.success_count,
13
executions.total_count,
14
templates.invoice_description
15
FROM
16
public.apartment_invoice_template_executions executions
17
INNER JOIN
18
public.apartment_invoice_templates templates
19
ON
20
executions.template_id = templates.id
21
WHERE
22
executions.company_id = p_company_id
23
24
ORDER BY
25
executions.execution_date DESC;
26
END;
27
$function$
|
|||||
| Function | get_group_invoice_summary | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_group_invoice_summary(p_group_invoice_id uuid)
2
RETURNS TABLE(id uuid, group_invoice_number text, execution_date timestamp without time zone, invoice_description text, paid_count integer, paid_amount numeric, unpaid_count integer, unpaid_amount numeric, overdue_count integer, overdue_amount numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
gih.id,
9
gih.group_invoice_number,
10
gih.execution_date,
11
git.invoice_description,
12
13
-- Paid
14
COALESCE(COUNT(CASE WHEN ih.invoice_status_id = 5 THEN 1 END), 0)::int AS paid_count,
15
COALESCE(SUM(CASE WHEN ih.invoice_status_id = 5 THEN ih.total_amount END), 0) AS paid_amount,
16
17
-- Unpaid
18
COALESCE(COUNT(CASE WHEN ih.invoice_status_id IN (1,2,3,4) THEN 1 END), 0)::int AS unpaid_count,
19
COALESCE(SUM(CASE WHEN ih.invoice_status_id IN (1,2,3,4) THEN ih.total_amount END), 0) AS unpaid_amount,
20
21
-- Overdue
22
COALESCE(COUNT(CASE WHEN ih.invoice_status_id NOT IN (5) AND ih.due_date < now() THEN 1 END), 0)::int AS overdue_count,
23
COALESCE(SUM(CASE WHEN ih.invoice_status_id NOT IN (5) AND ih.due_date < now() THEN ih.total_amount END), 0) AS overdue_amount
24
25
FROM group_invoice_headers gih
26
INNER JOIN group_invoice_templates git
27
ON gih.group_invoice_template_id = git.id
28
LEFT JOIN group_invoice_details gid
29
ON gid.group_invoice_header_id = gih.id AND gid.is_deleted = false
30
LEFT JOIN invoice_headers ih
31
ON ih.id = gid.invoice_header_id AND ih.is_deleted = false
32
WHERE gih.id = p_group_invoice_id
33
AND gih.is_deleted = false
34
GROUP BY gih.id, gih.group_invoice_number, gih.execution_date, git.invoice_description;
35
END;
36
$function$
|
|||||
| Function | get_income_expense_overview | Match | ||||||
| Function | get_invoice_approval_level | Match | ||||||
| Function | get_invoice_status | Match | ||||||
| Function | get_invoice_status_company_configs | Match | ||||||
| Function | get_new_group_invoice_number | Match | ||||||
| Function | get_new_payment_number | Match | ||||||
| Function | get_payment_distributions_for_invoice | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_payment_distributions_for_invoice(p_invoice_header_id uuid)
2
RETURNS TABLE(payment_detail_id uuid, invoice_payment_header_id uuid, payment_number text, invoice_header_id uuid, received_amount numeric, payment_created_by uuid, payment_created_by_name text, payment_created_on timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
ipd.id AS payment_detail_id,
9
ipd.invoice_payment_header_id,
10
iph.payment_number,
11
ipd.invoice_header_id,
12
ipd.received_amount,
13
ipd.created_by AS payment_created_by,
14
CONCAT(u.first_name, ' ', u.last_name) AS payment_created_by_name,
15
ipd.created_on_utc AS payment_created_on
16
FROM
17
public.invoice_payment_details ipd
18
JOIN public.invoice_payment_headers iph ON iph.id = ipd.invoice_payment_header_id
19
LEFT JOIN public.users u ON u.id = ipd.created_by
20
WHERE
21
ipd.invoice_header_id = p_invoice_header_id
22
AND ipd.is_deleted = false
23
ORDER BY
24
ipd.created_on_utc ASC;
25
END;
26
$function$
|
|||||
| 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 | pg_get_tabledef | Match | ||||||
| Function | upsert_invoice_status_company_config | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.upsert_invoice_status_company_config(p_company_id uuid, p_status_id integer, p_is_enabled boolean, p_user_id uuid)
1
CREATE OR REPLACE FUNCTION public.upsert_invoice_status_company_config(p_company_id uuid, p_status_id integer, p_is_enabled boolean)
2
RETURNS integer
2
RETURNS integer
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
v_id INT;
6
v_id INT;
7
BEGIN
7
BEGIN
8
-- Try update first (only non-deleted)
9
UPDATE public.invoice_status_company_configs
8
UPDATE public.invoice_status_company_configs
10
SET
9
SET is_enabled = p_is_enabled,
11
is_enabled = p_is_enabled,
10
modified_on_utc = now()
12
modified_on_utc = now(),
11
WHERE company_id = p_company_id AND status_id = p_status_id
13
modified_by = p_user_id
14
WHERE company_id = p_company_id
15
AND status_id = p_status_id
16
AND is_deleted = false
17
RETURNING id INTO v_id;
12
RETURNING id INTO v_id;
18
13
19
-- If not found, insert new with is_deleted = false
20
IF NOT FOUND THEN
14
IF NOT FOUND THEN
21
INSERT INTO public.invoice_status_company_configs (
15
INSERT INTO public.invoice_status_company_configs (
22
company_id,
16
company_id,
23
status_id,
17
status_id,
24
is_enabled,
18
is_enabled,
25
created_on_utc,
19
created_on_utc
26
created_by,
27
is_deleted
28
)
20
)
29
VALUES (
21
VALUES (
30
p_company_id,
22
p_company_id,
31
p_status_id,
23
p_status_id,
32
p_is_enabled,
24
p_is_enabled,
33
now(),
25
now()
34
p_user_id,
35
false
36
)
26
)
37
RETURNING id INTO v_id;
27
RETURNING id INTO v_id;
38
END IF;
28
END IF;
39
29
40
RETURN v_id;
30
RETURN v_id;
41
END;
31
END;
42
$function$
32
$function$
|
|||||
| Function | update_invoice_next_status_main | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.update_invoice_next_status_main(p_company_id uuid, p_invoice_status_id integer, p_invoice_ids uuid[], p_modified_by uuid)
2
RETURNS TABLE(invoice_id uuid, status_name text, error_msg text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_error_message text;
7
v_invoice_ids_draft uuid[];
8
v_invoice_ids_pending uuid[];
9
v_invoice_ids_other uuid[];
10
v_next_temp_id int;
11
BEGIN
12
RAISE NOTICE 'START update_invoice_next_status_main: company_id=%, invoice_status_id=%, modified_by=%', p_company_id, p_invoice_status_id, p_modified_by;
13
RAISE NOTICE 'Incoming Invoice IDs: %', p_invoice_ids;
14
15
-- Classify invoices by current status
16
SELECT array_agg(id) INTO v_invoice_ids_draft
17
FROM public.draft_invoice_headers dih
18
WHERE dih.id = ANY(p_invoice_ids) AND dih.invoice_status_id = 1;
19
20
SELECT array_agg(id) INTO v_invoice_ids_pending
21
FROM public.draft_invoice_headers dih
22
WHERE dih.id = ANY(p_invoice_ids) AND dih.invoice_status_id = 2;
23
24
SELECT array_agg(id) INTO v_invoice_ids_other
25
FROM public.invoice_headers ih
26
WHERE ih.id = ANY(p_invoice_ids) AND ih.invoice_status_id >= 3;
27
28
RAISE NOTICE 'Draft invoices: %', COALESCE(v_invoice_ids_draft, '{}');
29
RAISE NOTICE 'Pending Approval invoices: %', COALESCE(v_invoice_ids_pending, '{}');
30
RAISE NOTICE 'Other (Approved or higher) invoices: %', COALESCE(v_invoice_ids_other, '{}');
31
32
BEGIN
33
-- Direct update draft invoices to Pending Approval
34
IF array_length(v_invoice_ids_draft, 1) > 0 THEN
35
RAISE NOTICE 'Directly updating Draft invoices to Pending Approval';
36
37
UPDATE public.draft_invoice_headers
38
SET invoice_status_id = 2,
39
modified_by = p_modified_by,
40
modified_on_utc = now()
41
WHERE id = ANY(v_invoice_ids_draft);
42
43
-- Insert approval logs
44
INSERT INTO public.invoice_approval_logs(
45
invoice_id, status_id, approved_by, approved_on, "comment",
46
created_on_utc, created_by, approval_level
47
)
48
SELECT
49
dh.id, 2, p_modified_by, now(),
50
'Direct update from Draft to Pending Approval',
51
now(), p_modified_by, 0
52
FROM public.draft_invoice_headers dh
53
WHERE dh.id = ANY(v_invoice_ids_draft);
54
55
-- Generate next id for temp_invoice_next_status
56
SELECT COALESCE(MAX(id), 0) INTO v_next_temp_id FROM temp_invoice_next_status;
57
58
-- Insert into temp_invoice_next_status
59
INSERT INTO temp_invoice_next_status(id, invoice_id, status, error)
60
SELECT
61
row_number() OVER () + v_next_temp_id AS id,
62
dh.id,
63
'Pending Approval',
64
NULL
65
FROM public.draft_invoice_headers dh
66
WHERE dh.id = ANY(v_invoice_ids_draft);
67
68
END IF;
69
70
-- Call update_draft_invoice_next_status if Pending Approval invoices found and p_invoice_status_id = 2
71
IF array_length(v_invoice_ids_pending, 1) > 0 AND p_invoice_status_id = 2 THEN
72
RAISE NOTICE 'Calling update_draft_invoice_next_status for Pending Approval invoices';
73
CALL public.update_draft_invoice_next_status(p_company_id, p_invoice_status_id, v_invoice_ids_pending, p_modified_by);
74
END IF;
75
76
-- Call update_invoice_next_status_for_invoice_header for Approved or higher invoices
77
IF array_length(v_invoice_ids_other, 1) > 0 THEN
78
RAISE NOTICE 'Calling update_invoice_next_status_for_invoice_header for Approved or higher invoices';
79
CALL public.update_invoice_next_status_for_invoice_header(p_company_id, p_invoice_status_id, v_invoice_ids_other, p_modified_by);
80
END IF;
81
82
EXCEPTION WHEN OTHERS THEN
83
v_error_message := SQLERRM;
84
RAISE NOTICE 'Exception in child procedures: %', v_error_message;
85
END;
86
87
-- Return rows from temp_invoice_next_status
88
RAISE NOTICE 'Preparing to return rows from temp_invoice_next_status';
89
RETURN QUERY
90
SELECT tinv.invoice_id, tinv.status AS status_name, tinv.error AS error_msg
91
FROM temp_invoice_next_status tinv;
92
93
RAISE NOTICE 'END update_invoice_next_status_main';
94
END;
95
$function$
|
|||||
| Function | update_invoice_next_status_test | Match | ||||||
| Function | audit_queue_invoice_headers_trigger | Match | ||||||
| Function | edit_draft_invoice | Match | ||||||
| Function | get_customer_details | Match | ||||||
| Function | check_penalty_date | Match | ||||||
| Function | get_group_invoice_by_id | Match | ||||||
| Function | get_all_invoice_company_approvals | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_invoice_company_approvals(p_company_id uuid)
1
CREATE OR REPLACE FUNCTION public.get_all_invoice_company_approvals(p_company_id uuid)
2
RETURNS TABLE(id integer, status_id integer, user_id uuid, approval_level integer, required_approval_levels integer)
2
RETURNS TABLE(id integer, status_id integer, user_id uuid, approval_level integer)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
RETURN QUERY
6
RETURN QUERY
7
SELECT
7
SELECT
8
MIN(ia.id) AS id,
8
MIN(ia.id) AS id,
9
ia.status_id,
9
ia.status_id,
10
ia.user_id,
10
ia.user_id,
11
ia.approval_level,
11
ia.approval_level
12
iw.approval_level AS required_approval_levels
13
FROM invoice_approval_user_company ia
12
FROM invoice_approval_user_company ia
14
JOIN invoice_workflow iw
15
ON ia.company_id = iw.company_id
16
WHERE ia.company_id = p_company_id
13
WHERE ia.company_id = p_company_id
17
AND ia.is_deleted = false
14
AND ia.is_deleted = false
18
AND iw.is_deleted = false
15
GROUP BY ia.status_id, ia.user_id, ia.approval_level
19
AND iw.is_enabled = TRUE
20
AND iw.status = 2
21
GROUP BY ia.status_id, ia.user_id, ia.approval_level,iw.approval_level
22
ORDER BY ia.status_id, ia.user_id;
16
ORDER BY ia.status_id, ia.user_id;
23
END;
17
END;
24
$function$
18
$function$
|
|||||
| Function | soft_delete_invoice_if_no_details | Match | ||||||
| Function | upsert_invoice_workflow_and_config | Match | ||||||
| Function | list_scheduled_group_invoice_template_ids | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.list_scheduled_group_invoice_template_ids(p_run_date timestamp without time zone)
2
RETURNS TABLE(group_invoice_template_id uuid, company_id uuid, organization_id uuid)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
WITH params AS (
7
SELECT
8
(p_run_date)::date AS run_date,
9
EXTRACT(ISODOW FROM p_run_date)::int AS run_isodow,
10
EXTRACT(DAY FROM p_run_date)::int AS run_day,
11
EXTRACT(MONTH FROM p_run_date)::int AS run_month,
12
EXTRACT(DAY FROM (date_trunc('month', (p_run_date)::date)
13
+ interval '1 month - 1 day'))::int AS days_in_month
14
),
15
fy AS (
16
SELECT *,
17
CASE WHEN run_month >= 4 THEN run_month - 3 ELSE run_month + 9 END AS fy_month_index
18
FROM params
19
),
20
base AS (
21
SELECT
22
-- schedule fields (explicit; no bs.*)
23
bs.billing_cycle,
24
bs.day_of_week,
25
bs.day_of_month,
26
bs.bi_month,
27
bs.quarters_of_month,
28
bs.half_year,
29
bs.month_of_year,
30
bs.last_executed_at,
31
32
-- template & org linkage
33
git.id AS group_invoice_template_id,
34
c.id AS company_id,
35
o.id AS organization_id,
36
37
-- template window
38
git.starts_from,
39
git.end_date,
40
41
-- precomputed calendar
42
f.run_date,
43
f.run_isodow,
44
f.run_day,
45
f.days_in_month,
46
f.fy_month_index
47
FROM public.batch_schedules bs
48
JOIN public.group_invoice_templates git
49
ON git.id = bs.group_invoice_template_id
50
AND git.is_deleted = FALSE
51
AND git.active_status = TRUE
52
JOIN public.companies c
53
ON c.id = git.company_id
54
AND c.is_deleted = FALSE
55
JOIN public.organizations o
56
ON o.id = c.organization_id
57
AND o.is_deleted = FALSE
58
CROSS JOIN fy f
59
WHERE (bs.last_executed_at IS NULL OR bs.last_executed_at::date <> f.run_date)
60
AND (git.starts_from IS NULL OR f.run_date >= git.starts_from::date)
61
AND (git.end_date IS NULL OR f.run_date <= git.end_date::date)
62
),
63
calc AS (
64
SELECT
65
b.group_invoice_template_id,
66
b.company_id,
67
b.organization_id,
68
CASE b.billing_cycle
69
WHEN 'Daily' THEN TRUE
70
WHEN 'Weekly' THEN COALESCE(b.day_of_week, b.run_isodow) = b.run_isodow
71
WHEN 'Monthly' THEN b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
72
WHEN 'Bi-Monthly' THEN
73
COALESCE(b.bi_month, 1) IN (1,2)
74
AND (
75
(b.bi_month = 1 AND (b.fy_month_index % 2) = 1) OR
76
(b.bi_month = 2 AND (b.fy_month_index % 2) = 0)
77
)
78
AND b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
79
WHEN 'Quarterly' THEN
80
COALESCE(b.quarters_of_month, 1) BETWEEN 1 AND 4
81
AND b.fy_month_index = (CASE b.quarters_of_month
82
WHEN 1 THEN 1
83
WHEN 2 THEN 4
84
WHEN 3 THEN 7
85
ELSE 10 END)
86
AND b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
87
WHEN 'Half-yearly' THEN
88
COALESCE(b.half_year, 1) IN (1,2)
89
AND b.fy_month_index = (CASE b.half_year WHEN 1 THEN 1 ELSE 7 END)
90
AND b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
91
WHEN 'Yearly' THEN
92
COALESCE(b.month_of_year, b.fy_month_index) = b.fy_month_index
93
AND b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
94
ELSE FALSE
95
END AS is_due
96
FROM base b
97
)
98
SELECT
99
group_invoice_template_id,
100
company_id,
101
organization_id
102
FROM calc
103
WHERE is_due = TRUE
104
ORDER BY organization_id, company_id, group_invoice_template_id;
105
$function$
|
|||||
| Function | get_grouped_invoice | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_grouped_invoice(p_company_id uuid)
2
RETURNS TABLE(id uuid, template_id uuid, execution_date timestamp without time zone, error_message text, success_count integer, total_count integer, invoice_description text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
executions.id,
9
executions.template_id,
10
executions.execution_date,
11
executions.error_message,
12
executions.success_count,
13
executions.total_count,
14
templates.invoice_description
15
FROM
16
public.apartment_invoice_template_executions executions
17
INNER JOIN
18
public.apartment_invoice_templates templates
19
ON
20
executions.template_id = templates.id
21
WHERE
22
executions.company_id = p_company_id
23
24
ORDER BY
25
executions.execution_date DESC;
26
END;
27
$function$
|
|||||
| Function | get_user_by_email | Match | ||||||
| Function | get_grouped_invoice_header | Match | ||||||
| Function | get_invoice_header_ids | Match | ||||||
| Function | get_ledger_report | Match | ||||||
| Function | get_warehouse_details | Match | ||||||
| Function | close_resolved_cycles | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.close_resolved_cycles(p_organization_id uuid, p_company_ids uuid[])
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
UPDATE delinquency_cycles dc
7
SET
8
ended_on_utc = now(),
9
status_id = 2, -- CLOSED
10
modified_on_utc = now(),
11
modified_by = get_system_user_id()
12
WHERE dc.organization_id = p_organization_id
13
AND dc.ended_on_utc IS NULL
14
AND dc.is_deleted = false
15
AND NOT EXISTS (
16
SELECT 1
17
FROM invoice_headers ih
18
WHERE ih.company_id = ANY(p_company_ids)
19
AND ih.customer_id = dc.customer_id
20
AND ih.is_deleted = false
21
AND (ih.total_amount - ih.settled_amount) > 0
22
);
23
END;
24
$function$
|
|||||
| Function | update_invoice_payment_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.update_invoice_payment_status(p_company_id uuid, p_invoice_ids uuid[], p_payment_status_id integer, p_modified_by uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_invoice_header_id uuid; -- Declare a variable to hold each invoice_header_id
7
v_invoice_payment_header public.invoice_payment_headers%ROWTYPE; -- Declare ROWTYPE for full row
8
BEGIN
9
-- Loop over the provided array of Invoice Header IDs
10
FOREACH v_invoice_header_id IN ARRAY p_invoice_ids
11
LOOP
12
-- Log the current invoice being processed
13
RAISE NOTICE 'Processing InvoiceHeaderId: %, PaymentStatusId: %, CompanyId: %, ModifiedBy: %',
14
v_invoice_header_id, p_payment_status_id, p_company_id, p_modified_by;
15
16
-- Retrieve the corresponding InvoicePaymentDetail for the given invoice_header_id
17
SELECT invoice_payment_header_id INTO v_invoice_payment_header.id
18
FROM public.invoice_payment_details
19
WHERE invoice_header_id = v_invoice_header_id -- Match invoice header ID in invoice_payment_details
20
AND is_deleted = false
21
LIMIT 1;
22
23
-- If payment detail exists, proceed to update payment header
24
IF FOUND THEN
25
RAISE NOTICE 'Invoice Payment Detail found for InvoiceHeaderId: %', v_invoice_header_id;
26
27
-- Retrieve the corresponding InvoicePaymentHeader using the payment header id
28
SELECT * INTO v_invoice_payment_header
29
FROM public.invoice_payment_headers
30
WHERE id = v_invoice_payment_header.id -- Match invoice payment header ID
31
AND is_deleted = false -- Ensure not deleted
32
LIMIT 1;
33
34
-- If the payment header exists, update its PaymentStatusId
35
IF FOUND THEN
36
RAISE NOTICE 'Updating PaymentStatusId for InvoicePaymentHeaderId: %', v_invoice_payment_header.id;
37
38
UPDATE public.invoice_payment_headers
39
SET payment_status_id = p_payment_status_id,
40
modified_by = p_modified_by,
41
modified_on_utc = NOW()
42
WHERE id = v_invoice_payment_header.id;
43
44
RAISE NOTICE 'PaymentStatusId updated for InvoicePaymentHeaderId: %', v_invoice_payment_header.id;
45
ELSE
46
RAISE NOTICE 'InvoicePaymentHeader not found for InvoiceHeaderId: %', v_invoice_header_id;
47
END IF;
48
ELSE
49
RAISE NOTICE 'InvoicePaymentDetail not found for InvoiceHeaderId: %', v_invoice_header_id;
50
END IF;
51
END LOOP;
52
53
-- No need for COMMIT, as the transaction will be committed by the caller
54
RAISE NOTICE 'Updated payment status for % invoices.', array_length(p_invoice_ids, 1);
55
END;
56
$function$
|
|||||
| Function | get_invoices_by_customer_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_invoices_by_customer_id(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_customer_id uuid, p_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
2
RETURNS TABLE(id uuid, invoice_number character varying, type integer, invoice_date date, customer_id uuid, customer_name character varying, due_date date, total_amount numeric, settled_amount numeric, invoice_status_id integer, invoice_status character varying, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, created_by_name character varying, modified_by_name character varying, is_created_by_scheduler boolean, has_next_status_approval boolean, next_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
7
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
8
DRAFT_STATUS CONSTANT integer := 1;
9
BEGIN
10
RETURN QUERY
11
WITH invoice_data AS (
12
-- Posted/normal invoices
13
SELECT
14
ih.id,
15
CAST(ih.invoice_number AS varchar) AS invoice_number,
16
ih.type,
17
ih.invoice_date::date AS invoice_date,
18
ih.customer_id,
19
c.name AS customer_name,
20
ih.due_date::date AS due_date,
21
ih.total_amount,
22
ih.settled_amount,
23
ih.invoice_status_id,
24
s.name AS invoice_status,
25
ih.currency_id,
26
ih.created_on_utc,
27
ih.modified_on_utc,
28
ih.created_by,
29
ih.modified_by,
30
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
31
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
32
ih.is_created_by_scheduler,
33
ih.credit_account_id
34
FROM public.invoice_headers ih
35
LEFT JOIN public.customers c ON c.id = ih.customer_id
36
LEFT JOIN public.invoice_statuses s ON s.id = ih.invoice_status_id
37
LEFT JOIN public.users cu ON cu.id = ih.created_by
38
LEFT JOIN public.users mu ON mu.id = ih.modified_by
39
WHERE ih.company_id = p_company_id
40
AND ih.customer_id = p_customer_id
41
AND ih.is_deleted = false
42
AND (c.is_deleted = false OR c.is_deleted IS NULL)
43
AND ih.invoice_date BETWEEN v_start_date AND v_end_date
44
AND (p_type_id IS NULL OR p_type_id = 0 OR ih.type = p_type_id)
45
46
UNION ALL
47
48
-- Draft invoices
49
SELECT
50
dih.id,
51
CAST(dih.invoice_number AS varchar) AS invoice_number,
52
dih.type,
53
dih.invoice_date::date AS invoice_date,
54
dih.customer_id,
55
c.name AS customer_name,
56
dih.due_date::date AS due_date,
57
dih.total_amount,
58
dih.settled_amount,
59
dih.invoice_status_id,
60
s.name AS invoice_status,
61
dih.currency_id,
62
dih.created_on_utc,
63
dih.modified_on_utc,
64
dih.created_by,
65
dih.modified_by,
66
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
67
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
68
dih.is_created_by_scheduler,
69
dih.credit_account_id
70
FROM public.draft_invoice_headers dih
71
LEFT JOIN public.customers c ON c.id = dih.customer_id
72
LEFT JOIN public.invoice_statuses s ON s.id = dih.invoice_status_id
73
LEFT JOIN public.users cu ON cu.id = dih.created_by
74
LEFT JOIN public.users mu ON mu.id = dih.modified_by
75
WHERE dih.company_id = p_company_id
76
AND dih.customer_id = p_customer_id
77
AND dih.is_deleted = false
78
AND (c.is_deleted = false OR c.is_deleted IS NULL)
79
AND dih.invoice_date BETWEEN v_start_date AND v_end_date
80
AND (p_type_id IS NULL OR p_type_id = 0 OR dih.type = p_type_id)
81
)
82
SELECT
83
id.id,
84
id.invoice_number,
85
id.type,
86
id.invoice_date,
87
id.customer_id,
88
id.customer_name,
89
id.due_date,
90
id.total_amount,
91
id.settled_amount,
92
id.invoice_status_id,
93
id.invoice_status,
94
id.currency_id,
95
id.created_on_utc,
96
id.modified_on_utc,
97
id.created_by,
98
id.modified_by,
99
id.created_by_name,
100
id.modified_by_name,
101
id.is_created_by_scheduler,
102
103
/* Same approval logic as get_all_invoices_from_span */
104
CASE
105
WHEN id.invoice_status_id = DRAFT_STATUS THEN true
106
WHEN EXISTS (
107
SELECT 1
108
FROM public.invoice_approval_users_account a
109
WHERE a.account_id = id.credit_account_id
110
AND a.status_id = id.invoice_status_id
111
AND a.user_id = p_user_id
112
AND a.is_deleted = false
113
) THEN true
114
WHEN EXISTS (
115
SELECT 1
116
FROM public.invoice_approval_user_company c
117
WHERE c.company_id = p_company_id
118
AND c.status_id = id.invoice_status_id
119
AND c.user_id = p_user_id
120
AND c.is_deleted = false
121
) THEN true
122
ELSE false
123
END AS has_next_status_approval,
124
125
/* Same next-status resolution as get_all_invoices_from_span */
126
COALESCE(
127
(SELECT iw.next_status
128
FROM public.invoice_workflow iw
129
WHERE iw.company_id = p_company_id
130
AND iw.status = id.invoice_status_id
131
AND iw.is_deleted = false
132
LIMIT 1),
133
0
134
) AS next_status_id
135
FROM invoice_data id;
136
END;
137
$function$
|
|||||
| Function | list_scheduled_invoice_ids | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.list_scheduled_invoice_ids(p_schedule_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP)
2
RETURNS TABLE(invoice_header_id uuid, draft_invoice_header_id uuid, company_id uuid, organization_id uuid)
3
LANGUAGE sql
4
AS $function$
5
WITH base AS (
6
SELECT
7
s.id AS schedule_id,
8
s.invoice_header_id AS invoice_header_id,
9
s.draft_invoice_header_id AS draft_invoice_header_id,
10
s.company_id AS company_id,
11
c.organization_id AS organization_id,
12
d.frequency_cycle,
13
d.day_of_week,
14
d.day_of_month,
15
d.month_of_year,
16
s.starts_from,
17
s.end_date
18
FROM public.recurring_sales_schedules s
19
JOIN public.recurrence_schedule_details d
20
ON d.schedule_id = s.id
21
AND d.is_deleted = false
22
JOIN public.companies c
23
ON c.id = s.company_id
24
WHERE s.is_deleted = false
25
AND s.schedule_status = 'active'
26
AND s.starts_from <= (p_schedule_date::date)
27
AND (s.end_date IS NULL OR s.end_date >= (p_schedule_date::date))
28
),
29
aligned AS (
30
SELECT
31
b.schedule_id,
32
b.invoice_header_id,
33
b.draft_invoice_header_id,
34
b.company_id,
35
b.organization_id
36
FROM base b
37
WHERE CASE lower(b.frequency_cycle)
38
WHEN 'daily' THEN TRUE
39
WHEN 'weekly' THEN b.day_of_week IS NOT NULL
40
AND b.day_of_week = EXTRACT(ISODOW FROM p_schedule_date)::int
41
WHEN 'monthly' THEN b.day_of_month IS NOT NULL
42
AND b.day_of_month = EXTRACT(DAY FROM p_schedule_date)::int
43
WHEN 'yearly' THEN b.month_of_year IS NOT NULL AND b.day_of_month IS NOT NULL
44
AND b.month_of_year = EXTRACT(MONTH FROM p_schedule_date)::int
45
AND b.day_of_month = EXTRACT(DAY FROM p_schedule_date)::int
46
ELSE FALSE
47
END
48
)
49
SELECT DISTINCT
50
a.invoice_header_id,
51
a.draft_invoice_header_id,
52
a.company_id,
53
a.organization_id
54
FROM aligned a
55
WHERE NOT EXISTS (
56
SELECT 1
57
FROM public.schedule_execution_logs l
58
WHERE l.schedule_id = a.schedule_id
59
AND l.execution_date = (p_schedule_date::date)
60
AND l.is_deleted = false
61
);
62
$function$
|
|||||
| Function | get_invoice_by_id | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_invoice_by_id(p_invoice_header_id uuid)
1
CREATE OR REPLACE FUNCTION public.get_invoice_by_id(p_invoice_header_id uuid)
2
RETURNS TABLE(id uuid, invoice_number text, credit_account_id uuid, invoice_voucher text, invoice_date timestamp with time zone, payment_term integer, customer_id uuid, customer_name character varying, customer_email text, customer_mobile_number text, customer_phone_number text, customer_gstin text, customer_short_name text, customer_pan text, customer_tan text, due_date timestamp with time zone, total_amount numeric, taxable_amount numeric, fees numeric, cgst_amount numeric, sgst_amount numeric, igst_amount numeric, settled_amount numeric, currency_id integer, invoice_status_id integer, invoice_status text, discount numeric, note text, round_off numeric, source_warehouse_id uuid, destination_warehouse_id uuid, destination_customer_id uuid, destination_warehouse_name text, destination_address_id uuid, destination_country_name text, destination_country_id uuid, destination_state_name text, destination_state_id uuid, destination_city_name text, destination_city_id uuid, destination_address_line1 text, destination_address_line2 text, destination_zip_code text, destination_gstin text, invoice_lines jsonb, type integer, current_approval_level integer, is_created_by_scheduler boolean, created_on_utc timestamp with time zone, modified_on_utc timestamp with time zone, created_by uuid, modified_by uuid)
2
RETURNS TABLE(id uuid, invoice_number text, credit_account_id uuid, invoice_voucher text, invoice_date timestamp with time zone, payment_term integer, customer_id uuid, customer_name character varying, customer_email text, customer_mobile_number text, customer_phone_number text, customer_gstin text, customer_short_name text, customer_pan text, customer_tan text, due_date timestamp with time zone, total_amount numeric, taxable_amount numeric, fees numeric, cgst_amount numeric, sgst_amount numeric, igst_amount numeric, setteled_amount numeric, currency_id integer, invoice_status_id integer, invoice_status text, discount numeric, note text, round_off numeric, source_warehouse_id uuid, destination_warehouse_id uuid, destination_customer_id uuid, destination_warehouse_name text, destination_address_id uuid, destination_country_name text, destination_country_id uuid, destination_state_name text, destination_state_id uuid, destination_city_name text, destination_city_id uuid, destination_address_line1 text, destination_address_line2 text, destination_zip_code text, destination_gstin text, invoice_lines jsonb, type integer, current_approval_level integer, is_created_by_scheduler boolean, created_on_utc timestamp with time zone, modified_on_utc timestamp with time zone, created_by uuid, modified_by uuid)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
v_invoice_status_id integer;
6
v_invoice_status_id integer;
7
BEGIN
7
BEGIN
8
-- Get the invoice status using the get_invoice_status function
8
-- Get the invoice status using the get_invoice_status function
9
v_invoice_status_id := public.get_invoice_status(p_invoice_header_id);
9
v_invoice_status_id := public.get_invoice_status(p_invoice_header_id);
10
10
11
IF v_invoice_status_id >= 3 THEN
11
IF v_invoice_status_id >= 3 THEN
12
RETURN QUERY
12
RETURN QUERY
13
SELECT
13
SELECT
14
ih.id,
14
ih.id,
15
ih.invoice_number,
15
ih.invoice_number,
16
ih.credit_account_id,
16
ih.credit_account_id,
17
ih.invoice_voucher,
17
ih.invoice_voucher,
18
ih.invoice_date::timestamp with time zone,
18
ih.invoice_date::timestamp with time zone,
19
ih.payment_term,
19
ih.payment_term,
20
ih.customer_id,
20
ih.customer_id,
21
c.name AS customer_name,
21
c.name AS customer_name,
22
c.email AS customer_email,
22
c.email AS customer_email,
23
c.mobile_number AS customer_mobile_number,
23
c.mobile_number AS customer_mobile_number,
24
c.phone_number AS customer_phone_number,
24
c.phone_number AS customer_phone_number,
25
c.gstin AS customer_gstin,
25
c.gstin AS customer_gstin,
26
c.short_name AS customer_short_name,
26
c.short_name AS customer_short_name,
27
c.pan AS customer_pan,
27
c.pan AS customer_pan,
28
c.tan AS customer_tan,
28
c.tan AS customer_tan,
29
ih.due_date::timestamp with time zone,
29
ih.due_date::timestamp with time zone,
30
ih.total_amount,
30
ih.total_amount,
31
ih.taxable_amount,
31
ih.taxable_amount,
32
ih.fees,
32
ih.fees,
33
ih.cgst_amount,
33
ih.cgst_amount,
34
ih.sgst_amount,
34
ih.sgst_amount,
35
ih.igst_amount,
35
ih.igst_amount,
36
ih.settled_amount,
36
ih.setteled_amount,
37
ih.currency_id,
37
ih.currency_id,
38
ih.invoice_status_id,
38
ih.invoice_status_id,
39
invst.name :: text,
39
invst.name :: text,
40
ih.discount,
40
ih.discount,
41
ih.note,
41
ih.note,
42
ih.round_off,
42
ih.round_off,
43
ih.source_warehouse_id,
43
ih.source_warehouse_id,
44
wd.id AS destination_warehouse_id,
44
wd.id AS destination_warehouse_id,
45
wd.customer_id AS destination_customer_id,
45
wd.customer_id AS destination_customer_id,
46
wd.name AS destination_warehouse_name,
46
wd.name AS destination_warehouse_name,
47
wd.address_id AS destination_address_id,
47
wd.address_id AS destination_address_id,
48
wd.country_name AS destination_country_name,
48
wd.country_name AS destination_country_name,
49
wd.country_id AS destination_country_id,
49
wd.country_id AS destination_country_id,
50
wd.state_name AS destination_state_name,
50
wd.state_name AS destination_state_name,
51
wd.state_id AS destination_state_id,
51
wd.state_id AS destination_state_id,
52
wd.city_name AS destination_city_name,
52
wd.city_name AS destination_city_name,
53
wd.city_id AS destination_city_id,
53
wd.city_id AS destination_city_id,
54
wd.address_line1 AS destination_address_line1,
54
wd.address_line1 AS destination_address_line1,
55
wd.address_line2 AS destination_address_line2,
55
wd.address_line2 AS destination_address_line2,
56
wd.zip_code AS destination_zip_code,
56
wd.zip_code AS destination_zip_code,
57
wd.gstin AS destination_gstin,
57
wd.gstin AS destination_gstin,
58
jsonb_agg(jsonb_build_object(
58
jsonb_agg(jsonb_build_object(
59
'invoice_line_id', il.id,
59
'invoice_line_id', il.id,
60
'product_id', il.product_id,
60
'product_id', il.product_id,
61
'price', il.price,
61
'price', il.price,
62
'quantity', il.quantity,
62
'quantity', il.quantity,
63
'fees', il.fees,
63
'fees', il.fees,
64
'discount', il.discount,
64
'discount', il.discount,
65
'taxable_amount', il.taxable_amount,
65
'taxable_amount', il.taxable_amount,
66
'sgst_amount', il.sgst_amount,
66
'sgst_amount', il.sgst_amount,
67
'cgst_amount', il.cgst_amount,
67
'cgst_amount', il.cgst_amount,
68
'igst_amount', il.igst_amount,
68
'igst_amount', il.igst_amount,
69
'total_amount', il.total_amount,
69
'total_amount', il.total_amount,
70
'serial_number', il.serial_number
70
'serial_number', il.serial_number
71
) ORDER BY il.serial_number) AS invoice_lines,
71
) ORDER BY il.serial_number) AS invoice_lines,
72
ih.type,
72
ih.type,
73
ih.current_approval_level,
73
ih.current_approval_level,
74
ih.is_created_by_scheduler,
74
ih.is_created_by_scheduler,
75
ih.created_on_utc::timestamp with time zone,
75
ih.created_on_utc::timestamp with time zone,
76
ih.modified_on_utc::timestamp with time zone,
76
ih.modified_on_utc::timestamp with time zone,
77
ih.created_by,
77
ih.created_by,
78
ih.modified_by
78
ih.modified_by
79
FROM invoice_headers ih
79
FROM invoice_headers ih
80
LEFT JOIN public.get_customer_details(ih.customer_id) c ON TRUE
80
LEFT JOIN public.get_customer_details(ih.customer_id) c ON TRUE
81
LEFT JOIN public.get_warehouse_details(ih.destination_warehouse_id) wd ON TRUE
81
LEFT JOIN public.get_warehouse_details(ih.destination_warehouse_id) wd ON TRUE
82
LEFT JOIN public.get_invoice_lines(ih.id, ih.invoice_status_id) il ON TRUE
82
LEFT JOIN public.get_invoice_lines(ih.id, ih.invoice_status_id) il ON TRUE
83
JOIN public.invoice_statuses invst ON ih.invoice_status_id = invst.id
83
JOIN public.invoice_statuses invst ON ih.invoice_status_id = invst.id
84
WHERE ih.id = p_invoice_header_id
84
WHERE ih.id = p_invoice_header_id
85
GROUP BY
85
GROUP BY
86
ih.id, ih.invoice_number, ih.credit_account_id, ih.invoice_voucher, ih.invoice_date,
86
ih.id, ih.invoice_number, ih.credit_account_id, ih.invoice_voucher, ih.invoice_date,
87
ih.payment_term, ih.customer_id, c.name, c.email, c.mobile_number,
87
ih.payment_term, ih.customer_id, c.name, c.email, c.mobile_number,
88
c.phone_number, c.gstin, c.short_name, c.pan, c.tan, ih.due_date,
88
c.phone_number, c.gstin, c.short_name, c.pan, c.tan, ih.due_date,
89
ih.total_amount, ih.taxable_amount, ih.fees, ih.cgst_amount, ih.sgst_amount,
89
ih.total_amount, ih.taxable_amount, ih.fees, ih.cgst_amount, ih.sgst_amount,
90
ih.igst_amount, ih.settled_amount, ih.currency_id, ih.invoice_status_id, invst.name, ih.discount, ih.note,
90
ih.igst_amount, ih.setteled_amount, ih.currency_id, ih.invoice_status_id, invst.name, ih.discount, ih.note,
91
ih.round_off, ih.source_warehouse_id, wd.id, wd.customer_id, wd.name,
91
ih.round_off, ih.source_warehouse_id, wd.id, wd.customer_id, wd.name,
92
wd.address_id, wd.country_name, wd.country_id, wd.state_name, wd.state_id,
92
wd.address_id, wd.country_name, wd.country_id, wd.state_name, wd.state_id,
93
wd.city_name, wd.city_id, wd.address_line1, wd.address_line2, wd.zip_code,
93
wd.city_name, wd.city_id, wd.address_line1, wd.address_line2, wd.zip_code,
94
wd.gstin,ih.type, ih.current_approval_level, ih.is_created_by_scheduler, ih.created_on_utc, ih.modified_on_utc, ih.created_by, ih.modified_by;
94
wd.gstin,ih.type, ih.current_approval_level, ih.is_created_by_scheduler, ih.created_on_utc, ih.modified_on_utc, ih.created_by, ih.modified_by;
95
--order by il.serial_number;
95
--order by il.serial_number;
96
96
97
ELSE
97
ELSE
98
RETURN QUERY
98
RETURN QUERY
99
SELECT
99
SELECT
100
dih.id,
100
dih.id,
101
dih.invoice_number,
101
dih.invoice_number,
102
dih.credit_account_id,
102
dih.credit_account_id,
103
dih.invoice_voucher,
103
dih.invoice_voucher,
104
dih.invoice_date::timestamp with time zone,
104
dih.invoice_date::timestamp with time zone,
105
dih.payment_term,
105
dih.payment_term,
106
dih.customer_id,
106
dih.customer_id,
107
c.name AS customer_name,
107
c.name AS customer_name,
108
c.email AS customer_email,
108
c.email AS customer_email,
109
c.mobile_number AS customer_mobile_number,
109
c.mobile_number AS customer_mobile_number,
110
c.phone_number AS customer_phone_number,
110
c.phone_number AS customer_phone_number,
111
c.gstin AS customer_gstin,
111
c.gstin AS customer_gstin,
112
c.short_name AS customer_short_name,
112
c.short_name AS customer_short_name,
113
c.pan AS customer_pan,
113
c.pan AS customer_pan,
114
c.tan AS customer_tan,
114
c.tan AS customer_tan,
115
dih.due_date::timestamp with time zone,
115
dih.due_date::timestamp with time zone,
116
dih.total_amount,
116
dih.total_amount,
117
dih.taxable_amount,
117
dih.taxable_amount,
118
dih.fees,
118
dih.fees,
119
dih.cgst_amount,
119
dih.cgst_amount,
120
dih.sgst_amount,
120
dih.sgst_amount,
121
dih.igst_amount,
121
dih.igst_amount,
122
dih.settled_amount,
122
dih.setteled_amount,
123
dih.currency_id,
123
dih.currency_id,
124
dih.invoice_status_id,
124
dih.invoice_status_id,
125
dinvst.name :: text,
125
dinvst.name :: text,
126
dih.discount,
126
dih.discount,
127
dih.note,
127
dih.note,
128
dih.round_off,
128
dih.round_off,
129
dih.source_warehouse_id,
129
dih.source_warehouse_id,
130
wd.id AS destination_warehouse_id,
130
wd.id AS destination_warehouse_id,
131
wd.customer_id AS destination_customer_id,
131
wd.customer_id AS destination_customer_id,
132
wd.name AS destination_warehouse_name,
132
wd.name AS destination_warehouse_name,
133
wd.address_id AS destination_address_id,
133
wd.address_id AS destination_address_id,
134
wd.country_name AS destination_country_name,
134
wd.country_name AS destination_country_name,
135
wd.country_id AS destination_country_id,
135
wd.country_id AS destination_country_id,
136
wd.state_name AS destination_state_name,
136
wd.state_name AS destination_state_name,
137
wd.state_id AS destination_state_id,
137
wd.state_id AS destination_state_id,
138
wd.city_name AS destination_city_name,
138
wd.city_name AS destination_city_name,
139
wd.city_id AS destination_city_id,
139
wd.city_id AS destination_city_id,
140
wd.address_line1 AS destination_address_line1,
140
wd.address_line1 AS destination_address_line1,
141
wd.address_line2 AS destination_address_line2,
141
wd.address_line2 AS destination_address_line2,
142
wd.zip_code AS destination_zip_code,
142
wd.zip_code AS destination_zip_code,
143
wd.gstin AS destination_gstin,
143
wd.gstin AS destination_gstin,
144
jsonb_agg(jsonb_build_object(
144
jsonb_agg(jsonb_build_object(
145
'invoice_line_id', il.id,
145
'invoice_line_id', il.id,
146
'product_id', il.product_id,
146
'product_id', il.product_id,
147
'price', il.price,
147
'price', il.price,
148
'quantity', il.quantity,
148
'quantity', il.quantity,
149
'fees', il.fees,
149
'fees', il.fees,
150
'discount', il.discount,
150
'discount', il.discount,
151
'taxable_amount', il.taxable_amount,
151
'taxable_amount', il.taxable_amount,
152
'sgst_amount', il.sgst_amount,
152
'sgst_amount', il.sgst_amount,
153
'cgst_amount', il.cgst_amount,
153
'cgst_amount', il.cgst_amount,
154
'igst_amount', il.igst_amount,
154
'igst_amount', il.igst_amount,
155
'total_amount', il.total_amount,
155
'total_amount', il.total_amount,
156
'serial_number', il.serial_number
156
'serial_number', il.serial_number
157
) ORDER BY il.serial_number) AS invoice_lines,
157
) ORDER BY il.serial_number) AS invoice_lines,
158
dih.type,
158
dih.type,
159
dih.current_approval_level,
159
dih.current_approval_level,
160
dih.is_created_by_scheduler,
160
dih.is_created_by_scheduler,
161
dih.created_on_utc::timestamp with time zone,
161
dih.created_on_utc::timestamp with time zone,
162
dih.modified_on_utc::timestamp with time zone,
162
dih.modified_on_utc::timestamp with time zone,
163
dih.created_by,
163
dih.created_by,
164
dih.modified_by
164
dih.modified_by
165
FROM draft_invoice_headers dih
165
FROM draft_invoice_headers dih
166
LEFT JOIN public.get_customer_details(dih.customer_id) c ON TRUE
166
LEFT JOIN public.get_customer_details(dih.customer_id) c ON TRUE
167
LEFT JOIN public.get_warehouse_details(dih.destination_warehouse_id) wd ON TRUE
167
LEFT JOIN public.get_warehouse_details(dih.destination_warehouse_id) wd ON TRUE
168
LEFT JOIN public.get_invoice_lines(dih.id, dih.invoice_status_id) il ON TRUE
168
LEFT JOIN public.get_invoice_lines(dih.id, dih.invoice_status_id) il ON TRUE
169
JOIN public.invoice_statuses dinvst ON dih.invoice_status_id = dinvst.id
169
JOIN public.invoice_statuses dinvst ON dih.invoice_status_id = dinvst.id
170
WHERE dih.id = p_invoice_header_id
170
WHERE dih.id = p_invoice_header_id
171
GROUP BY
171
GROUP BY
172
dih.id, dih.invoice_number, dih.credit_account_id, dih.invoice_voucher, dih.invoice_date,
172
dih.id, dih.invoice_number, dih.credit_account_id, dih.invoice_voucher, dih.invoice_date,
173
dih.payment_term, dih.customer_id, c.name, c.email, c.mobile_number,
173
dih.payment_term, dih.customer_id, c.name, c.email, c.mobile_number,
174
c.phone_number, c.gstin, c.short_name, c.pan, c.tan, dih.due_date,
174
c.phone_number, c.gstin, c.short_name, c.pan, c.tan, dih.due_date,
175
dih.total_amount, dih.taxable_amount, dih.fees, dih.cgst_amount, dih.sgst_amount,
175
dih.total_amount, dih.taxable_amount, dih.fees, dih.cgst_amount, dih.sgst_amount,
176
dih.igst_amount, dih.settled_amount, dih.currency_id, dih.invoice_status_id, dinvst.name, dih.discount, dih.note,
176
dih.igst_amount, dih.setteled_amount, dih.currency_id, dih.invoice_status_id, dinvst.name, dih.discount, dih.note,
177
dih.round_off, dih.source_warehouse_id, wd.id, wd.customer_id, wd.name,
177
dih.round_off, dih.source_warehouse_id, wd.id, wd.customer_id, wd.name,
178
wd.address_id, wd.country_name, wd.country_id, wd.state_name, wd.state_id,
178
wd.address_id, wd.country_name, wd.country_id, wd.state_name, wd.state_id,
179
wd.city_name, wd.city_id, wd.address_line1, wd.address_line2, wd.zip_code,
179
wd.city_name, wd.city_id, wd.address_line1, wd.address_line2, wd.zip_code,
180
wd.gstin,dih.type, dih.current_approval_level, dih.is_created_by_scheduler, dih.created_on_utc, dih.modified_on_utc, dih.created_by, dih.modified_by;
180
wd.gstin,dih.type, dih.current_approval_level, dih.is_created_by_scheduler, dih.created_on_utc, dih.modified_on_utc, dih.created_by, dih.modified_by;
181
--order by il.serial_number;
181
--order by il.serial_number;
182
END IF;
182
END IF;
183
END;
183
END;
184
$function$
184
$function$
|
|||||
| Function | fetch_all_invoices | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.fetch_all_invoices(company_id uuid, fin_year_id integer)
1
CREATE OR REPLACE FUNCTION public.fetch_all_invoices(company_id uuid, fin_year_id integer)
2
RETURNS TABLE(invoice_id uuid, invoice_number text, credit_account_id uuid, invoice_voucher text, customer_id uuid, customer_name text, due_date date, invoice_date date, payment_term text, total_amount numeric, setteled_amount numeric, currency_id uuid, invoice_status text, invoice_status_id uuid, discount numeric, note text, created_on timestamp without time zone, modified_on timestamp without time zone, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, invoice_lines jsonb, invoice_type text, is_created_by_scheduler boolean)
2
RETURNS TABLE(invoice_id uuid, invoice_number text, credit_account_id uuid, invoice_voucher text, customer_id uuid, customer_name text, due_date date, invoice_date date, payment_term text, total_amount numeric, setteled_amount numeric, currency_id uuid, invoice_status text, invoice_status_id uuid, discount numeric, note text, created_on timestamp without time zone, modified_on timestamp without time zone, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, invoice_lines jsonb, invoice_type text, is_created_by_scheduler boolean)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
financial_year_start DATE := MAKE_DATE(fin_year_id, 4, 1);
6
financial_year_start DATE := MAKE_DATE(fin_year_id, 4, 1);
7
financial_year_end DATE := MAKE_DATE(fin_year_id + 1, 3, 31);
7
financial_year_end DATE := MAKE_DATE(fin_year_id + 1, 3, 31);
8
BEGIN
8
BEGIN
9
RETURN QUERY
9
RETURN QUERY
10
SELECT
10
SELECT
11
ih.id AS invoice_id,
11
ih.id AS invoice_id,
12
ih.invoice_number,
12
ih.invoice_number,
13
ih.credit_account_id,
13
ih.credit_account_id,
14
ih.invoice_voucher,
14
ih.invoice_voucher,
15
ih.customer_id,
15
ih.customer_id,
16
(SELECT c.name FROM customers c WHERE c.id = ih.customer_id) AS customer_name,
16
(SELECT c.name FROM customers c WHERE c.id = ih.customer_id) AS customer_name,
17
ih.due_date,
17
ih.due_date,
18
ih.invoice_date,
18
ih.invoice_date,
19
ih.payment_term,
19
ih.payment_term,
20
ih.total_amount,
20
ih.total_amount,
21
ih.settled_amount,
21
ih.setteled_amount,
22
ih.currency_id,
22
ih.currency_id,
23
(SELECT ist.name FROM invoice_statuses ist WHERE ist.id = ih.invoice_status_id) AS invoice_status,
23
(SELECT ist.name FROM invoice_statuses ist WHERE ist.id = ih.invoice_status_id) AS invoice_status,
24
ih.invoice_status_id,
24
ih.invoice_status_id,
25
ih.discount,
25
ih.discount,
26
ih.note,
26
ih.note,
27
ih.created_on_utc AS created_on,
27
ih.created_on_utc AS created_on,
28
ih.modified_on_utc AS modified_on,
28
ih.modified_on_utc AS modified_on,
29
ih.created_by,
29
ih.created_by,
30
ih.modified_by,
30
ih.modified_by,
31
ih.source_warehouse_id,
31
ih.source_warehouse_id,
32
ih.destination_warehouse_id,
32
ih.destination_warehouse_id,
33
(SELECT w.name FROM warehouses w WHERE w.id = ih.destination_warehouse_id) AS destination_warehouse_name,
33
(SELECT w.name FROM warehouses w WHERE w.id = ih.destination_warehouse_id) AS destination_warehouse_name,
34
(SELECT JSONB_AGG(
34
(SELECT JSONB_AGG(
35
JSONB_BUILD_OBJECT(
35
JSONB_BUILD_OBJECT(
36
'id', id,
36
'id', id,
37
'product_id', product_id,
37
'product_id', product_id,
38
'price', price,
38
'price', price,
39
'quantity', quantity,
39
'quantity', quantity,
40
'fees', fees,
40
'fees', fees,
41
'discount', discount,
41
'discount', discount,
42
'taxable_amount', taxable_amount,
42
'taxable_amount', taxable_amount,
43
'sgst_amount', sgst_amount,
43
'sgst_amount', sgst_amount,
44
'cgst_amount', cgst_amount,
44
'cgst_amount', cgst_amount,
45
'igst_amount', igst_amount,
45
'igst_amount', igst_amount,
46
'total_amount', total_amount,
46
'total_amount', total_amount,
47
'serial_number', serial_number
47
'serial_number', serial_number
48
)
48
)
49
)
49
)
50
FROM invoice_details
50
FROM invoice_details
51
WHERE invoice_header_id = ih.id) AS invoice_lines,
51
WHERE invoice_header_id = ih.id) AS invoice_lines,
52
ih.type AS invoice_type,
52
ih.type AS invoice_type,
53
ih.is_created_by_scheduler
53
ih.is_created_by_scheduler
54
FROM invoice_headers ih
54
FROM invoice_headers ih
55
WHERE ih.company_id = company_id
55
WHERE ih.company_id = company_id
56
AND ih.invoice_date BETWEEN financial_year_start AND financial_year_end
56
AND ih.invoice_date BETWEEN financial_year_start AND financial_year_end
57
57
58
UNION ALL
58
UNION ALL
59
59
60
SELECT
60
SELECT
61
dih.id AS invoice_id,
61
dih.id AS invoice_id,
62
dih.invoice_number,
62
dih.invoice_number,
63
dih.credit_account_id,
63
dih.credit_account_id,
64
dih.invoice_voucher,
64
dih.invoice_voucher,
65
dih.customer_id,
65
dih.customer_id,
66
(SELECT c.name FROM customers c WHERE c.id = dih.customer_id) AS customer_name,
66
(SELECT c.name FROM customers c WHERE c.id = dih.customer_id) AS customer_name,
67
dih.due_date,
67
dih.due_date,
68
dih.invoice_date,
68
dih.invoice_date,
69
dih.payment_term,
69
dih.payment_term,
70
dih.total_amount,
70
dih.total_amount,
71
dih.settled_amount,
71
dih.setteled_amount,
72
dih.currency_id,
72
dih.currency_id,
73
(SELECT ist.name FROM invoice_statuses ist WHERE ist.id = dih.invoice_status_id) AS invoice_status,
73
(SELECT ist.name FROM invoice_statuses ist WHERE ist.id = dih.invoice_status_id) AS invoice_status,
74
dih.invoice_status_id,
74
dih.invoice_status_id,
75
dih.discount,
75
dih.discount,
76
dih.note,
76
dih.note,
77
dih.created_on_utc AS created_on,
77
dih.created_on_utc AS created_on,
78
dih.modified_on_utc AS modified_on,
78
dih.modified_on_utc AS modified_on,
79
dih.created_by,
79
dih.created_by,
80
dih.modified_by,
80
dih.modified_by,
81
dih.source_warehouse_id,
81
dih.source_warehouse_id,
82
dih.destination_warehouse_id,
82
dih.destination_warehouse_id,
83
(SELECT w.name FROM warehouses w WHERE w.id = dih.destination_warehouse_id) AS destination_warehouse_name,
83
(SELECT w.name FROM warehouses w WHERE w.id = dih.destination_warehouse_id) AS destination_warehouse_name,
84
(SELECT JSONB_AGG(
84
(SELECT JSONB_AGG(
85
JSONB_BUILD_OBJECT(
85
JSONB_BUILD_OBJECT(
86
'id', id,
86
'id', id,
87
'product_id', product_id,
87
'product_id', product_id,
88
'price', price,
88
'price', price,
89
'quantity', quantity,
89
'quantity', quantity,
90
'fees', fees,
90
'fees', fees,
91
'discount', discount,
91
'discount', discount,
92
'taxable_amount', taxable_amount,
92
'taxable_amount', taxable_amount,
93
'sgst_amount', sgst_amount,
93
'sgst_amount', sgst_amount,
94
'cgst_amount', cgst_amount,
94
'cgst_amount', cgst_amount,
95
'igst_amount', igst_amount,
95
'igst_amount', igst_amount,
96
'total_amount', total_amount,
96
'total_amount', total_amount,
97
'serial_number', serial_number
97
'serial_number', serial_number
98
)
98
)
99
)
99
)
100
FROM draft_invoice_details
100
FROM draft_invoice_details
101
WHERE invoice_header_id = dih.id) AS invoice_lines,
101
WHERE invoice_header_id = dih.id) AS invoice_lines,
102
dih.type AS invoice_type,
102
dih.type AS invoice_type,
103
dih.is_created_by_scheduler
103
dih.is_created_by_scheduler
104
FROM draft_invoice_headers dih
104
FROM draft_invoice_headers dih
105
WHERE dih.company_id = company_id
105
WHERE dih.company_id = company_id
106
AND dih.invoice_date BETWEEN financial_year_start AND financial_year_end;
106
AND dih.invoice_date BETWEEN financial_year_start AND financial_year_end;
107
END;
107
END;
108
$function$
108
$function$
|
|||||
| Function | apply_penalty_for_due_invoices | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.apply_penalty_for_due_invoices(p_product_id uuid, p_company_id uuid, p_penalty_receivable_account_id uuid, p_revenue_account_id uuid, p_created_by uuid, p_batch_size integer DEFAULT 10, p_penalty_date date DEFAULT CURRENT_DATE)
1
CREATE OR REPLACE FUNCTION public.apply_penalty_for_due_invoices(p_product_id uuid, p_company_id uuid, p_penalty_receivable_account_id uuid, p_revenue_account_id uuid, p_created_by uuid, p_batch_size integer DEFAULT 10, p_penalty_date date DEFAULT CURRENT_DATE)
2
RETURNS TABLE(invoice_header_id uuid, credit_account uuid, debit_account uuid, company_id uuid, customer_id uuid, invoice_number text, penalty_amount numeric)
2
RETURNS TABLE(invoice_header_id uuid, credit_account uuid, debit_account uuid, company_id uuid, customer_id uuid, invoice_number text, penalty_amount numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
cursor_invoice CURSOR FOR
6
cursor_invoice CURSOR FOR
7
SELECT
7
SELECT
8
ih.id AS invoice_header_id,
8
ih.id AS invoice_header_id,
9
ih.total_amount,
9
ih.total_amount,
10
ih.settled_amount,
10
ih.setteled_amount,
11
ih.customer_id,
11
ih.customer_id,
12
ih.invoice_number,
12
ih.invoice_number,
13
ih.due_date,
13
ih.due_date,
14
pc.percentage as percentage,
14
pc.percentage as percentage,
15
pf.name AS frequency
15
pf.name AS frequency
16
FROM
16
FROM
17
invoice_headers ih
17
invoice_headers ih
18
JOIN
18
JOIN
19
penalty_configs pc ON ih.penalty_config_id = pc.id
19
penalty_configs pc ON ih.penalty_config_id = pc.id
20
JOIN
20
JOIN
21
penalty_frequencies pf ON pc.penalty_frequency_id = pf.id
21
penalty_frequencies pf ON pc.penalty_frequency_id = pf.id
22
LEFT JOIN
22
LEFT JOIN
23
penalty_processing_logs pl ON ih.id = pl.invoice_id AND pl.processing_date = p_penalty_date
23
penalty_processing_logs pl ON ih.id = pl.invoice_id AND pl.processing_date = p_penalty_date
24
WHERE
24
WHERE
25
ih.company_id = p_company_id
25
ih.company_id = p_company_id
26
AND public.check_penalty_date(ih.due_date::date, p_penalty_date) -- Apply penalty one day after the due date
26
AND public.check_penalty_date(ih.due_date::date, p_penalty_date) -- Apply penalty one day after the due date
27
AND ih.invoice_status_id IN (3, 4)-- Approved or Partially Paid
27
AND ih.invoice_status_id IN (3, 4)-- Approved or Partially Paid
28
AND (pl.invoice_id IS NULL OR pl.status = 'failed'); -- Only process unprocessed or failed ones
28
AND (pl.invoice_id IS NULL OR pl.status = 'failed'); -- Only process unprocessed or failed ones
29
29
30
invoice RECORD;
30
invoice RECORD;
31
penalty_amount NUMERIC;
31
penalty_amount NUMERIC;
32
frequency_factor NUMERIC;
32
frequency_factor NUMERIC;
33
base_amount NUMERIC;
33
base_amount NUMERIC;
34
batch_count INT := 0;
34
batch_count INT := 0;
35
invoice_header_id uuid;
35
invoice_header_id uuid;
36
invoice_details_id uuid;
36
invoice_details_id uuid;
37
37
38
BEGIN
38
BEGIN
39
-- Create a temporary table to store results
39
-- Create a temporary table to store results
40
CREATE TEMP TABLE temp_penalty_results (
40
CREATE TEMP TABLE temp_penalty_results (
41
invoice_header_id UUID,
41
invoice_header_id UUID,
42
credit_account UUID,
42
credit_account UUID,
43
debit_account UUID,
43
debit_account UUID,
44
company_id UUID,
44
company_id UUID,
45
customer_id UUID,
45
customer_id UUID,
46
invoice_number TEXT,
46
invoice_number TEXT,
47
penalty_amount NUMERIC
47
penalty_amount NUMERIC
48
) ON COMMIT DROP;
48
) ON COMMIT DROP;
49
49
50
OPEN cursor_invoice;
50
OPEN cursor_invoice;
51
51
52
LOOP
52
LOOP
53
-- Begin a new transaction batch
53
-- Begin a new transaction batch
54
BEGIN
54
BEGIN
55
-- Fetch the first record in the current batch
55
-- Fetch the first record in the current batch
56
FETCH cursor_invoice INTO invoice;
56
FETCH cursor_invoice INTO invoice;
57
57
58
EXIT WHEN NOT FOUND; -- Exit if no more records to process
58
EXIT WHEN NOT FOUND; -- Exit if no more records to process
59
59
60
-- Process up to p_batch_size records within this transaction
60
-- Process up to p_batch_size records within this transaction
61
FOR i IN 1 .. p_batch_size LOOP
61
FOR i IN 1 .. p_batch_size LOOP
62
-- Exit the inner loop if there are no more records
62
-- Exit the inner loop if there are no more records
63
EXIT WHEN NOT FOUND;
63
EXIT WHEN NOT FOUND;
64
64
65
BEGIN
65
BEGIN
66
-- Track the penalty application start
66
-- Track the penalty application start
67
INSERT INTO penalty_processing_logs (invoice_id, processing_date, status)
67
INSERT INTO penalty_processing_logs (invoice_id, processing_date, status)
68
VALUES (invoice.invoice_header_id, p_penalty_date, 'pending')
68
VALUES (invoice.invoice_header_id, p_penalty_date, 'pending')
69
ON CONFLICT (invoice_id, processing_date) DO UPDATE SET status = 'pending';
69
ON CONFLICT (invoice_id, processing_date) DO UPDATE SET status = 'pending';
70
70
71
-- Calculate penalty amount
71
-- Calculate penalty amount
72
base_amount := CASE
72
base_amount := CASE
73
WHEN invoice.settled_amount IS NOT NULL THEN (invoice.total_amount - invoice.settled_amount)
73
WHEN invoice.setteled_amount IS NOT NULL THEN (invoice.total_amount - invoice.setteled_amount)
74
ELSE invoice.total_amount
74
ELSE invoice.total_amount
75
END;
75
END;
76
RAISE NOTICE 'base_amount : %', base_amount;
76
RAISE NOTICE 'base_amount : %', base_amount;
77
77
78
frequency_factor := CASE invoice.frequency
78
frequency_factor := CASE invoice.frequency
79
WHEN 'Yearly' THEN (invoice.percentage / 100) / 12
79
WHEN 'Yearly' THEN (invoice.percentage / 100) / 12
80
WHEN 'Monthly' THEN (invoice.percentage / 100)
80
WHEN 'Monthly' THEN (invoice.percentage / 100)
81
ELSE 0
81
ELSE 0
82
END;
82
END;
83
RAISE NOTICE 'frequency_factor : %', frequency_factor;
83
RAISE NOTICE 'frequency_factor : %', frequency_factor;
84
84
85
penalty_amount := ROUND(base_amount * frequency_factor);
85
penalty_amount := ROUND(base_amount * frequency_factor);
86
RAISE NOTICE 'penalty_amount : %', penalty_amount;
86
RAISE NOTICE 'penalty_amount : %', penalty_amount;
87
87
88
SELECT gen_random_uuid() INTO invoice_details_id;
88
SELECT gen_random_uuid() INTO invoice_details_id;
89
SELECT gen_random_uuid() INTO invoice_header_id;
89
SELECT gen_random_uuid() INTO invoice_header_id;
90
-- Insert penalty into invoice_details
90
-- Insert penalty into invoice_details
91
91
92
--insert into invoice_headers
92
--insert into invoice_headers
93
CALL public.create_invoice_header(
93
CALL public.create_invoice_header(
94
invoice_header_id,
94
invoice_header_id,
95
p_company_id,
95
p_company_id,
96
invoice.customer_id,
96
invoice.customer_id,
97
p_penalty_receivable_account_id,
97
p_penalty_receivable_account_id,
98
p_revenue_account_id,
98
p_revenue_account_id,
99
p_penalty_date,
99
p_penalty_date,
100
invoice.due_date::date,
100
invoice.due_date::date,
101
0,
101
0,
102
penalty_amount,
102
penalty_amount,
103
0,
103
0,
104
0,
104
0,
105
0,
105
0,
106
penalty_amount,
106
penalty_amount,
107
0,
107
0,
108
0,
108
0,
109
0,
109
0,
110
1,
110
1,
111
'penalty charges',
111
'penalty charges',
112
3,
112
3,
113
p_created_by,
113
p_created_by,
114
'inv',
114
'inv',
115
'00000000-0000-0000-0000-000000000000',
115
'00000000-0000-0000-0000-000000000000',
116
'00000000-0000-0000-0000-000000000000',
116
'00000000-0000-0000-0000-000000000000',
117
null,
117
null,
118
null,
118
null,
119
5
119
5
120
);
120
);
121
121
122
--insert into invoice_details
122
--insert into invoice_details
123
CALL public.create_invoice_detail(
123
CALL public.create_invoice_detail(
124
invoice_header_id,
124
invoice_header_id,
125
penalty_amount,
125
penalty_amount,
126
1,
126
1,
127
0,
127
0,
128
0,
128
0,
129
0,
129
0,
130
0,
130
0,
131
0,
131
0,
132
penalty_amount,
132
penalty_amount,
133
penalty_amount,
133
penalty_amount,
134
1,
134
1,
135
p_product_id
135
p_product_id
136
);
136
);
137
137
138
RAISE NOTICE 'successful inserted in invoiceDetails';
138
RAISE NOTICE 'successful inserted in invoiceDetails';
139
139
140
-- Mark the penalty application as completed in the log
140
-- Mark the penalty application as completed in the log
141
UPDATE penalty_processing_logs
141
UPDATE penalty_processing_logs
142
SET status = 'completed'
142
SET status = 'completed'
143
WHERE invoice_id = invoice.invoice_header_id;
143
WHERE invoice_id = invoice.invoice_header_id;
144
RAISE NOTICE 'successful updated in penalty_processing_logs';
144
RAISE NOTICE 'successful updated in penalty_processing_logs';
145
145
146
-- Insert data into the temporary table for returning at the end
146
-- Insert data into the temporary table for returning at the end
147
INSERT INTO temp_penalty_results (
147
INSERT INTO temp_penalty_results (
148
invoice_header_id, credit_account, debit_account, company_id, customer_id, invoice_number, penalty_amount
148
invoice_header_id, credit_account, debit_account, company_id, customer_id, invoice_number, penalty_amount
149
) VALUES (
149
) VALUES (
150
invoice.invoice_header_id, p_revenue_account_id, p_penalty_receivable_account_id, p_company_id,
150
invoice.invoice_header_id, p_revenue_account_id, p_penalty_receivable_account_id, p_company_id,
151
invoice.customer_id, invoice.invoice_number, penalty_amount
151
invoice.customer_id, invoice.invoice_number, penalty_amount
152
);
152
);
153
153
154
-- Fetch the next invoice for processing within the same batch
154
-- Fetch the next invoice for processing within the same batch
155
FETCH cursor_invoice INTO invoice;
155
FETCH cursor_invoice INTO invoice;
156
EXCEPTION
156
EXCEPTION
157
WHEN OTHERS THEN
157
WHEN OTHERS THEN
158
-- Mark the failed invoices as 'failed' in the log
158
-- Mark the failed invoices as 'failed' in the log
159
UPDATE penalty_processing_logs
159
UPDATE penalty_processing_logs
160
SET status = 'failed'
160
SET status = 'failed'
161
WHERE invoice_id = invoice.invoice_header_id
161
WHERE invoice_id = invoice.invoice_header_id
162
AND processing_date = p_penalty_date;
162
AND processing_date = p_penalty_date;
163
163
164
-- Log the error
164
-- Log the error
165
RAISE NOTICE 'Error in processing invoice %: %', invoice.invoice_header_id, SQLERRM;
165
RAISE NOTICE 'Error in processing invoice %: %', invoice.invoice_header_id, SQLERRM;
166
END;
166
END;
167
END LOOP;
167
END LOOP;
168
168
169
-- Increment batch count for reference
169
-- Increment batch count for reference
170
batch_count := batch_count + 1;
170
batch_count := batch_count + 1;
171
END;
171
END;
172
END LOOP;
172
END LOOP;
173
173
174
CLOSE cursor_invoice;
174
CLOSE cursor_invoice;
175
175
176
-- Return all data from the temporary table at the end
176
-- Return all data from the temporary table at the end
177
RETURN QUERY SELECT * FROM temp_penalty_results;
177
RETURN QUERY SELECT * FROM temp_penalty_results;
178
END;
178
END;
179
$function$
179
$function$
|
|||||
| Function | get_all_invoices | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_invoices(company_id uuid, fin_year_id integer)
1
CREATE OR REPLACE FUNCTION public.get_all_invoices(company_id uuid, fin_year_id integer)
2
RETURNS TABLE(invoice_id uuid, invoice_number character varying, credit_account_id uuid, invoice_voucher character varying, customer_id uuid, customer_name character varying, due_date date, invoice_date date, payment_term character varying, total_amount numeric, settled_amount numeric, currency_id integer, invoice_status character varying, invoice_status_id integer, discount numeric, note character varying, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name character varying, invoice_lines jsonb, type character varying, is_created_by_scheduler boolean)
2
RETURNS TABLE(invoice_id uuid, invoice_number character varying, credit_account_id uuid, invoice_voucher character varying, customer_id uuid, customer_name character varying, due_date date, invoice_date date, payment_term character varying, total_amount numeric, settled_amount numeric, currency_id integer, invoice_status character varying, invoice_status_id integer, discount numeric, note character varying, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name character varying, invoice_lines jsonb, type character varying, is_created_by_scheduler boolean)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
RETURN QUERY
6
RETURN QUERY
7
SELECT
7
SELECT
8
ih.id AS invoice_id,
8
ih.id AS invoice_id,
9
ih.invoice_number::character varying, -- Cast to character varying
9
ih.invoice_number::character varying, -- Cast to character varying
10
ih.credit_account_id,
10
ih.credit_account_id,
11
ih.invoice_voucher::character varying, -- Cast to character varying
11
ih.invoice_voucher::character varying, -- Cast to character varying
12
ih.customer_id,
12
ih.customer_id,
13
(SELECT c.name::character varying FROM customers c WHERE c.id = ih.customer_id) AS customer_name,
13
(SELECT c.name::character varying FROM customers c WHERE c.id = ih.customer_id) AS customer_name,
14
ih.due_date::date, -- Cast to date
14
ih.due_date::date, -- Cast to date
15
ih.invoice_date::date, -- Cast to date
15
ih.invoice_date::date, -- Cast to date
16
ih.payment_term::character varying, -- Cast to character varying
16
ih.payment_term::character varying, -- Cast to character varying
17
ih.total_amount,
17
ih.total_amount,
18
ih.settled_amount,
18
ih.setteled_amount,
19
ih.currency_id,
19
ih.currency_id,
20
(SELECT s.name::character varying FROM invoice_statuses s WHERE s.id = ih.invoice_status_id) AS invoice_status,
20
(SELECT s.name::character varying FROM invoice_statuses s WHERE s.id = ih.invoice_status_id) AS invoice_status,
21
ih.invoice_status_id, -- Already integer
21
ih.invoice_status_id, -- Already integer
22
ih.discount,
22
ih.discount,
23
ih.note::character varying, -- Cast to character varying
23
ih.note::character varying, -- Cast to character varying
24
ih.created_on_utc,
24
ih.created_on_utc,
25
ih.modified_on_utc,
25
ih.modified_on_utc,
26
ih.created_by,
26
ih.created_by,
27
ih.modified_by,
27
ih.modified_by,
28
ih.source_warehouse_id,
28
ih.source_warehouse_id,
29
ih.destination_warehouse_id,
29
ih.destination_warehouse_id,
30
(SELECT w.name::character varying FROM warehouses w WHERE w.id = ih.destination_warehouse_id) AS destination_warehouse_name,
30
(SELECT w.name::character varying FROM warehouses w WHERE w.id = ih.destination_warehouse_id) AS destination_warehouse_name,
31
(
31
(
32
SELECT jsonb_agg(
32
SELECT jsonb_agg(
33
jsonb_build_object(
33
jsonb_build_object(
34
'id', d.id,
34
'id', d.id,
35
'product_id', d.product_id,
35
'product_id', d.product_id,
36
'price', d.price,
36
'price', d.price,
37
'quantity', d.quantity,
37
'quantity', d.quantity,
38
'fees', d.fees,
38
'fees', d.fees,
39
'discount', d.discount,
39
'discount', d.discount,
40
'taxable_amount', d.taxable_amount,
40
'taxable_amount', d.taxable_amount,
41
'sgst_amount', d.sgst_amount,
41
'sgst_amount', d.sgst_amount,
42
'cgst_amount', d.cgst_amount,
42
'cgst_amount', d.cgst_amount,
43
'igst_amount', d.igst_amount,
43
'igst_amount', d.igst_amount,
44
'total_amount', d.total_amount,
44
'total_amount', d.total_amount,
45
'serial_number', d.serial_number
45
'serial_number', d.serial_number
46
)
46
)
47
)
47
)
48
FROM invoice_details d WHERE d.invoice_header_id = ih.id
48
FROM invoice_details d WHERE d.invoice_header_id = ih.id
49
) AS invoice_lines,
49
) AS invoice_lines,
50
ih.type::character varying, -- Cast to character varying
50
ih.type::character varying, -- Cast to character varying
51
ih.is_created_by_scheduler
51
ih.is_created_by_scheduler
52
FROM invoice_headers ih
52
FROM invoice_headers ih
53
WHERE ih.company_id = get_all_invoices.company_id
53
WHERE ih.company_id = get_all_invoices.company_id
54
AND ih.invoice_date BETWEEN MAKE_DATE(get_all_invoices.fin_year_id, 4, 1) AND MAKE_DATE(get_all_invoices.fin_year_id + 1, 3, 31)
54
AND ih.invoice_date BETWEEN MAKE_DATE(get_all_invoices.fin_year_id, 4, 1) AND MAKE_DATE(get_all_invoices.fin_year_id + 1, 3, 31)
55
UNION ALL
55
UNION ALL
56
SELECT
56
SELECT
57
dih.id AS invoice_id,
57
dih.id AS invoice_id,
58
dih.invoice_number::character varying, -- Cast to character varying
58
dih.invoice_number::character varying, -- Cast to character varying
59
dih.credit_account_id,
59
dih.credit_account_id,
60
dih.invoice_voucher::character varying, -- Cast to character varying
60
dih.invoice_voucher::character varying, -- Cast to character varying
61
dih.customer_id,
61
dih.customer_id,
62
(SELECT c.name::character varying FROM customers c WHERE c.id = dih.customer_id) AS customer_name,
62
(SELECT c.name::character varying FROM customers c WHERE c.id = dih.customer_id) AS customer_name,
63
dih.due_date::date, -- Cast to date
63
dih.due_date::date, -- Cast to date
64
dih.invoice_date::date, -- Cast to date
64
dih.invoice_date::date, -- Cast to date
65
dih.payment_term::character varying, -- Cast to character varying
65
dih.payment_term::character varying, -- Cast to character varying
66
dih.total_amount,
66
dih.total_amount,
67
dih.settled_amount,
67
dih.setteled_amount,
68
dih.currency_id,
68
dih.currency_id,
69
(SELECT s.name::character varying FROM invoice_statuses s WHERE s.id = dih.invoice_status_id) AS invoice_status,
69
(SELECT s.name::character varying FROM invoice_statuses s WHERE s.id = dih.invoice_status_id) AS invoice_status,
70
dih.invoice_status_id, -- Already integer
70
dih.invoice_status_id, -- Already integer
71
dih.discount,
71
dih.discount,
72
dih.note::character varying, -- Cast to character varying
72
dih.note::character varying, -- Cast to character varying
73
dih.created_on_utc,
73
dih.created_on_utc,
74
dih.modified_on_utc,
74
dih.modified_on_utc,
75
dih.created_by,
75
dih.created_by,
76
dih.modified_by,
76
dih.modified_by,
77
dih.source_warehouse_id,
77
dih.source_warehouse_id,
78
dih.destination_warehouse_id,
78
dih.destination_warehouse_id,
79
(SELECT w.name::character varying FROM warehouses w WHERE w.id = dih.destination_warehouse_id) AS destination_warehouse_name,
79
(SELECT w.name::character varying FROM warehouses w WHERE w.id = dih.destination_warehouse_id) AS destination_warehouse_name,
80
(
80
(
81
SELECT jsonb_agg(
81
SELECT jsonb_agg(
82
jsonb_build_object(
82
jsonb_build_object(
83
'id', d.id,
83
'id', d.id,
84
'product_id', d.product_id,
84
'product_id', d.product_id,
85
'price', d.price,
85
'price', d.price,
86
'quantity', d.quantity,
86
'quantity', d.quantity,
87
'fees', d.fees,
87
'fees', d.fees,
88
'discount', d.discount,
88
'discount', d.discount,
89
'taxable_amount', d.taxable_amount,
89
'taxable_amount', d.taxable_amount,
90
'sgst_amount', d.sgst_amount,
90
'sgst_amount', d.sgst_amount,
91
'cgst_amount', d.cgst_amount,
91
'cgst_amount', d.cgst_amount,
92
'igst_amount', d.igst_amount,
92
'igst_amount', d.igst_amount,
93
'total_amount', d.total_amount,
93
'total_amount', d.total_amount,
94
'serial_number', d.serial_number
94
'serial_number', d.serial_number
95
)
95
)
96
)
96
)
97
FROM draft_invoice_details d WHERE d.invoice_header_id = dih.id
97
FROM draft_invoice_details d WHERE d.invoice_header_id = dih.id
98
) AS invoice_lines,
98
) AS invoice_lines,
99
dih.type::character varying, -- Cast to character varying
99
dih.type::character varying, -- Cast to character varying
100
dih.is_created_by_scheduler
100
dih.is_created_by_scheduler
101
FROM draft_invoice_headers dih
101
FROM draft_invoice_headers dih
102
WHERE dih.company_id = get_all_invoices.company_id
102
WHERE dih.company_id = get_all_invoices.company_id
103
AND dih.invoice_date BETWEEN MAKE_DATE(get_all_invoices.fin_year_id, 4, 1) AND MAKE_DATE(get_all_invoices.fin_year_id + 1, 3, 31);
103
AND dih.invoice_date BETWEEN MAKE_DATE(get_all_invoices.fin_year_id, 4, 1) AND MAKE_DATE(get_all_invoices.fin_year_id + 1, 3, 31);
104
END;
104
END;
105
$function$
105
$function$
|
|||||
| Function | get_grouped_invoice_header | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_header(p_company_id uuid, p_fin_year_id integer)
2
RETURNS TABLE(id uuid, group_invoice_number text, grouped_invoice_template_id uuid, execution_date timestamp without time zone, error_message text, success_count integer, total_count integer, invoice_description text, group_invoice_batch_id text, paid_count integer, total_paid_amount numeric, original_total_amount numeric, remaining_amount numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
7
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
8
BEGIN
9
RETURN QUERY
10
SELECT
11
gih.id,
12
gih.group_invoice_number,
13
gih.group_invoice_template_id,
14
gih.execution_date,
15
gih.error_message,
16
gih.success_count,
17
gih.total_count,
18
templates.invoice_description,
19
gih.group_invoice_batch_id,
20
21
/* Paid invoice count (fully settled invoices only) */
22
CAST(
23
COALESCE(
24
SUM(
25
CASE
26
WHEN ih.settled_amount >= ih.total_amount THEN 1
27
ELSE 0
28
END
29
),
30
0
31
) AS integer
32
) AS paid_count,
33
34
/* ✅ FIX: Cap payment per invoice */
35
COALESCE(
36
SUM(
37
LEAST(ih.settled_amount, ih.total_amount)
38
),
39
0
40
) AS total_paid_amount,
41
42
/* Original invoice total */
43
COALESCE(
44
SUM(ih.total_amount),
45
0
46
) AS original_total_amount,
47
48
/* ✅ FIX: Never negative remaining */
49
COALESCE(
50
SUM(
51
GREATEST(ih.total_amount - ih.settled_amount, 0)
52
),
53
0
54
) AS remaining_amount
55
56
FROM public.group_invoice_headers gih
57
INNER JOIN public.group_invoice_templates templates
58
ON templates.id = gih.group_invoice_template_id
59
60
LEFT JOIN public.group_invoice_details gid
61
ON gid.group_invoice_header_id = gih.id
62
AND gid.is_deleted = false
63
AND gid.company_id = p_company_id
64
65
LEFT JOIN public.invoice_headers ih
66
ON ih.id = gid.invoice_header_id
67
AND ih.is_deleted = false
68
AND ih.company_id = p_company_id
69
70
WHERE gih.company_id = p_company_id
71
AND gih.execution_date::date BETWEEN v_start_date AND v_end_date
72
73
GROUP BY
74
gih.id,
75
gih.group_invoice_number,
76
gih.group_invoice_template_id,
77
gih.execution_date,
78
gih.error_message,
79
gih.success_count,
80
gih.total_count,
81
templates.invoice_description,
82
gih.group_invoice_batch_id
83
84
ORDER BY gih.execution_date DESC;
85
END;
86
$function$
|
|||||
| Function | get_grouped_invoice_details | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_details(p_group_invoice_header_id uuid)
1
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_details(p_group_invoice_header_id uuid)
2
RETURNS TABLE(invoice_header_id uuid, invoice_number text, invoice_date timestamp without time zone, payment_term numeric, due_date timestamp without time zone, total_amount numeric, settled_amount numeric, taxable_amount numeric, discount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, round_off numeric, note text, customer_id uuid, customer_name text, invoice_status_id integer, invoice_status text, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, credit_account_id uuid, invoice_voucher text, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, type integer, is_created_by_scheduler boolean)
2
RETURNS TABLE(invoice_header_id uuid, invoice_number text, invoice_date timestamp without time zone, payment_term numeric, due_date timestamp without time zone, total_amount numeric, setteled_amount numeric, taxable_amount numeric, discount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, round_off numeric, note text, customer_id uuid, customer_name text, invoice_status_id integer, invoice_status text, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, credit_account_id uuid, invoice_voucher text, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, type integer, is_created_by_scheduler boolean)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
RETURN QUERY
6
RETURN QUERY
7
SELECT
7
SELECT
8
ih.id AS invoice_header_id,
8
ih.id AS invoice_header_id,
9
ih.invoice_number::text AS invoice_number, -- Explicit cast
9
ih.invoice_number::text AS invoice_number, -- Explicit cast
10
ih.invoice_date,
10
ih.invoice_date,
11
ih.payment_term::numeric AS payment_term, -- Cast to numeric
11
ih.payment_term::numeric AS payment_term, -- Cast to numeric
12
ih.due_date,
12
ih.due_date,
13
ih.total_amount,
13
ih.total_amount,
14
ih.settled_amount,
14
ih.setteled_amount,
15
ih.taxable_amount,
15
ih.taxable_amount,
16
ih.discount,
16
ih.discount,
17
ih.sgst_amount,
17
ih.sgst_amount,
18
ih.cgst_amount,
18
ih.cgst_amount,
19
ih.igst_amount,
19
ih.igst_amount,
20
ih.round_off,
20
ih.round_off,
21
ih.note::text AS note, -- Explicit cast
21
ih.note::text AS note, -- Explicit cast
22
ih.customer_id,
22
ih.customer_id,
23
COALESCE(c.name, 'Unknown')::text AS customer_name, -- Explicit cast
23
COALESCE(c.name, 'Unknown')::text AS customer_name, -- Explicit cast
24
ih.invoice_status_id,
24
ih.invoice_status_id,
25
COALESCE(s.name, 'Unknown')::text AS invoice_status, -- Explicit cast
25
COALESCE(s.name, 'Unknown')::text AS invoice_status, -- Explicit cast
26
ih.currency_id,
26
ih.currency_id,
27
ih.created_on_utc,
27
ih.created_on_utc,
28
ih.modified_on_utc,
28
ih.modified_on_utc,
29
ih.credit_account_id,
29
ih.credit_account_id,
30
ih.invoice_voucher::text AS invoice_voucher, -- Explicit cast
30
ih.invoice_voucher::text AS invoice_voucher, -- Explicit cast
31
ih.created_by,
31
ih.created_by,
32
ih.modified_by,
32
ih.modified_by,
33
ih.source_warehouse_id,
33
ih.source_warehouse_id,
34
ih.destination_warehouse_id,
34
ih.destination_warehouse_id,
35
COALESCE(w.name, 'Unknown')::text AS destination_warehouse_name, -- Explicit cast
35
COALESCE(w.name, 'Unknown')::text AS destination_warehouse_name, -- Explicit cast
36
ih.type,
36
ih.type,
37
ih.is_created_by_scheduler
37
ih.is_created_by_scheduler
38
FROM
38
FROM
39
public.group_invoice_details aid
39
public.group_invoice_details aid
40
INNER JOIN
40
INNER JOIN
41
invoice_headers ih ON aid.invoice_header_id = ih.id
41
invoice_headers ih ON aid.invoice_header_id = ih.id
42
LEFT JOIN
42
LEFT JOIN
43
customers c ON ih.customer_id = c.id
43
customers c ON ih.customer_id = c.id
44
LEFT JOIN
44
LEFT JOIN
45
invoice_statuses s ON ih.invoice_status_id = s.id
45
invoice_statuses s ON ih.invoice_status_id = s.id
46
LEFT JOIN
46
LEFT JOIN
47
warehouses w ON ih.destination_warehouse_id = w.id
47
warehouses w ON ih.destination_warehouse_id = w.id
48
WHERE
48
WHERE
49
aid.group_invoice_header_id = p_group_invoice_header_id
49
aid.group_invoice_header_id = p_group_invoice_header_id
50
AND NOT aid.is_deleted
50
AND NOT aid.is_deleted
51
AND NOT ih.is_deleted;
51
AND NOT ih.is_deleted;
52
END;
52
END;
53
$function$
53
$function$
|
|||||
| Function | get_unit_dues_by_cust_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_unit_dues_by_cust_id(p_company_id uuid, p_customer_id uuid)
2
RETURNS TABLE(id uuid, name text, total_due numeric, total_paid numeric, original_amount numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
6
BEGIN
7
RETURN QUERY
8
SELECT
9
c.id::uuid,
10
c.name::text,
11
COALESCE(SUM(
12
CASE
13
WHEN i.due_date < now()
14
AND i.is_deleted = false
15
AND (i.total_amount - i.settled_amount) > 0
16
THEN (i.total_amount - i.settled_amount)
17
ELSE 0
18
END
19
), 0)::numeric AS total_due,
20
COALESCE(SUM(
21
CASE
22
WHEN i.due_date < now()
23
AND i.is_deleted = false
24
THEN i.settled_amount
25
ELSE 0
26
END
27
), 0)::numeric AS total_paid,
28
COALESCE(SUM(i.total_amount), 0)::numeric AS original_amount
29
FROM public.customers c
30
LEFT JOIN public.invoice_headers i
31
ON i.customer_id = c.id AND i.company_id = p_company_id
32
WHERE c.is_deleted = false
33
AND c.company_id = p_company_id
34
AND c.id = p_customer_id
35
GROUP BY c.id, c.name;
36
END;
37
$function$
|
|||||
| Function | get_all_customers_details | Match | ||||||
| Function | get_customer_by_id | Match | ||||||
| Function | get_user_by_username | Match | ||||||
| Function | get_invoice_company_approvers | Match | ||||||
| Function | create_user | Match | ||||||
| Function | get_account_total_amount_expense | Match | ||||||
| Function | get_all_customers_details_byorg | Match | ||||||
| Function | get_all_invoice_account_approvers | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_invoice_account_approvers(p_company_id uuid)
1
CREATE OR REPLACE FUNCTION public.get_all_invoice_account_approvers(p_company_id uuid)
2
RETURNS TABLE(id integer, account_id uuid, status_id integer, user_id uuid, approval_level integer, required_approval_levels integer)
2
RETURNS TABLE(id integer, account_id uuid, status_id integer, user_id uuid, approval_level integer)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
RETURN QUERY
6
RETURN QUERY
7
SELECT
7
SELECT
8
ia.id,
8
ia.id,
9
ia.account_id,
9
ia.account_id,
10
ia.status_id, -- Use ia.status_id here (the user's approval status)
10
ia.status_id,
11
ia.user_id,
11
ia.user_id,
12
ia.approval_level,
12
ia.approval_level
13
iaal.approval_level_required AS required_approval_levels
14
FROM invoice_approval_users_account ia
13
FROM invoice_approval_users_account ia
15
JOIN invoice_account_approval_levels iaal
16
ON ia.company_id = iaal.company_id
17
AND ia.account_id = iaal.account_id
18
WHERE ia.company_id = p_company_id
14
WHERE ia.company_id = p_company_id
19
AND ia.is_deleted = FALSE
15
AND ia.is_deleted = false
20
AND iaal.is_deleted = FALSE
21
AND iaal.status_id = 2 -- Filter for status_id = 2
22
ORDER BY ia.account_id, ia.status_id;
16
ORDER BY ia.account_id, ia.status_id;
23
END;
17
END;
24
$function$
18
$function$
|
|||||
| Function | upsert_customer_default_account | Match | ||||||
| Function | get_invoice_approval_log | Match | ||||||
| Function | get_invoice_lines | Match | ||||||
| Function | get_new_draft_invoice_number | Match | ||||||
| Function | bulk_delete_customers | Match | ||||||
| Function | get_account_total_amount | Match | ||||||
| Function | get_all_customer_notes | Match | ||||||
| Function | get_all_invoice_headers | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_invoice_headers(p_company_id uuid)
2
RETURNS TABLE(id uuid, invoice_number text, invoice_voucher text, customer_id uuid, customer_name text, due_date timestamp with time zone, invoice_date timestamp with time zone, payment_term integer, total_amount numeric, settled_amount numeric, discount numeric, note text, currency_id integer, invoice_status_id integer, invoice_status text, type integer, is_created_by_scheduler boolean, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
-- Query InvoiceHeaders
8
SELECT header.id, header.invoice_number, header.invoice_voucher, header.customer_id,
9
customer.name::text AS customer_name,
10
header.due_date::timestamptz, header.invoice_date::timestamptz, header.payment_term,
11
header.total_amount, header.settled_amount, header.discount, header.note,
12
header.currency_id, header.invoice_status_id, status.name::text AS invoice_status, header.type, header.is_created_by_scheduler,
13
header.created_by,
14
COALESCE(u_created.first_name || ' ' || u_created.last_name, 'N/A') AS created_by_name, -- full name or 'N/A'
15
header.modified_by,
16
COALESCE(u_modified.first_name || ' ' || u_modified.last_name, 'N/A') AS modified_by_name -- full name or 'N/A'
17
FROM invoice_headers header
18
JOIN customers customer ON customer.id = header.customer_id
19
JOIN invoice_statuses status ON status.id = header.invoice_status_id
20
LEFT JOIN users u_created ON u_created.id = header.created_by -- join to get the created_by user's full name
21
LEFT JOIN users u_modified ON u_modified.id = header.modified_by -- join to get the modified_by user's full name
22
WHERE header.company_id = p_company_id
23
AND header.is_deleted = false
24
25
UNION ALL
26
27
-- Query DraftInvoiceHeaders
28
SELECT draft.id, draft.invoice_number, draft.invoice_voucher, draft.customer_id,
29
customer.name::text AS customer_name,
30
draft.due_date::timestamptz, draft.invoice_date::timestamptz, draft.payment_term,
31
draft.total_amount, draft.settled_amount, draft.discount, draft.note,
32
draft.currency_id, draft.invoice_status_id, status.name::text AS invoice_status, draft.type, draft.is_created_by_scheduler,
33
draft.created_by,
34
COALESCE(u_created.first_name || ' ' || u_created.last_name, 'N/A') AS created_by_name, -- full name or 'N/A'
35
draft.modified_by,
36
COALESCE(u_modified.first_name || ' ' || u_modified.last_name, 'N/A') AS modified_by_name -- full name or 'N/A'
37
FROM draft_invoice_headers draft
38
JOIN customers customer ON customer.id = draft.customer_id
39
JOIN invoice_statuses status ON status.id = draft.invoice_status_id
40
LEFT JOIN users u_created ON u_created.id = draft.created_by -- join to get the created_by user's full name
41
LEFT JOIN users u_modified ON u_modified.id = draft.modified_by -- join to get the modified_by user's full name
42
WHERE draft.company_id = p_company_id
43
AND draft.is_deleted = false;
44
END;
45
$function$
|
|||||
| Function | get_group_invoice_stats | Match | ||||||
| Function | get_invoice_workflow_config | Match | ||||||
| Function | get_new_invoice_number | Match | ||||||
| Function | update_invoice_next_status_main | Match | ||||||
| Function | get_invoice_header_by_id | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_invoice_header_by_id(p_invoice_header_id uuid)
1
CREATE OR REPLACE FUNCTION public.get_invoice_header_by_id(p_invoice_header_id uuid)
2
RETURNS TABLE(id uuid, invoice_number text, invoice_voucher text, customer_id uuid, customer_name text, due_date timestamp with time zone, invoice_date timestamp with time zone, payment_term integer, total_amount numeric, settled_amount numeric, discount numeric, note text, currency_id integer, invoice_status_id integer, invoice_status text, type integer, is_created_by_scheduler boolean)
2
RETURNS TABLE(id uuid, invoice_number text, invoice_voucher text, customer_id uuid, customer_name text, due_date timestamp with time zone, invoice_date timestamp with time zone, payment_term integer, total_amount numeric, setteled_amount numeric, discount numeric, note text, currency_id integer, invoice_status_id integer, invoice_status text, type integer, is_created_by_scheduler boolean)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
RETURN QUERY
6
RETURN QUERY
7
-- Query for the specific InvoiceHeader
7
-- Query for the specific InvoiceHeader
8
SELECT header.id,
8
SELECT header.id,
9
header.invoice_number,
9
header.invoice_number,
10
header.invoice_voucher,
10
header.invoice_voucher,
11
customer.id AS customer_id,
11
customer.id AS customer_id,
12
customer.name::text AS customer_name, -- Cast to text
12
customer.name::text AS customer_name, -- Cast to text
13
header.due_date::timestamptz,
13
header.due_date::timestamptz,
14
header.invoice_date::timestamptz,
14
header.invoice_date::timestamptz,
15
header.payment_term,
15
header.payment_term,
16
header.total_amount,
16
header.total_amount,
17
header.settled_amount,
17
header.setteled_amount,
18
header.discount,
18
header.discount,
19
header.note,
19
header.note,
20
header.currency_id,
20
header.currency_id,
21
status.id AS invoice_status_id,
21
status.id AS invoice_status_id,
22
status.name::text AS invoice_status, -- Cast to text
22
status.name::text AS invoice_status, -- Cast to text
23
header.type,
23
header.type,
24
header.is_created_by_scheduler
24
header.is_created_by_scheduler
25
FROM invoice_headers header
25
FROM invoice_headers header
26
JOIN customers customer ON customer.id = header.customer_id
26
JOIN customers customer ON customer.id = header.customer_id
27
JOIN invoice_statuses status ON status.id = header.invoice_status_id
27
JOIN invoice_statuses status ON status.id = header.invoice_status_id
28
WHERE header.id = p_invoice_header_id
28
WHERE header.id = p_invoice_header_id
29
AND header.is_deleted = false;
29
AND header.is_deleted = false;
30
END;
30
END;
31
$function$
31
$function$
|
|||||
| Function | get_invoice_payments_by_customer_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_invoice_payments_by_customer_id(p_company_id uuid, p_customer_id uuid, p_from_date date, p_to_date date)
2
RETURNS TABLE(company_id uuid, customer_id uuid, customer_name character varying, id uuid, payment_number text, mode_of_payment text, reference text, received_date timestamp without time zone, received_amount numeric, grand_total_amount numeric, advance_amount numeric, tds_amount numeric, description text, debit_account_id uuid, credit_account_id uuid, transaction_id bigint, is_posted boolean, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, is_deleted boolean)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
iph.company_id,
9
iph.customer_id,
10
c.name AS customer_name,
11
iph.id AS id,
12
iph.payment_number,
13
iph.mode_of_payment,
14
iph.reference,
15
iph.received_date,
16
iph.received_amount,
17
iph.grand_total_amount,
18
iph.advance_amount,
19
iph.tds_amount,
20
iph.description,
21
iph.debit_account_id,
22
iph.credit_account_id,
23
iph.transaction_id,
24
iph.is_posted,
25
iph.created_on_utc,
26
iph.modified_on_utc,
27
iph.is_deleted
28
FROM
29
invoice_payment_headers iph
30
INNER JOIN
31
customers c ON iph.customer_id = c.id
32
WHERE
33
iph.company_id = p_company_id
34
AND iph.customer_id = p_customer_id
35
AND iph.is_deleted = FALSE
36
AND c.is_deleted = FALSE
37
AND iph.received_date BETWEEN p_from_date AND p_to_date
38
ORDER BY
39
iph.received_date DESC;
40
END;
41
$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, p_from_date date, p_to_date date)
2
RETURNS TABLE(company_id uuid, customer_id uuid, customer_name character varying, id uuid, invoice_number text, invoice_voucher text, invoice_date timestamp without time zone, due_date timestamp without time zone, total_amount numeric, settled_amount numeric, due_amount numeric, payment_status_id integer, fin_entry_status_id integer, transaction_id bigint, is_posted boolean, current_approval_level integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, is_deleted boolean)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
i.company_id,
9
i.customer_id,
10
c.name AS customer_name,
11
i.id AS id,
12
i.invoice_number,
13
i.invoice_voucher,
14
i.invoice_date,
15
i.due_date,
16
i.total_amount,
17
i.settled_amount,
18
(i.total_amount - COALESCE(i.settled_amount, 0))::numeric AS due_amount,
19
i.payment_status_id,
20
i.fin_entry_status_id,
21
i.transaction_id,
22
i.is_posted,
23
i.current_approval_level,
24
i.created_on_utc,
25
i.modified_on_utc,
26
i.is_deleted
27
FROM
28
invoice_headers i
29
INNER JOIN
30
customers c ON i.customer_id = c.id
31
WHERE
32
i.company_id = p_company_id
33
AND i.customer_id = p_customer_id
34
AND i.is_deleted = FALSE
35
AND c.is_deleted = FALSE
36
-- Unpaid or partially paid invoices
37
AND (i.settled_amount IS NULL OR i.settled_amount < i.total_amount)
38
-- Due date filter
39
AND i.due_date BETWEEN p_from_date AND p_to_date
40
ORDER BY
41
i.due_date ASC;
42
END;
43
$function$
|
|||||
| Function | get_customers_with_invoice_summary | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_customers_with_invoice_summary(p_company_id uuid, p_fin_year_id integer)
2
RETURNS TABLE(id uuid, name character varying, contact_name text, gst_in text, mobile_number text, email text, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, billing_address jsonb, contact_count integer, total_invoice_amount numeric, total_settled_amount numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
6
DECLARE
7
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
8
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
9
BEGIN
10
RETURN QUERY
11
SELECT
12
c.id,
13
c.name,
14
con.first_name || ' ' || con.last_name AS contact_name,
15
c.gstin AS gst_in,
16
con.mobile_number,
17
con.email,
18
c.created_by,
19
u_created.first_name || ' ' || u_created.last_name AS created_by_name,
20
c.modified_by,
21
u_modified.first_name || ' ' || u_modified.last_name AS modified_by_name,
22
c.created_on_utc,
23
c.modified_on_utc,
24
CASE
25
WHEN ba.id IS NOT NULL THEN jsonb_build_object(
26
'AddressLine1', ba.address_line1,
27
'AddressLine2', ba.address_line2,
28
'ZipCode', ba.zip_code,
29
'CountryId', ba.country_id,
30
'StateId', ba.state_id,
31
'CityId', ba.city_id
32
)
33
ELSE NULL
34
END AS billing_address,
35
(SELECT COUNT(*)::integer FROM public.customer_contacts cc_count WHERE cc_count.customer_id = c.id) AS contact_count,
36
COALESCE(inv_sums.total_invoice_amount, 0) AS total_invoice_amount,
37
COALESCE(inv_sums.total_settled_amount, 0) AS total_settled_amount
38
FROM public.customers c
39
LEFT JOIN public.customer_contacts cc ON c.id = cc.customer_id
40
LEFT JOIN public.contacts con ON cc.contact_id = con.id AND con.is_primary = true
41
LEFT JOIN public.users u_created ON c.created_by = u_created.id
42
LEFT JOIN public.users u_modified ON c.modified_by = u_modified.id
43
LEFT JOIN public.addresses ba ON c.billing_address_id = ba.id
44
LEFT JOIN (
45
SELECT
46
ih.customer_id,
47
SUM(ih.total_amount) AS total_invoice_amount,
48
SUM(ih.settled_amount) AS total_settled_amount
49
FROM public.invoice_headers ih
50
WHERE ih.company_id = p_company_id
51
AND ih.is_deleted = false
52
AND ih.invoice_date >= v_start_date
53
AND ih.invoice_date <= v_end_date
54
GROUP BY ih.customer_id
55
) inv_sums ON inv_sums.customer_id = c.id
56
WHERE c.company_id = p_company_id
57
AND c.is_deleted = false;
58
END;
59
$function$
|
|||||
| Function | get_defaultors | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_defaultors(p_company_id uuid)
1
CREATE OR REPLACE FUNCTION public.get_defaultors(p_company_id uuid)
2
RETURNS TABLE(id uuid, customer_name text, invoice_number text, created_date timestamp without time zone, due_date timestamp without time zone, amount numeric)
2
RETURNS TABLE(id uuid, customer_name text, invoice_number text, created_date timestamp without time zone, due_date timestamp without time zone, amount numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
-- Calculate and return the list of defaultors (overdue invoices)
6
-- Calculate and return the list of defaultors (overdue invoices)
7
RETURN QUERY
7
RETURN QUERY
8
SELECT
8
SELECT
9
gen_random_uuid() AS id, -- Generate a random unique ID
9
gen_random_uuid() AS id, -- Generate a random unique ID
10
c.name::TEXT, -- Customer Name
10
c.name::TEXT, -- Customer Name
11
i.invoice_number, -- Invoice Number
11
i.invoice_number, -- Invoice Number
12
i.created_on_utc AS created_date, -- Created Date
12
i.created_on_utc AS created_date, -- Created Date
13
i.due_date, -- Due Date
13
i.due_date, -- Due Date
14
i.total_amount AS amount -- Total Invoice Amount
14
i.total_amount AS amount -- Total Invoice Amount
15
FROM
15
FROM
16
invoice_headers i
16
invoice_headers i
17
JOIN
17
JOIN
18
customers c ON i.customer_id = c.id
18
customers c ON i.customer_id = c.id
19
JOIN
19
JOIN
20
company_defaultor_configurations cfg ON i.company_id = cfg.company_id
20
company_defaultor_configurations cfg ON i.company_id = cfg.company_id
21
WHERE
21
WHERE
22
i.company_id = p_company_id
22
i.company_id = p_company_id
23
AND i.due_date < CURRENT_DATE - INTERVAL '1 day' * cfg.due_period_days -- Overdue invoices based on company configuration
23
AND i.due_date < CURRENT_DATE - INTERVAL '1 day' * cfg.due_period_days -- Overdue invoices based on company configuration
24
AND i.settled_amount < i.total_amount -- Only unpaid invoices
24
AND i.setteled_amount < i.total_amount -- Only unpaid invoices
25
AND i.is_deleted = FALSE -- Exclude deleted invoices
25
AND i.is_deleted = FALSE -- Exclude deleted invoices
26
AND c.is_deleted = FALSE; -- Exclude deleted customers
26
AND c.is_deleted = FALSE; -- Exclude deleted customers
27
END;
27
END;
28
$function$
28
$function$
|
|||||
| Function | get_execution_invoices | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_execution_invoices(p_execution_id uuid)
2
RETURNS TABLE(invoice_header_id uuid, invoice_number text, invoice_date timestamp without time zone, payment_term numeric, due_date timestamp without time zone, total_amount numeric, settled_amount numeric, taxable_amount numeric, discount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, round_off numeric, note text, customer_id uuid, customer_name text, invoice_status_id integer, invoice_status text, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, credit_account_id uuid, invoice_voucher text, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, type integer, is_created_by_scheduler boolean)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
ih.id AS invoice_header_id,
9
ih.invoice_number::text AS invoice_number, -- Explicit cast
10
ih.invoice_date,
11
ih.payment_term::numeric AS payment_term, -- Cast to numeric
12
ih.due_date,
13
ih.total_amount,
14
ih.settled_amount,
15
ih.taxable_amount,
16
ih.discount,
17
ih.sgst_amount,
18
ih.cgst_amount,
19
ih.igst_amount,
20
ih.round_off,
21
ih.note::text AS note, -- Explicit cast
22
ih.customer_id,
23
COALESCE(c.name, 'Unknown')::text AS customer_name, -- Explicit cast
24
ih.invoice_status_id,
25
COALESCE(s.name, 'Unknown')::text AS invoice_status, -- Explicit cast
26
ih.currency_id,
27
ih.created_on_utc,
28
ih.modified_on_utc,
29
ih.credit_account_id,
30
ih.invoice_voucher::text AS invoice_voucher, -- Explicit cast
31
ih.created_by,
32
ih.modified_by,
33
ih.source_warehouse_id,
34
ih.destination_warehouse_id,
35
COALESCE(w.name, 'Unknown')::text AS destination_warehouse_name, -- Explicit cast
36
ih.type,
37
ih.is_created_by_scheduler
38
FROM
39
apartment_invoice_postings aip
40
INNER JOIN
41
invoice_headers ih ON aip.invoice_header_id = ih.id
42
LEFT JOIN
43
customers c ON ih.customer_id = c.id
44
LEFT JOIN
45
invoice_statuses s ON ih.invoice_status_id = s.id
46
LEFT JOIN
47
warehouses w ON ih.destination_warehouse_id = w.id
48
WHERE
49
aip.execution_id = p_execution_id
50
AND NOT aip.is_deleted
51
AND NOT ih.is_deleted;
52
END;
53
$function$
|
|||||
| Function | get_total_defaultors_and_amount | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_total_defaultors_and_amount(p_company_id uuid)
1
CREATE OR REPLACE FUNCTION public.get_total_defaultors_and_amount(p_company_id uuid)
2
RETURNS TABLE(result_id uuid, total_defaultors integer, total_amount numeric)
2
RETURNS TABLE(result_id uuid, total_defaultors integer, total_amount numeric)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
BEGIN
5
BEGIN
6
-- Generate a unique id for this result
6
-- Generate a unique id for this result
7
result_id := gen_random_uuid();
7
result_id := gen_random_uuid();
8
8
9
-- Debugging: Output the company_id being passed
9
-- Debugging: Output the company_id being passed
10
RAISE NOTICE 'Calculating for company_id: %', p_company_id;
10
RAISE NOTICE 'Calculating for company_id: %', p_company_id;
11
11
12
-- Calculate total defaultors and total amount for overdue invoices
12
-- Calculate total defaultors and total amount for overdue invoices
13
RETURN QUERY
13
RETURN QUERY
14
SELECT
14
SELECT
15
result_id As id, -- Return the generated unique id for each result
15
result_id As id, -- Return the generated unique id for each result
16
COUNT(DISTINCT i.customer_id)::INT AS total_defaultors, -- Cast COUNT to INT
16
COUNT(DISTINCT i.customer_id)::INT AS total_defaultors, -- Cast COUNT to INT
17
SUM(i.total_amount) AS total_amount -- Sum of total amounts of overdue invoices
17
SUM(i.total_amount) AS total_amount -- Sum of total amounts of overdue invoices
18
FROM
18
FROM
19
invoice_headers i
19
invoice_headers i
20
JOIN
20
JOIN
21
customers c ON i.customer_id = c.id -- Join with customers to filter by company_id
21
customers c ON i.customer_id = c.id -- Join with customers to filter by company_id
22
WHERE
22
WHERE
23
i.company_id = p_company_id -- Use the passed-in parameter (p_company_id)
23
i.company_id = p_company_id -- Use the passed-in parameter (p_company_id)
24
AND i.due_date < CURRENT_DATE - (SELECT due_period_days FROM company_defaultor_configurations WHERE company_id = p_company_id LIMIT 1) -- Check for overdue invoices based on due_period_days
24
AND i.due_date < CURRENT_DATE - (SELECT due_period_days FROM company_defaultor_configurations WHERE company_id = p_company_id LIMIT 1) -- Check for overdue invoices based on due_period_days
25
AND i.settled_amount < i.total_amount -- Only unpaid invoices (settled_amount < total_amount)
25
AND i.setteled_amount < i.total_amount -- Only unpaid invoices (settled_amount < total_amount)
26
AND i.is_deleted = FALSE -- Exclude deleted invoices
26
AND i.is_deleted = FALSE -- Exclude deleted invoices
27
AND c.is_deleted = FALSE; -- Exclude deleted customers
27
AND c.is_deleted = FALSE; -- Exclude deleted customers
28
END;
28
END;
29
$function$
29
$function$
|
|||||
| Function | get_grouped_invoice_details | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_details(p_group_invoice_header_id uuid, p_fin_year integer)
2
RETURNS TABLE(invoice_header_id uuid, invoice_number text, invoice_date timestamp without time zone, payment_term numeric, due_date timestamp without time zone, total_amount numeric, settled_amount numeric, taxable_amount numeric, discount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, round_off numeric, note text, customer_id uuid, customer_name text, invoice_status_id integer, invoice_status text, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, credit_account_id uuid, invoice_voucher text, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, type integer, is_created_by_scheduler boolean)
3
LANGUAGE plpgsql
4
AS $function$
5
6
DECLARE
7
v_start_date date := MAKE_DATE(p_fin_year, 4, 1);
8
v_end_date date := MAKE_DATE(p_fin_year + 1, 3, 31);
9
BEGIN
10
RETURN QUERY
11
SELECT
12
ih.id AS invoice_header_id,
13
ih.invoice_number::text AS invoice_number,
14
ih.invoice_date,
15
ih.payment_term::numeric AS payment_term,
16
ih.due_date,
17
ih.total_amount,
18
ih.settled_amount,
19
ih.taxable_amount,
20
ih.discount,
21
ih.sgst_amount,
22
ih.cgst_amount,
23
ih.igst_amount,
24
ih.round_off,
25
ih.note::text AS note,
26
ih.customer_id,
27
COALESCE(c.name, 'Unknown')::text AS customer_name,
28
ih.invoice_status_id,
29
COALESCE(s.name, 'Unknown')::text AS invoice_status,
30
ih.currency_id,
31
ih.created_on_utc,
32
ih.modified_on_utc,
33
ih.credit_account_id,
34
ih.invoice_voucher::text AS invoice_voucher,
35
ih.created_by,
36
ih.modified_by,
37
ih.source_warehouse_id,
38
ih.destination_warehouse_id,
39
COALESCE(w.name, 'Unknown')::text AS destination_warehouse_name,
40
ih.type,
41
ih.is_created_by_scheduler
42
FROM
43
public.group_invoice_details aid
44
INNER JOIN
45
invoice_headers ih ON aid.invoice_header_id = ih.id
46
LEFT JOIN
47
customers c ON ih.customer_id = c.id
48
LEFT JOIN
49
invoice_statuses s ON ih.invoice_status_id = s.id
50
LEFT JOIN
51
warehouses w ON ih.destination_warehouse_id = w.id
52
WHERE
53
aid.group_invoice_header_id = p_group_invoice_header_id
54
AND NOT aid.is_deleted
55
AND NOT ih.is_deleted
56
AND (p_fin_year IS NULL OR (ih.invoice_date >= v_start_date AND ih.invoice_date <= v_end_date));
57
END;
58
$function$
|
|||||
| Function | get_group_invoice_totals | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_group_invoice_totals(p_company_id uuid, p_grouped_invoice_id uuid)
2
RETURNS TABLE(id uuid, grouped_invoice_name text, total_amount numeric, paid_amount numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
6
BEGIN
7
RETURN QUERY
8
SELECT
9
gih.id AS id,
10
gih.group_invoice_number AS grouped_invoice_name,
11
SUM(ih.total_amount) AS total_amount,
12
SUM(
13
CASE
14
WHEN ih.invoice_status_id = 3 THEN ih.settled_amount
15
ELSE 0
16
END
17
) AS paid_amount
18
FROM
19
public.group_invoice_headers gih
20
JOIN
21
public.group_invoice_details gid ON gih.id = gid.group_invoice_header_id
22
JOIN
23
public.invoice_headers ih ON gid.invoice_header_id = ih.id
24
WHERE
25
gih.id = p_grouped_invoice_id
26
AND gih.company_id = p_company_id
27
AND gih.is_deleted = false
28
GROUP BY
29
gih.id,
30
gih.group_invoice_number;
31
END;
32
$function$
|
|||||
| Function | get_invoice_analytics | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_invoice_analytics(p_company_id uuid, p_finance_year_id integer, p_group_invoice_header_id uuid DEFAULT NULL::uuid)
2
RETURNS TABLE(id integer, status text, count integer, total_amount numeric, month_start_date date)
3
LANGUAGE plpgsql
4
AS $function$
5
6
DECLARE
7
v_financial_year_start DATE;
8
v_financial_year_end DATE;
9
BEGIN
10
v_financial_year_start := TO_DATE(p_finance_year_id || '-04-01', 'YYYY-MM-DD');
11
v_financial_year_end := TO_DATE((p_finance_year_id + 1) || '-03-31', 'YYYY-MM-DD');
12
13
-- Case 1: Group Invoice Specific
14
IF p_group_invoice_header_id IS NOT NULL THEN
15
IF NOT EXISTS (
16
SELECT 1
17
FROM group_invoice_headers gih
18
WHERE gih.id = p_group_invoice_header_id
19
AND gih.is_deleted = false
20
) THEN
21
RETURN;
22
END IF;
23
24
RETURN QUERY
25
SELECT row_number() OVER ()::int AS id,
26
'Paid' AS status,
27
COUNT(*)::int,
28
COALESCE(SUM(ih.settled_amount),0),
29
DATE_TRUNC('month', ih.invoice_date)::date
30
FROM group_invoice_details gid
31
JOIN invoice_headers ih ON ih.id = gid.invoice_header_id
32
WHERE gid.group_invoice_header_id = p_group_invoice_header_id
33
AND gid.is_deleted = false
34
AND ih.invoice_status_id IN (4,5)
35
AND ih.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
36
GROUP BY DATE_TRUNC('month', ih.invoice_date)
37
38
UNION ALL
39
SELECT row_number() OVER ()::int,
40
'Pending',
41
COUNT(*)::int,
42
COALESCE(SUM(ih.total_amount - ih.settled_amount),0),
43
DATE_TRUNC('month', ih.invoice_date)::date
44
FROM group_invoice_details gid
45
JOIN invoice_headers ih ON ih.id = gid.invoice_header_id
46
WHERE gid.group_invoice_header_id = p_group_invoice_header_id
47
AND gid.is_deleted = false
48
AND ih.invoice_status_id IN (3,4)
49
AND ih.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
50
GROUP BY DATE_TRUNC('month', ih.invoice_date)
51
52
UNION ALL
53
SELECT row_number() OVER ()::int,
54
'Overdue',
55
COUNT(*)::int,
56
COALESCE(SUM(ih.total_amount - ih.settled_amount),0),
57
DATE_TRUNC('month', ih.invoice_date)::date
58
FROM group_invoice_details gid
59
JOIN invoice_headers ih ON ih.id = gid.invoice_header_id
60
WHERE gid.group_invoice_header_id = p_group_invoice_header_id
61
AND gid.is_deleted = false
62
AND ih.invoice_status_id IN (3,4)
63
AND ih.due_date < now()
64
AND ih.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
65
GROUP BY DATE_TRUNC('month', ih.invoice_date);
66
67
-- Case 2: Company-wide Analytics
68
ELSE
69
RETURN QUERY
70
SELECT row_number() OVER ()::int,
71
'Paid',
72
COUNT(*)::int,
73
COALESCE(SUM(i.settled_amount), 0),
74
DATE_TRUNC('month', i.invoice_date)::date
75
FROM invoice_headers i
76
WHERE i.company_id = p_company_id
77
AND i.invoice_status_id IN (4, 5)
78
AND i.is_deleted = false
79
AND i.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
80
GROUP BY DATE_TRUNC('month', i.invoice_date)
81
82
UNION ALL
83
SELECT row_number() OVER ()::int,
84
'Pending',
85
COUNT(*)::int,
86
COALESCE(SUM(i.total_amount - i.settled_amount), 0),
87
DATE_TRUNC('month', i.invoice_date)::date
88
FROM invoice_headers i
89
WHERE i.company_id = p_company_id
90
AND i.invoice_status_id IN (3, 4)
91
AND i.is_deleted = false
92
AND i.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
93
GROUP BY DATE_TRUNC('month', i.invoice_date)
94
95
UNION ALL
96
SELECT row_number() OVER ()::int,
97
'Overdue',
98
COUNT(*)::int,
99
COALESCE(SUM(i.total_amount - i.settled_amount), 0),
100
DATE_TRUNC('month', i.invoice_date)::date
101
FROM invoice_headers i
102
WHERE i.company_id = p_company_id
103
AND i.invoice_status_id IN (3, 4)
104
AND i.due_date < CURRENT_DATE
105
AND i.is_deleted = false
106
AND i.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
107
GROUP BY DATE_TRUNC('month', i.invoice_date);
108
END IF;
109
END;
110
$function$
|
|||||
| Function | get_grouped_invoice_header_by_limit | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_header_by_limit(p_company_id uuid, p_fin_year_id integer, p_limit integer DEFAULT NULL::integer)
2
RETURNS TABLE(id uuid, group_invoice_number text, grouped_invoice_template_id uuid, execution_date timestamp without time zone, error_message text, success_count integer, total_count integer, invoice_description text, group_invoice_batch_id text, paid_count integer, total_paid_amount numeric, original_total_amount numeric, remaining_amount numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
7
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
8
BEGIN
9
IF p_limit IS NOT NULL THEN
10
-- If p_limit is provided, apply the LIMIT
11
RETURN QUERY
12
SELECT
13
gih.id,
14
gih.group_invoice_number,
15
gih.group_invoice_template_id,
16
gih.execution_date,
17
gih.error_message,
18
gih.success_count,
19
gih.total_count,
20
templates.invoice_description,
21
gih.group_invoice_batch_id,
22
CAST(COALESCE(SUM(CASE WHEN ih.settled_amount >= ih.total_amount THEN 1 ELSE 0 END), 0) AS integer) AS paid_count,
23
COALESCE(SUM(ih.settled_amount), 0) AS total_paid_amount,
24
COALESCE(SUM(ih.total_amount), 0) AS original_total_amount,
25
COALESCE(SUM(ih.total_amount - ih.settled_amount), 0) AS remaining_amount
26
FROM
27
public.group_invoice_headers gih
28
INNER JOIN
29
public.group_invoice_templates templates
30
ON gih.group_invoice_template_id = templates.id
31
LEFT JOIN
32
public.group_invoice_details gid
33
ON gid.group_invoice_header_id = gih.id
34
AND gid.is_deleted = false
35
AND gid.company_id = p_company_id
36
LEFT JOIN
37
public.invoice_headers ih
38
ON ih.id = gid.invoice_header_id
39
AND ih.is_deleted = false
40
AND ih.company_id = p_company_id
41
WHERE
42
gih.company_id = p_company_id
43
AND gih.execution_date::date BETWEEN v_start_date AND v_end_date
44
GROUP BY
45
gih.id,
46
gih.group_invoice_number,
47
gih.group_invoice_template_id,
48
gih.execution_date,
49
gih.error_message,
50
gih.success_count,
51
gih.total_count,
52
templates.invoice_description,
53
gih.group_invoice_batch_id
54
ORDER BY
55
gih.execution_date DESC
56
LIMIT p_limit; -- Apply the LIMIT when p_limit is provided
57
ELSE
58
-- If p_limit is NULL, return all results for the specified company and financial year
59
RETURN QUERY
60
SELECT
61
gih.id,
62
gih.group_invoice_number,
63
gih.group_invoice_template_id,
64
gih.execution_date,
65
gih.error_message,
66
gih.success_count,
67
gih.total_count,
68
templates.invoice_description,
69
gih.group_invoice_batch_id,
70
CAST(COALESCE(SUM(CASE WHEN ih.settled_amount >= ih.total_amount THEN 1 ELSE 0 END), 0) AS integer) AS paid_count,
71
COALESCE(SUM(ih.settled_amount), 0) AS total_paid_amount,
72
COALESCE(SUM(ih.total_amount), 0) AS original_total_amount,
73
COALESCE(SUM(ih.total_amount - ih.settled_amount), 0) AS remaining_amount
74
FROM
75
public.group_invoice_headers gih
76
INNER JOIN
77
public.group_invoice_templates templates
78
ON gih.group_invoice_template_id = templates.id
79
LEFT JOIN
80
public.group_invoice_details gid
81
ON gid.group_invoice_header_id = gih.id
82
AND gid.is_deleted = false
83
AND gid.company_id = p_company_id
84
LEFT JOIN
85
public.invoice_headers ih
86
ON ih.id = gid.invoice_header_id
87
AND ih.is_deleted = false
88
AND ih.company_id = p_company_id
89
WHERE
90
gih.company_id = p_company_id
91
AND gih.execution_date::date BETWEEN v_start_date AND v_end_date
92
GROUP BY
93
gih.id,
94
gih.group_invoice_number,
95
gih.group_invoice_template_id,
96
gih.execution_date,
97
gih.error_message,
98
gih.success_count,
99
gih.total_count,
100
templates.invoice_description,
101
gih.group_invoice_batch_id
102
ORDER BY
103
gih.execution_date DESC;
104
END IF;
105
END;
106
$function$
|
|||||
| Function | get_all_invoices_from_span | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_invoices_from_span(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
1
CREATE OR REPLACE FUNCTION public.get_all_invoices_from_span(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
2
RETURNS TABLE(id uuid, invoice_number character varying, type integer, invoice_date date, customer_id uuid, customer_name character varying, due_date date, total_amount numeric, settled_amount numeric, invoice_status_id integer, invoice_status character varying, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, created_by_name character varying, modified_by_name character varying, is_created_by_scheduler boolean, has_next_status_approval boolean, next_status_id integer, has_pending_payment_verification boolean)
2
RETURNS TABLE(id uuid, invoice_number character varying, type integer, invoice_date date, customer_id uuid, customer_name character varying, due_date date, total_amount numeric, setteled_amount numeric, invoice_status_id integer, invoice_status character varying, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, created_by_name character varying, modified_by_name character varying, is_created_by_scheduler boolean, has_next_status_approval boolean, next_status_id integer)
3
LANGUAGE plpgsql
3
LANGUAGE plpgsql
4
AS $function$
4
AS $function$
5
DECLARE
5
DECLARE
6
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
6
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
7
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
7
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
8
DRAFT_STATUS CONSTANT integer := 1;
8
DRAFT_STATUS CONSTANT integer := 1;
9
v_organization_id uuid;
10
BEGIN
9
BEGIN
11
RETURN QUERY
10
RETURN QUERY
12
WITH invoice_data AS (
11
WITH invoice_data AS (
13
SELECT
12
SELECT
14
ih.id,
13
ih.id,
15
CAST(ih.invoice_number AS varchar) AS invoice_number,
14
CAST(ih.invoice_number AS varchar) AS invoice_number,
16
ih.type,
15
ih.type,
17
ih.invoice_date::date,
16
ih.invoice_date::date,
18
ih.customer_id,
17
ih.customer_id,
19
c.name AS customer_name,
18
c.name AS customer_name,
20
ih.due_date::date,
19
ih.due_date::date,
21
ih.total_amount,
20
ih.total_amount,
22
ih.settled_amount,
21
ih.setteled_amount,
23
ih.invoice_status_id,
22
ih.invoice_status_id,
24
s.name AS invoice_status,
23
s.name AS invoice_status,
25
ih.currency_id,
24
ih.currency_id,
26
ih.created_on_utc,
25
ih.created_on_utc,
27
ih.modified_on_utc,
26
ih.modified_on_utc,
28
ih.created_by,
27
ih.created_by,
29
ih.modified_by,
28
ih.modified_by,
30
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
29
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
31
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
30
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
32
ih.is_created_by_scheduler,
31
ih.is_created_by_scheduler,
33
ih.credit_account_id
32
ih.credit_account_id
34
FROM invoice_headers ih
33
FROM invoice_headers ih
35
LEFT JOIN customers c ON c.id = ih.customer_id
34
LEFT JOIN customers c ON c.id = ih.customer_id
36
LEFT JOIN invoice_statuses s ON s.id = ih.invoice_status_id
35
LEFT JOIN invoice_statuses s ON s.id = ih.invoice_status_id
37
LEFT JOIN users cu ON cu.id = ih.created_by
36
LEFT JOIN users cu ON cu.id = ih.created_by
38
LEFT JOIN users mu ON mu.id = ih.modified_by
37
LEFT JOIN users mu ON mu.id = ih.modified_by
39
WHERE ih.company_id = p_company_id
38
WHERE ih.company_id = p_company_id
40
AND ih.is_deleted = false
39
AND ih.is_deleted = false
41
AND ih.invoice_date BETWEEN v_start_date AND v_end_date
40
AND ih.invoice_date BETWEEN v_start_date AND v_end_date
42
AND (p_type_id IS NULL OR p_type_id = 0 OR ih.type = p_type_id)
41
AND (p_type_id IS NULL OR p_type_id = 0 OR ih.type = p_type_id)
42
43
UNION ALL
43
UNION ALL
44
44
SELECT
45
SELECT
45
dih.id,
46
dih.id,
46
CAST(dih.invoice_number AS varchar) AS invoice_number,
47
CAST(dih.invoice_number AS varchar) AS invoice_number,
47
dih.type,
48
dih.type,
48
dih.invoice_date::date,
49
dih.invoice_date::date,
49
dih.customer_id,
50
dih.customer_id,
50
c.name AS customer_name,
51
c.name AS customer_name,
51
dih.due_date::date,
52
dih.due_date::date,
52
dih.total_amount,
53
dih.total_amount,
53
dih.settled_amount,
54
dih.setteled_amount,
54
dih.invoice_status_id,
55
dih.invoice_status_id,
55
s.name AS invoice_status,
56
s.name AS invoice_status,
56
dih.currency_id,
57
dih.currency_id,
57
dih.created_on_utc,
58
dih.created_on_utc,
58
dih.modified_on_utc,
59
dih.modified_on_utc,
59
dih.created_by,
60
dih.created_by,
60
dih.modified_by,
61
dih.modified_by,
61
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
62
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
62
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
63
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
63
dih.is_created_by_scheduler,
64
dih.is_created_by_scheduler,
64
dih.credit_account_id
65
dih.credit_account_id
65
FROM draft_invoice_headers dih
66
FROM draft_invoice_headers dih
66
LEFT JOIN customers c ON c.id = dih.customer_id
67
LEFT JOIN customers c ON c.id = dih.customer_id
67
LEFT JOIN invoice_statuses s ON s.id = dih.invoice_status_id
68
LEFT JOIN invoice_statuses s ON s.id = dih.invoice_status_id
68
LEFT JOIN users cu ON cu.id = dih.created_by
69
LEFT JOIN users cu ON cu.id = dih.created_by
69
LEFT JOIN users mu ON mu.id = dih.modified_by
70
LEFT JOIN users mu ON mu.id = dih.modified_by
70
WHERE dih.company_id = p_company_id
71
WHERE dih.company_id = p_company_id
71
AND dih.is_deleted = false
72
AND dih.is_deleted = false
72
AND dih.invoice_date BETWEEN v_start_date AND v_end_date
73
AND dih.invoice_date BETWEEN v_start_date AND v_end_date
73
AND (p_type_id IS NULL OR p_type_id = 0 OR dih.type = p_type_id)
74
AND (p_type_id IS NULL OR p_type_id = 0 OR dih.type = p_type_id)
74
)
75
)
75
SELECT
76
SELECT
76
id.id,
77
id.id,
77
id.invoice_number,
78
id.invoice_number,
78
id.type,
79
id.type,
79
id.invoice_date,
80
id.invoice_date,
80
id.customer_id,
81
id.customer_id,
81
id.customer_name,
82
id.customer_name,
82
id.due_date,
83
id.due_date,
83
id.total_amount,
84
id.total_amount,
84
id.settled_amount,
85
id.setteled_amount,
85
id.invoice_status_id,
86
id.invoice_status_id,
86
id.invoice_status,
87
id.invoice_status,
87
id.currency_id,
88
id.currency_id,
88
id.created_on_utc,
89
id.created_on_utc,
89
id.modified_on_utc,
90
id.modified_on_utc,
90
id.created_by,
91
id.created_by,
91
id.modified_by,
92
id.modified_by,
92
id.created_by_name,
93
id.created_by_name,
93
id.modified_by_name,
94
id.modified_by_name,
94
id.is_created_by_scheduler,
95
id.is_created_by_scheduler,
95
-- Determine if current user can approve
96
-- Determine if current user can approve
96
CASE
97
CASE
97
WHEN id.invoice_status_id = DRAFT_STATUS THEN true
98
WHEN id.invoice_status_id = DRAFT_STATUS THEN true
98
WHEN EXISTS (
99
WHEN EXISTS (
99
SELECT 1
100
SELECT 1
100
FROM public.invoice_approval_users_account a
101
FROM invoice_approval_users_account a
101
WHERE a.account_id = id.credit_account_id
102
WHERE a.account_id = id.credit_account_id
102
AND a.status_id = id.invoice_status_id
103
AND a.status_id = id.invoice_status_id
103
AND a.user_id = p_user_id
104
AND a.user_id = p_user_id
104
AND a.is_deleted = false
105
AND a.is_deleted = false
105
) THEN true
106
) THEN true
106
WHEN EXISTS (
107
WHEN EXISTS (
107
SELECT 1
108
SELECT 1
108
FROM public.invoice_approval_user_company c
109
FROM invoice_approval_user_company c
109
WHERE c.company_id = p_company_id
110
WHERE c.company_id = p_company_id
110
AND c.status_id = id.invoice_status_id
111
AND c.status_id = id.invoice_status_id
111
AND c.user_id = p_user_id
112
AND c.user_id = p_user_id
112
AND c.is_deleted = false
113
AND c.is_deleted = false
113
) THEN true
114
) THEN true
114
ELSE false
115
ELSE false
115
END AS has_next_status_approval,
116
END AS has_next_status_approval,
116
-- Determine next status
117
-- Determine next status
117
COALESCE(
118
COALESCE(
118
(SELECT iw.next_status
119
(SELECT iw.next_status
119
FROM invoice_workflow iw
120
FROM invoice_workflow iw
120
WHERE iw.company_id = p_company_id
121
WHERE iw.company_id = p_company_id
121
AND iw.status = id.invoice_status_id
122
AND iw.status = id.invoice_status_id
122
AND iw.is_deleted = false
123
AND iw.is_deleted = false
123
LIMIT 1),
124
LIMIT 1),
124
0
125
0
125
) AS next_status_id,
126
) AS next_status_id
126
-- Check if there's a pending payment verification
127
EXISTS (
128
SELECT 1
129
FROM public.invoice_payment_headers iph
130
JOIN public.invoice_payment_details ipd
131
ON ipd.invoice_payment_header_id = iph.id
132
WHERE ipd.invoice_header_id = id.id
133
AND iph.payment_status_id = 4 -- Payment Pending Verification status
134
AND iph.is_deleted = false
135
) AS has_pending_payment_verification
136
FROM invoice_data id;
127
FROM invoice_data id;
137
END;
128
END;
138
$function$
129
$function$
|
|||||
| Function | get_unit_dues_by_cust_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_unit_dues_by_cust_id(p_company_id uuid, p_customer_id uuid, p_fin_year_id integer)
2
RETURNS TABLE(id uuid, invoice_number text, note text, due_date timestamp without time zone, total_due numeric, total_paid numeric, original_amount numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1); -- Default: April 1st of the given financial year
7
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31); -- Default: March 31st of the next financial year
8
BEGIN
9
RETURN QUERY
10
SELECT
11
i.id AS id,
12
i.invoice_number,
13
i.note,
14
i.due_date,
15
(i.total_amount - i.settled_amount) AS total_due,
16
i.settled_amount AS total_paid,
17
i.total_amount AS original_amount
18
FROM invoice_headers i
19
INNER JOIN customers c
20
ON c.id = i.customer_id
21
AND c.company_id = p_company_id
22
WHERE
23
i.company_id = p_company_id
24
AND i.customer_id = p_customer_id
25
AND c.is_deleted = false
26
AND i.is_deleted = false
27
AND i.invoice_status_id IN (3, 4) -- Approved / Partially Paid
28
AND (i.total_amount - i.settled_amount) > 0
29
AND i.due_date >= v_start_date -- Filter by the financial year start date
30
AND i.due_date <= v_end_date -- Filter by the financial year end date
31
ORDER BY i.due_date;
32
END;
33
$function$
|
|||||
| Function | get_defaulter_list | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_defaulter_list(p_company_id uuid, p_as_of_date timestamp without time zone, p_limit integer DEFAULT NULL::integer)
2
RETURNS TABLE(id integer, customer_id uuid, customer_name character varying, due_date date, days_overdue integer, total_outstanding_per_customer numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT *
8
FROM (
9
SELECT
10
ROW_NUMBER() OVER (
11
ORDER BY
12
SUM(ih.total_amount - ih.settled_amount) DESC,
13
MAX(DATE_PART('day', p_as_of_date - ih.due_date)) DESC,
14
c."name" ASC
15
)::INT AS id,
16
17
c.id AS customer_id,
18
c."name" AS customer_name,
19
20
-- Oldest unpaid invoice due date
21
MIN(ih.due_date)::DATE AS due_date,
22
23
-- Max overdue days across invoices
24
MAX(
25
DATE_PART('day', p_as_of_date - ih.due_date)
26
)::INT AS days_overdue,
27
28
SUM(ih.total_amount - ih.settled_amount)
29
AS total_outstanding_per_customer
30
31
FROM public.invoice_headers ih
32
JOIN public.customers c
33
ON c.id = ih.customer_id
34
LEFT JOIN public.defaulter_configurations dc
35
ON dc.company_id = p_company_id
36
37
WHERE
38
ih.settled_amount < ih.total_amount
39
AND ih.due_date < p_as_of_date
40
AND ih.is_deleted = FALSE
41
AND c.is_deleted = FALSE
42
AND ih.company_id = p_company_id
43
AND dc.is_deleted = FALSE
44
AND DATE_PART('day', p_as_of_date - ih.due_date)
45
> dc.defaulter_period_days
46
47
GROUP BY
48
c.id,
49
c."name"
50
) ranked
51
ORDER BY
52
ranked.id
53
LIMIT
54
COALESCE(p_limit, 2147483647);
55
END;
56
$function$
|
|||||
| Function | get_unit_dues_summary_by_cust_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_unit_dues_summary_by_cust_id(p_company_id uuid, p_customer_id uuid, p_fin_year_id integer)
2
RETURNS TABLE(last_collection_target_value numeric, last_collection_collected_value numeric, last_collection_units_targeted integer, last_collection_paid_units integer, total_outstanding_due numeric, total_units_not_paid integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
7
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
8
9
v_latest_cycle_date date;
10
BEGIN
11
/*
12
* 1) Find latest invoice cycle date within the FY
13
* Using invoice_date if present, otherwise created_on_utc
14
*/
15
SELECT MAX(COALESCE(i.invoice_date::date, i.created_on_utc::date))
16
INTO v_latest_cycle_date
17
FROM invoice_headers i
18
INNER JOIN customers c ON c.id = i.customer_id
19
WHERE i.company_id = p_company_id
20
AND i.customer_id = p_customer_id
21
AND c.company_id = p_company_id
22
AND c.is_deleted = false
23
AND i.is_deleted = false
24
AND i.invoice_status_id IN (3, 4) -- Approved / Partially Paid
25
AND i.due_date::date BETWEEN v_start_date AND v_end_date;
26
27
-- No invoices in FY → return zero summary
28
IF v_latest_cycle_date IS NULL THEN
29
RETURN QUERY
30
SELECT 0, 0, 0, 0, 0, 0;
31
RETURN;
32
END IF;
33
34
/*
35
* 2) Build FY invoice set
36
*/
37
RETURN QUERY
38
WITH fy_invoices AS (
39
SELECT
40
i.customer_id,
41
i.total_amount,
42
i.settled_amount,
43
(i.total_amount - i.settled_amount) AS due_amount,
44
COALESCE(i.invoice_date::date, i.created_on_utc::date) AS cycle_date
45
FROM invoice_headers i
46
INNER JOIN customers c ON c.id = i.customer_id
47
WHERE i.company_id = p_company_id
48
AND i.customer_id = p_customer_id
49
AND c.company_id = p_company_id
50
AND c.is_deleted = false
51
AND i.is_deleted = false
52
AND i.invoice_status_id IN (3, 4)
53
AND i.due_date::date BETWEEN v_start_date AND v_end_date
54
),
55
last_cycle AS (
56
SELECT *
57
FROM fy_invoices
58
WHERE cycle_date = v_latest_cycle_date
59
),
60
last_cycle_rollup AS (
61
SELECT
62
COALESCE(SUM(total_amount), 0) AS target_value,
63
COALESCE(SUM(settled_amount), 0) AS collected_value,
64
COUNT(DISTINCT customer_id)::int AS units_targeted,
65
COUNT(
66
DISTINCT CASE
67
WHEN due_amount <= 0 THEN customer_id
68
END
69
)::int AS paid_units
70
FROM last_cycle
71
),
72
overall_rollup AS (
73
SELECT
74
COALESCE(SUM(due_amount), 0) AS total_due,
75
COUNT(
76
DISTINCT CASE
77
WHEN due_amount > 0 THEN customer_id
78
END
79
)::int AS units_not_paid
80
FROM fy_invoices
81
)
82
SELECT
83
l.target_value,
84
l.collected_value,
85
l.units_targeted,
86
l.paid_units,
87
o.total_due,
88
o.units_not_paid
89
FROM last_cycle_rollup l
90
CROSS JOIN overall_rollup o;
91
END;
92
$function$
|
|||||
| Function | get_unit_dues_summary_by_company | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_unit_dues_summary_by_company(p_company_id uuid, p_fin_year_id integer)
2
RETURNS TABLE(last_collection_target_value numeric, last_collection_collected_value numeric, last_collection_units_targeted integer, last_collection_paid_units integer, total_outstanding_due numeric, total_units_not_paid integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
7
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
8
9
v_latest_cycle_date date;
10
BEGIN
11
/*
12
* 1) Find latest invoice cycle date in FY (company-wide)
13
*/
14
SELECT MAX(COALESCE(i.invoice_date::date, i.created_on_utc::date))
15
INTO v_latest_cycle_date
16
FROM invoice_headers i
17
INNER JOIN customers c ON c.id = i.customer_id
18
WHERE i.company_id = p_company_id
19
AND c.company_id = p_company_id
20
AND c.is_deleted = false
21
AND i.is_deleted = false
22
AND i.invoice_status_id IN (3, 4) -- Approved / Partially Paid
23
AND i.due_date::date BETWEEN v_start_date AND v_end_date;
24
25
-- No invoices in FY → return zero summary
26
IF v_latest_cycle_date IS NULL THEN
27
RETURN QUERY
28
SELECT 0, 0, 0, 0, 0, 0;
29
RETURN;
30
END IF;
31
32
/*
33
* 2) Build FY invoice dataset
34
*/
35
RETURN QUERY
36
WITH fy_invoices AS (
37
SELECT
38
i.customer_id,
39
i.total_amount,
40
i.settled_amount,
41
(i.total_amount - i.settled_amount) AS due_amount,
42
COALESCE(i.invoice_date::date, i.created_on_utc::date) AS cycle_date
43
FROM invoice_headers i
44
INNER JOIN customers c ON c.id = i.customer_id
45
WHERE i.company_id = p_company_id
46
AND c.company_id = p_company_id
47
AND c.is_deleted = false
48
AND i.is_deleted = false
49
AND i.invoice_status_id IN (3, 4)
50
AND i.due_date::date BETWEEN v_start_date AND v_end_date
51
),
52
last_cycle AS (
53
SELECT *
54
FROM fy_invoices
55
WHERE cycle_date = v_latest_cycle_date
56
),
57
last_cycle_rollup AS (
58
SELECT
59
COALESCE(SUM(total_amount), 0) AS target_value,
60
COALESCE(SUM(settled_amount), 0) AS collected_value,
61
COUNT(DISTINCT customer_id)::int AS units_targeted,
62
COUNT(
63
DISTINCT CASE
64
WHEN due_amount <= 0 THEN customer_id
65
END
66
)::int AS paid_units
67
FROM last_cycle
68
),
69
overall_rollup AS (
70
SELECT
71
COALESCE(SUM(due_amount), 0) AS total_due,
72
COUNT(
73
DISTINCT CASE
74
WHEN due_amount > 0 THEN customer_id
75
END
76
)::int AS units_not_paid
77
FROM fy_invoices
78
)
79
SELECT
80
l.target_value,
81
l.collected_value,
82
l.units_targeted,
83
l.paid_units,
84
o.total_due,
85
o.units_not_paid
86
FROM last_cycle_rollup l
87
CROSS JOIN overall_rollup o;
88
END;
89
$function$
|
|||||
| Function | get_discussion_messages | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_discussion_messages(p_organization_id uuid, p_cycle_id uuid)
2
RETURNS jsonb
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_result jsonb;
7
BEGIN
8
-- Verify the cycle exists and belongs to the organization
9
IF NOT EXISTS (
10
SELECT 1
11
FROM public.delinquency_cycles dc
12
WHERE dc.id = p_cycle_id
13
AND dc.organization_id = p_organization_id
14
AND dc.is_deleted = false
15
) THEN
16
RETURN jsonb_build_object('messages', jsonb_build_array());
17
END IF;
18
19
-- Fetch all messages for the cycle
20
SELECT jsonb_build_object(
21
'messages',
22
COALESCE(
23
jsonb_agg(row_to_json(msg)),
24
jsonb_build_array()
25
)
26
)
27
INTO v_result
28
FROM (
29
SELECT
30
ddm.id,
31
ddm.cycle_id,
32
ddm.sender_id,
33
concat(u.first_name, ' ', u.last_name) AS sender_name,
34
ddm.sender_type,
35
ddm.message_text,
36
ddm.sent_on_utc
37
FROM public.delinquency_discussion_messages ddm
38
LEFT JOIN public.users u
39
ON u.id = ddm.sender_id
40
WHERE ddm.cycle_id = p_cycle_id
41
AND ddm.organization_id = p_organization_id
42
AND ddm.is_deleted = false
43
ORDER BY ddm.sent_on_utc ASC
44
) msg;
45
46
RETURN v_result;
47
END;
48
$function$
|
|||||
| Function | dblink_connect | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_connect(text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_connect$function$
|
|||||
| Function | dblink_connect | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_connect(text, text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_connect$function$
|
|||||
| Function | dblink_connect_u | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_connect_u(text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT SECURITY DEFINER
5
AS '$libdir/dblink', $function$dblink_connect$function$
|
|||||
| Function | dblink_connect_u | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_connect_u(text, text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT SECURITY DEFINER
5
AS '$libdir/dblink', $function$dblink_connect$function$
|
|||||
| Function | dblink_disconnect | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_disconnect()
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_disconnect$function$
|
|||||
| Function | dblink_disconnect | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_disconnect(text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_disconnect$function$
|
|||||
| Function | dblink_open | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_open(text, text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_open$function$
|
|||||
| Function | dblink_open | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_open(text, text, boolean)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_open$function$
|
|||||
| Function | dblink_open | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_open(text, text, text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_open$function$
|
|||||
| Function | dblink_open | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_open(text, text, text, boolean)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_open$function$
|
|||||
| Function | dblink_fetch | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_fetch(text, integer)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_fetch$function$
|
|||||
| Function | dblink_fetch | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_fetch(text, integer, boolean)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_fetch$function$
|
|||||
| Function | dblink_fetch | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_fetch(text, text, integer)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_fetch$function$
|
|||||
| Function | dblink_fetch | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_fetch(text, text, integer, boolean)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_fetch$function$
|
|||||
| Function | dblink_close | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_close(text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_close$function$
|
|||||
| Function | dblink_close | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_close(text, boolean)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_close$function$
|
|||||
| Function | dblink_close | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_close(text, text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_close$function$
|
|||||
| Function | dblink_close | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_close(text, text, boolean)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_close$function$
|
|||||
| Function | dblink | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink(text, text)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_record$function$
|
|||||
| Function | dblink | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink(text, text, boolean)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_record$function$
|
|||||
| Function | dblink | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink(text)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_record$function$
|
|||||
| Function | dblink | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink(text, boolean)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_record$function$
|
|||||
| Function | dblink_exec | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_exec(text, text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_exec$function$
|
|||||
| Function | dblink_exec | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_exec(text, text, boolean)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_exec$function$
|
|||||
| Function | dblink_exec | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_exec(text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_exec$function$
|
|||||
| Function | open_or_get_delinquency_cycle | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.open_or_get_delinquency_cycle(p_organization_id uuid, p_customer_id uuid, p_start_due_date date, p_created_by uuid)
2
RETURNS uuid
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_cycle_id uuid;
7
CYCLE_STATUS_OPEN CONSTANT int := 1;
8
BEGIN
9
-- Try to get existing open cycle
10
SELECT id
11
INTO v_cycle_id
12
FROM delinquency_cycles
13
WHERE organization_id = p_organization_id
14
AND customer_id = p_customer_id
15
AND ended_on_utc IS NULL
16
AND is_deleted = false
17
LIMIT 1;
18
19
IF v_cycle_id IS NOT NULL THEN
20
RETURN v_cycle_id;
21
END IF;
22
23
-- if not then Create new cycle
24
INSERT INTO delinquency_cycles (
25
id,
26
organization_id,
27
customer_id,
28
start_due_date,
29
started_on_utc,
30
status_id,
31
created_on_utc,
32
created_by,
33
is_deleted
34
)
35
VALUES (
36
gen_random_uuid(),
37
p_organization_id,
38
p_customer_id,
39
p_start_due_date,
40
CURRENT_TIMESTAMP,
41
CYCLE_STATUS_OPEN,
42
CURRENT_TIMESTAMP,
43
p_created_by,
44
false
45
)
46
RETURNING id INTO v_cycle_id;
47
48
RETURN v_cycle_id;
49
END;
50
$function$
|
|||||
| Function | dblink_exec | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_exec(text, boolean)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_exec$function$
|
|||||
| Function | dblink_get_pkey | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_get_pkey(text)
2
RETURNS SETOF dblink_pkey_results
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_get_pkey$function$
|
|||||
| Function | dblink_build_sql_insert | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_build_sql_insert(text, int2vector, integer, text[], text[])
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_build_sql_insert$function$
|
|||||
| Function | dblink_build_sql_delete | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_build_sql_delete(text, int2vector, integer, text[])
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_build_sql_delete$function$
|
|||||
| Function | dblink_build_sql_update | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_build_sql_update(text, int2vector, integer, text[], text[])
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_build_sql_update$function$
|
|||||
| Function | dblink_current_query | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_current_query()
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED
5
AS '$libdir/dblink', $function$dblink_current_query$function$
|
|||||
| Function | dblink_send_query | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_send_query(text, text)
2
RETURNS integer
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_send_query$function$
|
|||||
| Function | dblink_is_busy | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_is_busy(text)
2
RETURNS integer
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_is_busy$function$
|
|||||
| Function | dblink_get_result | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_get_result(text)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_get_result$function$
|
|||||
| Function | dblink_get_result | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_get_result(text, boolean)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_get_result$function$
|
|||||
| Function | dblink_get_connections | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_get_connections()
2
RETURNS text[]
3
LANGUAGE c
4
PARALLEL RESTRICTED
5
AS '$libdir/dblink', $function$dblink_get_connections$function$
|
|||||
| Function | dblink_cancel_query | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_cancel_query(text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_cancel_query$function$
|
|||||
| Function | dblink_error_message | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_error_message(text)
2
RETURNS text
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_error_message$function$
|
|||||
| Function | dblink_get_notify | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_get_notify(OUT notify_name text, OUT be_pid integer, OUT extra text)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_get_notify$function$
|
|||||
| Function | dblink_get_notify | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_get_notify(conname text, OUT notify_name text, OUT be_pid integer, OUT extra text)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/dblink', $function$dblink_get_notify$function$
|
|||||
| Function | dblink_fdw_validator | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.dblink_fdw_validator(options text[], catalog oid)
2
RETURNS void
3
LANGUAGE c
4
PARALLEL SAFE STRICT
5
AS '$libdir/dblink', $function$dblink_fdw_validator$function$
|
|||||
| Function | get_latest_raised_group_invoice_summary | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_latest_raised_group_invoice_summary(p_company_id uuid DEFAULT NULL::uuid)
2
RETURNS TABLE(id integer, group_invoice_id uuid, group_invoice_number text, total_targeted_revenue numeric, collected_revenue numeric, total_units_count integer, collected_units_count integer)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
WITH latest_raised_group_invoice AS (
7
SELECT
8
gih.id,
9
gih.group_invoice_number
10
FROM public.group_invoice_headers gih
11
WHERE gih.is_deleted = false
12
AND (
13
p_company_id IS NULL
14
OR gih.company_id = p_company_id
15
)
16
AND EXISTS (
17
SELECT 1
18
FROM public.group_invoice_details gid
19
WHERE gid.group_invoice_header_id = gih.id
20
AND gid.is_deleted = false
21
)
22
ORDER BY gih.created_on_utc DESC NULLS LAST
23
LIMIT 1
24
)
25
SELECT
26
1 AS id,
27
lgi.id AS group_invoice_id,
28
lgi.group_invoice_number,
29
30
SUM(ih.total_amount) AS total_targeted_revenue,
31
SUM(COALESCE(ih.settled_amount, 0)) AS collected_revenue,
32
33
COUNT(DISTINCT gid.unit_id) AS total_units_count,
34
35
COUNT(
36
DISTINCT CASE
37
WHEN COALESCE(ih.settled_amount, 0) > 0
38
THEN gid.unit_id
39
END
40
) AS collected_units_count
41
42
FROM latest_raised_group_invoice lgi
43
JOIN public.group_invoice_details gid
44
ON gid.group_invoice_header_id = lgi.id
45
AND gid.is_deleted = false
46
JOIN public.invoice_headers ih
47
ON ih.id = gid.invoice_header_id
48
AND ih.is_deleted = false
49
GROUP BY
50
lgi.id,
51
lgi.group_invoice_number;
52
$function$
|
|||||
| Function | get_aging_bucket | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_aging_bucket(p_days integer)
2
RETURNS text
3
LANGUAGE sql
4
IMMUTABLE
5
AS $function$
6
SELECT CASE
7
WHEN p_days <= 30 THEN '0-30'
8
WHEN p_days <= 60 THEN '31-60'
9
WHEN p_days <= 90 THEN '61-90'
10
ELSE '90+'
11
END;
12
$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_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_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 | run_defaulter_fdw | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.run_defaulter_fdw()
2
RETURNS TABLE(result text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
PERFORM public.evaluate_delinquency_for_org('b2756bc9-be6a-42bb-a78a-178ea22b46eb');
7
RETURN QUERY SELECT 'SUCCESS';
8
END;
9
$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
'00000000-0000-0000-0000-000000000000'::uuid
19
);
20
END;
21
$function$
|
|||||
| Function | get_invoice_payment_timeline_by_customerid | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_invoice_payment_timeline_by_customerid(p_customer_id uuid, p_company_id uuid DEFAULT NULL::uuid)
2
RETURNS jsonb
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
result jsonb;
7
BEGIN
8
9
SELECT jsonb_agg(
10
jsonb_build_object(
11
'section', section,
12
'dates', dates
13
)
14
)
15
INTO result
16
FROM (
17
SELECT
18
section,
19
jsonb_agg(date_block ORDER BY date_block->>'date') AS dates
20
FROM (
21
SELECT
22
section,
23
jsonb_build_object(
24
'date', event_date,
25
'dateLabel', to_char(event_date, 'Mon DD'),
26
'items',
27
jsonb_agg(
28
jsonb_build_object(
29
'eventType', event_type,
30
'invoiceNumber', invoice_number,
31
'paymentNumber', payment_number,
32
'reference', reference,
33
'title', title,
34
'timeLabel', time_label,
35
'status', status,
36
'amount', amount,
37
'daysToDue', days_to_due,
38
'dueLabel', due_label,
39
'invoiceId', invoice_id,
40
'paymentId', payment_id
41
)
42
ORDER BY event_time
43
)
44
) AS date_block
45
FROM (
46
/* =====================================================
47
INVOICE ISSUED (to resident)
48
====================================================== */
49
SELECT
50
ih.id AS invoice_id,
51
NULL::uuid AS payment_id,
52
ih.invoice_number AS invoice_number,
53
NULL::text AS payment_number,
54
ih.created_on_utc AS event_time,
55
ih.created_on_utc::date AS event_date,
56
'Invoice' AS event_type,
57
ih.invoice_number AS reference,
58
'Invoice Issued' AS title,
59
to_char(ih.created_on_utc, 'HH12:MI AM') AS time_label,
60
ih.total_amount AS amount,
61
62
CASE
63
WHEN ih.settled_amount >= ih.total_amount THEN 'PAID'
64
WHEN ih.settled_amount > 0 THEN 'PARTIAL'
65
WHEN ih.due_date < now() THEN 'OVERDUE'
66
ELSE 'DUE'
67
END AS status,
68
69
CASE
70
WHEN ih.settled_amount >= ih.total_amount THEN NULL
71
ELSE (ih.due_date::date - current_date)
72
END AS days_to_due,
73
74
CASE
75
WHEN ih.settled_amount >= ih.total_amount THEN NULL
76
WHEN ih.due_date::date - current_date < 0
77
THEN concat('Overdue by ', abs(ih.due_date::date - current_date), ' days')
78
ELSE concat('Due in ', ih.due_date::date - current_date, ' days')
79
END AS due_label,
80
81
CASE
82
WHEN ih.settled_amount >= ih.total_amount THEN 'Settled'
83
ELSE 'Open'
84
END AS section
85
86
FROM invoice_headers ih
87
WHERE ih.customer_id = p_customer_id
88
AND ih.is_deleted = false
89
AND (p_company_id IS NULL OR ih.company_id = p_company_id)
90
91
UNION ALL
92
93
/* =====================================================
94
PAYMENT MADE (by resident)
95
====================================================== */
96
SELECT
97
ipd.invoice_header_id AS invoice_id,
98
iph.id AS payment_id,
99
ih.invoice_number AS invoice_number,
100
iph.payment_number AS payment_number,
101
iph.received_date AS event_time,
102
iph.received_date::date AS event_date,
103
'Payment' AS event_type,
104
iph.payment_number AS reference,
105
106
CASE
107
WHEN ih.settled_amount >= ih.total_amount
108
THEN 'Invoice Paid'
109
ELSE 'Partial Payment Made'
110
END AS title,
111
112
to_char(iph.received_date, 'HH12:MI AM') AS time_label,
113
ipd.received_amount AS amount,
114
115
'PAYMENT' AS status,
116
NULL::int AS days_to_due,
117
NULL::text AS due_label,
118
119
CASE
120
WHEN ih.settled_amount >= ih.total_amount THEN 'Settled'
121
ELSE 'Open'
122
END AS section
123
124
FROM invoice_payment_details ipd
125
JOIN invoice_payment_headers iph
126
ON iph.id = ipd.invoice_payment_header_id
127
AND iph.is_deleted = false
128
JOIN invoice_headers ih
129
ON ih.id = ipd.invoice_header_id
130
AND ih.is_deleted = false
131
WHERE iph.customer_id = p_customer_id
132
AND ipd.is_deleted = false
133
AND (p_company_id IS NULL OR iph.company_id = p_company_id)
134
135
) timeline_events
136
GROUP BY section, event_date
137
) grouped_dates
138
GROUP BY section
139
) final_sections;
140
141
RETURN COALESCE(result, '[]'::jsonb);
142
END;
143
$function$
|
|||||
| Function | get_invoice_payment_timeline_by_customerid | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_invoice_payment_timeline_by_customerid(p_customer_id uuid, p_company_id uuid, p_financial_year_start integer)
2
RETURNS jsonb
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
result jsonb;
7
v_financial_year_start date;
8
v_financial_year_end date;
9
BEGIN
10
/* ---------------------------------------------------------
11
Financial Year Window (India: Apr 1 → Mar 31)
12
--------------------------------------------------------- */
13
v_financial_year_start := TO_DATE(p_financial_year_start || '-04-01', 'YYYY-MM-DD');
14
v_financial_year_end := TO_DATE((p_financial_year_start + 1) || '-03-31', 'YYYY-MM-DD');
15
16
/* ---------------------------------------------------------
17
Timeline JSON
18
--------------------------------------------------------- */
19
SELECT jsonb_agg(
20
jsonb_build_object(
21
'section', section,
22
'dates', dates
23
)
24
)
25
INTO result
26
FROM (
27
SELECT
28
section,
29
jsonb_agg(date_block ORDER BY date_block->>'date') AS dates
30
FROM (
31
SELECT
32
section,
33
jsonb_build_object(
34
'date', event_date,
35
'dateLabel', to_char(event_date, 'Mon DD'),
36
'items',
37
jsonb_agg(
38
jsonb_build_object(
39
'eventType', event_type,
40
'invoiceNumber', invoice_number,
41
'paymentNumber', payment_number,
42
'reference', reference,
43
'title', title,
44
'timeLabel', time_label,
45
'status', status,
46
'amount', amount,
47
'daysToDue', days_to_due,
48
'dueLabel', due_label,
49
'invoiceId', invoice_id,
50
'paymentId', payment_id
51
)
52
ORDER BY event_time
53
)
54
) AS date_block
55
FROM (
56
/* =====================================================
57
INVOICE ISSUED (Apartment → Resident)
58
====================================================== */
59
SELECT
60
ih.id AS invoice_id,
61
NULL::uuid AS payment_id,
62
ih.invoice_number,
63
NULL::text AS payment_number,
64
ih.created_on_utc AS event_time,
65
ih.due_date::date AS event_date,
66
'Invoice' AS event_type,
67
ih.invoice_number AS reference,
68
'Invoice Issued' AS title,
69
to_char(ih.created_on_utc, 'HH12:MI AM') AS time_label,
70
ih.total_amount AS amount,
71
72
CASE
73
WHEN ih.settled_amount >= ih.total_amount THEN 'PAID'
74
WHEN ih.settled_amount > 0 THEN 'PARTIAL'
75
WHEN ih.due_date < current_date THEN 'OVERDUE'
76
ELSE 'DUE'
77
END AS status,
78
79
CASE
80
WHEN ih.settled_amount >= ih.total_amount THEN NULL
81
ELSE (ih.due_date::date - current_date)
82
END AS days_to_due,
83
84
CASE
85
WHEN ih.settled_amount >= ih.total_amount THEN NULL
86
WHEN ih.due_date::date < current_date
87
THEN 'Overdue by ' || abs(ih.due_date::date - current_date) || ' days'
88
ELSE 'Due in ' || (ih.due_date::date - current_date) || ' days'
89
END AS due_label,
90
91
CASE
92
WHEN ih.settled_amount >= ih.total_amount THEN 'Settled'
93
ELSE 'Open'
94
END AS section
95
96
FROM invoice_headers ih
97
WHERE ih.customer_id = p_customer_id
98
AND ih.company_id = p_company_id
99
AND ih.is_deleted = false
100
AND ih.due_date::date BETWEEN v_financial_year_start AND v_financial_year_end
101
102
UNION ALL
103
104
/* =====================================================
105
PAYMENT MADE (Resident → Apartment)
106
====================================================== */
107
SELECT
108
ipd.invoice_header_id AS invoice_id,
109
iph.id AS payment_id,
110
ih.invoice_number,
111
iph.payment_number,
112
iph.received_date AS event_time,
113
iph.received_date::date AS event_date,
114
'Payment' AS event_type,
115
iph.payment_number AS reference,
116
117
CASE
118
WHEN ih.settled_amount >= ih.total_amount
119
THEN 'Invoice Paid'
120
ELSE 'Partial Payment Made'
121
END AS title,
122
123
to_char(iph.received_date, 'HH12:MI AM') AS time_label,
124
ipd.received_amount AS amount,
125
126
'PAYMENT' AS status,
127
NULL::int AS days_to_due,
128
NULL::text AS due_label,
129
130
CASE
131
WHEN ih.settled_amount >= ih.total_amount THEN 'Settled'
132
ELSE 'Open'
133
END AS section
134
135
FROM invoice_payment_details ipd
136
JOIN invoice_payment_headers iph
137
ON iph.id = ipd.invoice_payment_header_id
138
AND iph.is_deleted = false
139
JOIN invoice_headers ih
140
ON ih.id = ipd.invoice_header_id
141
AND ih.is_deleted = false
142
WHERE iph.customer_id = p_customer_id
143
AND iph.company_id = p_company_id
144
AND ipd.is_deleted = false
145
AND iph.received_date::date BETWEEN v_financial_year_start AND v_financial_year_end
146
147
) timeline_events
148
GROUP BY section, event_date
149
) grouped_dates
150
GROUP BY section
151
) final_sections;
152
153
RETURN COALESCE(result, '[]'::jsonb);
154
END;
155
$function$
|
|||||
| Function | evaluate_delinquency_for_org | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.evaluate_delinquency_for_org(p_organization_id uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_grace_days integer := 0;
7
v_company_ids uuid[];
8
v_system_user_id uuid;
9
BEGIN
10
/* ====================================================
11
1) Load latest collection policy
12
==================================================== */
13
SELECT COALESCE(cp.grace_days, 0)
14
INTO v_grace_days
15
FROM public.collection_policies cp
16
WHERE cp.organization_id = p_organization_id
17
AND cp.is_deleted = false
18
ORDER BY cp.created_on_utc DESC
19
LIMIT 1;
20
21
/* ====================================================
22
2) Fetch companies for organization
23
==================================================== */
24
SELECT COALESCE(array_agg(c.id), ARRAY[]::uuid[])
25
INTO v_company_ids
26
FROM public.companies c
27
WHERE c.organization_id = p_organization_id
28
AND c.is_deleted = false;
29
30
IF array_length(v_company_ids, 1) IS NULL THEN
31
RETURN;
32
END IF;
33
34
/* ====================================================
35
3) Resolve system user
36
==================================================== */
37
v_system_user_id := public.get_system_user_id();
38
39
/* ====================================================
40
4) Cleanup temp tables (safe for same session)
41
==================================================== */
42
DROP TABLE IF EXISTS tmp_overdue_invoices;
43
DROP TABLE IF EXISTS tmp_calc;
44
DROP TABLE IF EXISTS tmp_open_cycles;
45
DROP TABLE IF EXISTS tmp_cycles_created;
46
DROP TABLE IF EXISTS tmp_cycle_map;
47
DROP TABLE IF EXISTS tmp_snapshot_inserted;
48
DROP TABLE IF EXISTS tmp_active_snapshots;
49
50
/* ====================================================
51
5) MATERIALIZE overdue invoices
52
==================================================== */
53
CREATE TEMP TABLE tmp_overdue_invoices ON COMMIT DROP AS
54
SELECT
55
ih.company_id,
56
ih.customer_id,
57
ih.id AS invoice_id,
58
ih.invoice_date,
59
ih.due_date::date AS due_date,
60
ih.total_amount,
61
COALESCE(ih.settled_amount, 0) AS settled_amount,
62
(ih.total_amount - COALESCE(ih.settled_amount, 0)) AS outstanding_amount
63
FROM public.invoice_headers ih
64
WHERE ih.company_id = ANY (v_company_ids)
65
AND ih.is_deleted = false
66
AND (ih.total_amount - COALESCE(ih.settled_amount, 0)) > 0
67
AND (ih.due_date::date + v_grace_days) < CURRENT_DATE;
68
69
/* ====================================================
70
6) Aggregate delinquency per customer
71
==================================================== */
72
CREATE TEMP TABLE tmp_calc ON COMMIT DROP AS
73
SELECT
74
customer_id,
75
MIN(due_date) AS oldest_due_date,
76
SUM(outstanding_amount) AS overdue_amount,
77
(CURRENT_DATE - MIN(due_date + v_grace_days))::int AS days_past_due,
78
public.get_aging_bucket(
79
(CURRENT_DATE - MIN(due_date + v_grace_days))::int
80
) AS aging_bucket
81
FROM tmp_overdue_invoices
82
GROUP BY customer_id;
83
84
/* ====================================================
85
7) Existing open cycles
86
==================================================== */
87
CREATE TEMP TABLE tmp_open_cycles ON COMMIT DROP AS
88
SELECT
89
dc.customer_id,
90
dc.id AS cycle_id
91
FROM public.delinquency_cycles dc
92
WHERE dc.organization_id = p_organization_id
93
AND dc.ended_on_utc IS NULL
94
AND dc.is_deleted = false;
95
96
/* ====================================================
97
8) Create missing cycles (CTE bridge)
98
==================================================== */
99
CREATE TEMP TABLE tmp_cycles_created ON COMMIT DROP AS
100
WITH ins AS (
101
INSERT INTO public.delinquency_cycles (
102
id, organization_id, customer_id,
103
start_due_date, started_on_utc,
104
ended_on_utc,
105
status_id, resolution_state_id,
106
created_by, created_on_utc, is_deleted
107
)
108
SELECT
109
gen_random_uuid(),
110
p_organization_id,
111
c.customer_id,
112
c.oldest_due_date,
113
now(),
114
NULL,
115
1, -- status id
116
1, -- resolution status id 1
117
v_system_user_id,
118
now(),
119
false
120
FROM tmp_calc c
121
LEFT JOIN tmp_open_cycles oc ON oc.customer_id = c.customer_id
122
WHERE oc.cycle_id IS NULL
123
RETURNING customer_id, id AS cycle_id
124
)
125
SELECT * FROM ins;
126
127
/* ====================================================
128
9) Cycle map (existing + created)
129
==================================================== */
130
CREATE TEMP TABLE tmp_cycle_map ON COMMIT DROP AS
131
SELECT
132
c.customer_id,
133
COALESCE(oc.cycle_id, cc.cycle_id) AS cycle_id,
134
c.oldest_due_date,
135
c.overdue_amount,
136
c.days_past_due,
137
c.aging_bucket
138
FROM tmp_calc c
139
LEFT JOIN tmp_open_cycles oc ON oc.customer_id = c.customer_id
140
LEFT JOIN tmp_cycles_created cc ON cc.customer_id = c.customer_id;
141
142
/* ====================================================
143
10) Insert snapshot if missing (CTE bridge)
144
==================================================== */
145
CREATE TEMP TABLE tmp_snapshot_inserted ON COMMIT DROP AS
146
WITH ins AS (
147
INSERT INTO public.delinquency_snapshots (
148
id, organization_id, customer_id, cycle_id,
149
overdue_amount, oldest_due_date,
150
days_past_due, aging_bucket,
151
last_evaluated_on,
152
created_by, created_on_utc,
153
is_deleted
154
)
155
SELECT
156
gen_random_uuid(),
157
p_organization_id,
158
cm.customer_id,
159
cm.cycle_id,
160
cm.overdue_amount,
161
cm.oldest_due_date,
162
cm.days_past_due,
163
cm.aging_bucket,
164
now(),
165
v_system_user_id,
166
now(),
167
false
168
FROM tmp_cycle_map cm
169
WHERE NOT EXISTS (
170
SELECT 1
171
FROM public.delinquency_snapshots ds
172
WHERE ds.cycle_id = cm.cycle_id
173
AND ds.is_deleted = false
174
)
175
RETURNING cycle_id, id AS snapshot_id
176
)
177
SELECT * FROM ins;
178
179
/* ====================================================
180
11) Resolve active snapshot per cycle
181
==================================================== */
182
CREATE TEMP TABLE tmp_active_snapshots ON COMMIT DROP AS
183
SELECT
184
cm.customer_id,
185
cm.cycle_id,
186
COALESCE(
187
tsi.snapshot_id,
188
(
189
SELECT ds.id
190
FROM public.delinquency_snapshots ds
191
WHERE ds.cycle_id = cm.cycle_id
192
AND ds.is_deleted = false
193
ORDER BY ds.created_on_utc DESC
194
LIMIT 1
195
)
196
) AS snapshot_id
197
FROM tmp_cycle_map cm
198
LEFT JOIN tmp_snapshot_inserted tsi ON tsi.cycle_id = cm.cycle_id;
199
200
/* ====================================================
201
12) Update active snapshots
202
==================================================== */
203
UPDATE public.delinquency_snapshots ds
204
SET overdue_amount = c.overdue_amount,
205
oldest_due_date = c.oldest_due_date,
206
days_past_due = c.days_past_due,
207
aging_bucket = c.aging_bucket,
208
last_evaluated_on = now(),
209
modified_on_utc = now(),
210
modified_by = v_system_user_id
211
FROM tmp_cycle_map c
212
JOIN tmp_active_snapshots a ON a.cycle_id = c.cycle_id
213
WHERE ds.id = a.snapshot_id
214
AND ds.is_deleted = false;
215
216
/* ====================================================
217
13) Refresh snapshot invoices (UPSERT – idempotent)
218
==================================================== */
219
220
-- Soft delete old rows
221
UPDATE public.delinquency_snapshot_invoices dsi
222
SET is_deleted = true,
223
deleted_on_utc = now(),
224
modified_on_utc = now(),
225
modified_by = v_system_user_id
226
WHERE dsi.organization_id = p_organization_id
227
AND dsi.is_deleted = false
228
AND EXISTS (
229
SELECT 1
230
FROM tmp_active_snapshots a
231
WHERE a.snapshot_id = dsi.snapshot_id
232
);
233
234
-- Insert / revive latest overdue invoices
235
INSERT INTO public.delinquency_snapshot_invoices (
236
id,
237
organization_id,
238
company_id,
239
snapshot_id,
240
invoice_id,
241
invoice_date,
242
due_date,
243
total_amount,
244
settled_amount,
245
outstanding_amount,
246
created_on_utc,
247
created_by,
248
modified_on_utc,
249
modified_by,
250
is_deleted,
251
deleted_on_utc
252
)
253
SELECT
254
gen_random_uuid(),
255
p_organization_id,
256
oi.company_id,
257
a.snapshot_id,
258
oi.invoice_id,
259
oi.invoice_date,
260
oi.due_date,
261
oi.total_amount,
262
oi.settled_amount,
263
oi.outstanding_amount,
264
now(),
265
v_system_user_id,
266
now(),
267
v_system_user_id,
268
false,
269
NULL
270
FROM tmp_overdue_invoices oi
271
JOIN tmp_active_snapshots a
272
ON a.customer_id = oi.customer_id
273
GROUP BY
274
oi.company_id,
275
a.snapshot_id,
276
oi.invoice_id,
277
oi.invoice_date,
278
oi.due_date,
279
oi.total_amount,
280
oi.settled_amount,
281
oi.outstanding_amount
282
ON CONFLICT (snapshot_id, invoice_id)
283
DO UPDATE
284
SET
285
invoice_date = EXCLUDED.invoice_date,
286
due_date = EXCLUDED.due_date,
287
total_amount = EXCLUDED.total_amount,
288
settled_amount = EXCLUDED.settled_amount,
289
outstanding_amount = EXCLUDED.outstanding_amount,
290
is_deleted = false,
291
deleted_on_utc = NULL,
292
modified_on_utc = now(),
293
modified_by = v_system_user_id;
294
295
/* ====================================================
296
14) Close cycles with no overdue invoices
297
==================================================== */
298
UPDATE public.delinquency_cycles dc
299
SET ended_on_utc = now(),
300
status_id = 2,
301
modified_on_utc = now(),
302
modified_by = v_system_user_id
303
WHERE dc.organization_id = p_organization_id
304
AND dc.ended_on_utc IS NULL
305
AND dc.is_deleted = false
306
AND NOT EXISTS (
307
SELECT 1
308
FROM tmp_calc c
309
WHERE c.customer_id = dc.customer_id
310
);
311
312
/* ====================================================
313
15) Cleanup snapshots & windows for closed cycles
314
==================================================== */
315
UPDATE public.delinquency_snapshots ds
316
SET is_deleted = true,
317
deleted_on_utc = now(),
318
modified_on_utc = now(),
319
modified_by = v_system_user_id
320
WHERE ds.organization_id = p_organization_id
321
AND ds.is_deleted = false
322
AND EXISTS (
323
SELECT 1
324
FROM public.delinquency_cycles dc
325
WHERE dc.id = ds.cycle_id
326
AND dc.ended_on_utc IS NOT NULL
327
AND dc.is_deleted = false
328
);
329
330
UPDATE public.delinquency_resolution_window drw
331
SET is_deleted = true,
332
deleted_on_utc = now(),
333
modified_on_utc = now(),
334
modified_by = v_system_user_id
335
WHERE drw.is_deleted = false
336
AND EXISTS (
337
SELECT 1
338
FROM public.delinquency_cycles dc
339
WHERE dc.id = drw.id
340
AND dc.organization_id = p_organization_id
341
AND dc.ended_on_utc IS NOT NULL
342
AND dc.is_deleted = false
343
);
344
345
END;
346
$function$
|
|||||
| Function | evaluate_delinquency_for_org | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.evaluate_delinquency_for_org(p_organization_id uuid, p_company_id uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_grace_days integer := 0;
7
v_system_user_id uuid;
8
BEGIN
9
/* ====================================================
10
1) Load latest collection policy
11
==================================================== */
12
SELECT COALESCE(cp.grace_days, 0)
13
INTO v_grace_days
14
FROM public.collection_policies cp
15
WHERE cp.organization_id = p_organization_id
16
AND cp.is_deleted = false
17
ORDER BY cp.created_on_utc DESC
18
LIMIT 1;
19
20
21
v_system_user_id := public.get_system_user_id();
22
23
/* ====================================================
24
4) Cleanup temp tables (safe for same session)
25
==================================================== */
26
DROP TABLE IF EXISTS tmp_overdue_invoices;
27
DROP TABLE IF EXISTS tmp_calc;
28
DROP TABLE IF EXISTS tmp_open_cycles;
29
DROP TABLE IF EXISTS tmp_cycles_created;
30
DROP TABLE IF EXISTS tmp_cycle_map;
31
DROP TABLE IF EXISTS tmp_snapshot_inserted;
32
DROP TABLE IF EXISTS tmp_active_snapshots;
33
34
/* ====================================================
35
5) MATERIALIZE overdue invoices
36
==================================================== */
37
CREATE TEMP TABLE tmp_overdue_invoices ON COMMIT DROP AS
38
SELECT
39
company_id,
40
customer_id,
41
invoice_id,
42
invoice_date,
43
due_date,
44
total_amount,
45
settled_amount,
46
outstanding_amount
47
FROM public.get_canonical_overdue_invoices(p_company_id);
48
49
/* ====================================================
50
6) Aggregate delinquency per customer
51
==================================================== */
52
CREATE TEMP TABLE tmp_calc ON COMMIT DROP AS
53
SELECT
54
customer_id,
55
MIN(due_date) AS oldest_due_date,
56
SUM(outstanding_amount) AS overdue_amount,
57
(CURRENT_DATE - MIN(due_date + v_grace_days))::int AS days_past_due,
58
public.get_aging_bucket(
59
(CURRENT_DATE - MIN(due_date + v_grace_days))::int
60
) AS aging_bucket
61
FROM tmp_overdue_invoices
62
GROUP BY customer_id;
63
64
/* ====================================================
65
7) Existing open cycles
66
==================================================== */
67
CREATE TEMP TABLE tmp_open_cycles ON COMMIT DROP AS
68
SELECT
69
dc.customer_id,
70
dc.id AS cycle_id
71
FROM public.delinquency_cycles dc
72
WHERE dc.company_id = p_company_id
73
AND dc.ended_on_utc IS NULL
74
AND dc.is_deleted = false;
75
76
/* ====================================================
77
8) Create missing cycles (CTE bridge)
78
==================================================== */
79
CREATE TEMP TABLE tmp_cycles_created ON COMMIT DROP AS
80
WITH ins AS (
81
INSERT INTO public.delinquency_cycles (
82
id, organization_id, company_id, customer_id,
83
start_due_date, started_on_utc,
84
ended_on_utc,
85
status_id, resolution_state_id,
86
created_by, created_on_utc, is_deleted
87
)
88
SELECT
89
gen_random_uuid(),
90
p_organization_id,
91
p_company_id,
92
c.customer_id,
93
c.oldest_due_date,
94
now(),
95
NULL,
96
1, -- status id
97
1, -- resolution status id 1
98
v_system_user_id,
99
now(),
100
false
101
FROM tmp_calc c
102
LEFT JOIN tmp_open_cycles oc ON oc.customer_id = c.customer_id
103
WHERE oc.cycle_id IS NULL
104
RETURNING customer_id, id AS cycle_id
105
)
106
SELECT * FROM ins;
107
108
/* ====================================================
109
9) Cycle map (existing + created)
110
==================================================== */
111
CREATE TEMP TABLE tmp_cycle_map ON COMMIT DROP AS
112
SELECT
113
c.customer_id,
114
COALESCE(oc.cycle_id, cc.cycle_id) AS cycle_id,
115
c.oldest_due_date,
116
c.overdue_amount,
117
c.days_past_due,
118
c.aging_bucket
119
FROM tmp_calc c
120
LEFT JOIN tmp_open_cycles oc ON oc.customer_id = c.customer_id
121
LEFT JOIN tmp_cycles_created cc ON cc.customer_id = c.customer_id;
122
123
/* ====================================================
124
10) Insert snapshot if missing (CTE bridge)
125
==================================================== */
126
CREATE TEMP TABLE tmp_snapshot_inserted ON COMMIT DROP AS
127
WITH ins AS (
128
INSERT INTO public.delinquency_snapshots (
129
id, organization_id, company_id, customer_id, cycle_id,
130
overdue_amount, oldest_due_date,
131
days_past_due, aging_bucket,
132
last_evaluated_on,
133
created_by, created_on_utc,
134
is_deleted
135
)
136
SELECT
137
gen_random_uuid(),
138
p_organization_id,
139
p_company_id,
140
cm.customer_id,
141
cm.cycle_id,
142
cm.overdue_amount,
143
cm.oldest_due_date,
144
cm.days_past_due,
145
cm.aging_bucket,
146
now(),
147
v_system_user_id,
148
now(),
149
false
150
FROM tmp_cycle_map cm
151
WHERE NOT EXISTS (
152
SELECT 1
153
FROM public.delinquency_snapshots ds
154
WHERE ds.cycle_id = cm.cycle_id
155
AND ds.is_deleted = false
156
)
157
RETURNING cycle_id, id AS snapshot_id
158
)
159
SELECT * FROM ins;
160
161
/* ====================================================
162
11) Resolve active snapshot per cycle
163
==================================================== */
164
CREATE TEMP TABLE tmp_active_snapshots ON COMMIT DROP AS
165
SELECT
166
cm.customer_id,
167
cm.cycle_id,
168
COALESCE(
169
tsi.snapshot_id,
170
(
171
SELECT ds.id
172
FROM public.delinquency_snapshots ds
173
WHERE ds.cycle_id = cm.cycle_id
174
AND ds.is_deleted = false
175
ORDER BY ds.created_on_utc DESC
176
LIMIT 1
177
)
178
) AS snapshot_id
179
FROM tmp_cycle_map cm
180
LEFT JOIN tmp_snapshot_inserted tsi ON tsi.cycle_id = cm.cycle_id;
181
182
/* ====================================================
183
12) Update active snapshots
184
==================================================== */
185
UPDATE public.delinquency_snapshots ds
186
SET overdue_amount = c.overdue_amount,
187
oldest_due_date = c.oldest_due_date,
188
days_past_due = c.days_past_due,
189
aging_bucket = c.aging_bucket,
190
last_evaluated_on = now(),
191
modified_on_utc = now(),
192
modified_by = v_system_user_id
193
FROM tmp_cycle_map c
194
JOIN tmp_active_snapshots a ON a.cycle_id = c.cycle_id
195
WHERE ds.id = a.snapshot_id
196
AND ds.is_deleted = false;
197
198
/* ====================================================
199
13) Refresh snapshot invoices (UPSERT – idempotent)
200
==================================================== */
201
202
-- Soft delete old rows
203
UPDATE public.delinquency_snapshot_invoices dsi
204
SET is_deleted = true,
205
deleted_on_utc = now(),
206
modified_on_utc = now(),
207
modified_by = v_system_user_id
208
WHERE dsi.company_id = p_company_id
209
AND dsi.is_deleted = false
210
AND EXISTS (
211
SELECT 1
212
FROM tmp_active_snapshots a
213
WHERE a.snapshot_id = dsi.snapshot_id
214
);
215
216
-- Insert / revive latest overdue invoices
217
INSERT INTO public.delinquency_snapshot_invoices (
218
id,
219
company_id,
220
snapshot_id,
221
invoice_id,
222
invoice_date,
223
due_date,
224
total_amount,
225
settled_amount,
226
outstanding_amount,
227
created_on_utc,
228
created_by,
229
modified_on_utc,
230
modified_by,
231
is_deleted,
232
deleted_on_utc
233
)
234
SELECT
235
gen_random_uuid(),
236
oi.company_id,
237
a.snapshot_id,
238
oi.invoice_id,
239
oi.invoice_date,
240
oi.due_date,
241
oi.total_amount,
242
oi.settled_amount,
243
oi.outstanding_amount,
244
now(),
245
v_system_user_id,
246
now(),
247
v_system_user_id,
248
false,
249
NULL
250
FROM tmp_overdue_invoices oi
251
JOIN tmp_active_snapshots a
252
ON a.customer_id = oi.customer_id
253
GROUP BY
254
oi.company_id,
255
a.snapshot_id,
256
oi.invoice_id,
257
oi.invoice_date,
258
oi.due_date,
259
oi.total_amount,
260
oi.settled_amount,
261
oi.outstanding_amount
262
ON CONFLICT (snapshot_id, invoice_id)
263
DO UPDATE
264
SET
265
invoice_date = EXCLUDED.invoice_date,
266
due_date = EXCLUDED.due_date,
267
total_amount = EXCLUDED.total_amount,
268
settled_amount = EXCLUDED.settled_amount,
269
outstanding_amount = EXCLUDED.outstanding_amount,
270
is_deleted = false,
271
deleted_on_utc = NULL,
272
modified_on_utc = now(),
273
modified_by = v_system_user_id;
274
275
/* ====================================================
276
14) Close cycles with no overdue invoices
277
==================================================== */
278
UPDATE public.delinquency_cycles dc
279
SET ended_on_utc = now(),
280
status_id = 2,
281
modified_on_utc = now(),
282
modified_by = v_system_user_id
283
WHERE dc.organization_id = p_organization_id
284
AND dc.company_id = p_company_id
285
AND dc.ended_on_utc IS NULL
286
AND dc.is_deleted = false
287
AND NOT EXISTS (
288
SELECT 1
289
FROM tmp_calc c
290
WHERE c.customer_id = dc.customer_id
291
);
292
293
/* ====================================================
294
15) Cleanup snapshots & windows for closed cycles
295
==================================================== */
296
UPDATE public.delinquency_snapshots ds
297
SET is_deleted = true,
298
deleted_on_utc = now(),
299
modified_on_utc = now(),
300
modified_by = v_system_user_id
301
WHERE ds.organization_id = p_organization_id
302
AND ds.company_id = p_company_id
303
AND ds.is_deleted = false
304
AND EXISTS (
305
SELECT 1
306
FROM public.delinquency_cycles dc
307
WHERE dc.id = ds.cycle_id
308
AND dc.ended_on_utc IS NOT NULL
309
AND dc.is_deleted = false
310
);
311
312
UPDATE public.delinquency_resolution_window drw
313
SET is_deleted = true,
314
deleted_on_utc = now(),
315
modified_on_utc = now(),
316
modified_by = v_system_user_id
317
WHERE drw.is_deleted = false
318
AND EXISTS (
319
SELECT 1
320
FROM public.delinquency_cycles dc
321
WHERE dc.id = drw.id
322
AND dc.organization_id = p_organization_id
323
AND dc.company_id = p_company_id
324
AND dc.ended_on_utc IS NOT NULL
325
AND dc.is_deleted = false
326
);
327
328
END;
329
$function$
|
|||||
| Function | create_delinquency_action | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_delinquency_action(p_organization_id uuid, p_customer_id uuid, p_cycle_id uuid, p_action_type_code text, p_action_on_utc timestamp with time zone, p_actor uuid, p_resolution_state_code text DEFAULT NULL::text, p_notes text DEFAULT NULL::text, p_meta jsonb DEFAULT NULL::jsonb)
2
RETURNS jsonb
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_action_type_id int;
7
v_resolution_state_id int;
8
v_action_id uuid := gen_random_uuid();
9
v_snapshot_id uuid;
10
BEGIN
11
-- Validate cycle belongs to org + customer and is open
12
IF NOT EXISTS (
13
SELECT 1
14
FROM public.delinquency_cycles dc
15
WHERE dc.id = p_cycle_id
16
AND dc.organization_id = p_organization_id
17
AND dc.customer_id = p_customer_id
18
AND dc.is_deleted = false
19
) THEN
20
RAISE EXCEPTION 'Invalid cycle for org/customer';
21
END IF;
22
23
-- Resolve action type
24
SELECT dat.id INTO v_action_type_id
25
FROM public.delinquency_action_types dat
26
WHERE dat.code = p_action_type_code;
27
28
IF v_action_type_id IS NULL THEN
29
RAISE EXCEPTION 'Unknown action type code %', p_action_type_code;
30
END IF;
31
32
-- Resolve resolution state
33
IF p_resolution_state_code IS NOT NULL THEN
34
SELECT drs.id INTO v_resolution_state_id
35
FROM public.delinquency_resolution_states drs
36
WHERE drs.code = p_resolution_state_code;
37
END IF;
38
39
-- Attach current active snapshot (if any)
40
SELECT ds.id INTO v_snapshot_id
41
FROM public.delinquency_snapshots ds
42
WHERE ds.cycle_id = p_cycle_id
43
AND ds.is_deleted = false
44
ORDER BY ds.created_on_utc DESC
45
LIMIT 1;
46
47
-- Insert action
48
INSERT INTO public.delinquency_actions (
49
id, organization_id, customer_id, cycle_id, snapshot_id,
50
action_type_id, resolution_state_id,
51
notes, meta, action_on_utc,
52
created_by, created_on_utc, is_deleted
53
)
54
VALUES (
55
v_action_id, p_organization_id, p_customer_id, p_cycle_id, v_snapshot_id,
56
v_action_type_id, v_resolution_state_id,
57
p_notes, p_meta, p_action_on_utc,
58
p_actor, now(), false
59
);
60
61
-- Update cycle resolution state if provided
62
IF v_resolution_state_id IS NOT NULL THEN
63
UPDATE public.delinquency_cycles
64
SET resolution_state_id = v_resolution_state_id,
65
modified_on_utc = now(),
66
modified_by = p_actor
67
WHERE id = p_cycle_id;
68
END IF;
69
70
-- Upsert resolution window if TIME_GRANTED or PROMISE_TO_PAY
71
IF p_resolution_state_code IN ('TIME_GRANTED', 'PROMISE_TO_PAY') THEN
72
INSERT INTO public.delinquency_resolution_window (
73
cycle_id, pause_evaluation_until, reason,
74
created_on_utc, created_by, is_deleted
75
)
76
VALUES (
77
p_cycle_id,
78
COALESCE(
79
(p_meta ->> 'expectedDate')::date,
80
(p_meta ->> 'promiseDate')::date,
81
CURRENT_DATE
82
),
83
p_notes,
84
now(),
85
p_actor,
86
false
87
)
88
ON CONFLICT (cycle_id) DO UPDATE
89
SET pause_evaluation_until = EXCLUDED.pause_evaluation_until,
90
reason = COALESCE(EXCLUDED.reason, public.delinquency_resolution_window.reason),
91
is_deleted = false,
92
modified_by = p_actor,
93
modified_on_utc = now();
94
END IF;
95
96
-- If SETTLED or WAIVED -> close cycle + deactivate snapshot data
97
IF p_resolution_state_code IN ('SETTLED', 'WAIVED') THEN
98
UPDATE public.delinquency_cycles
99
SET ended_on_utc = now(),
100
status_id = 2,
101
modified_on_utc = now(),
102
modified_by = p_actor
103
WHERE id = p_cycle_id
104
AND ended_on_utc IS NULL;
105
106
-- Soft delete active snapshot
107
UPDATE public.delinquency_snapshots
108
SET is_deleted = true,
109
deleted_on_utc = now(),
110
deleted_by = p_actor
111
WHERE cycle_id = p_cycle_id
112
AND is_deleted = false;
113
114
-- Soft delete snapshot invoices
115
UPDATE public.delinquency_snapshot_invoices
116
SET is_deleted = true,
117
deleted_on_utc = now(),
118
deleted_by = p_actor
119
WHERE snapshot_id = v_snapshot_id
120
AND is_deleted = false;
121
122
-- Soft delete resolution window
123
UPDATE public.delinquency_resolution_window
124
SET is_deleted = true,
125
deleted_on_utc = now(),
126
deleted_by = p_actor
127
WHERE cycle_id = p_cycle_id
128
AND is_deleted = false;
129
END IF;
130
131
RETURN jsonb_build_object(
132
'action',
133
(SELECT row_to_json(a)
134
FROM (
135
SELECT da.id, da.organization_id, da.customer_id, da.cycle_id, da.snapshot_id,
136
dat.code AS action_type_code,
137
drs.code AS resolution_state_code,
138
da.notes, da.meta, da.action_on_utc
139
FROM public.delinquency_actions da
140
JOIN public.delinquency_action_types dat ON dat.id = da.action_type_id
141
LEFT JOIN public.delinquency_resolution_states drs ON drs.id = da.resolution_state_id
142
WHERE da.id = v_action_id
143
) a),
144
'cycle',
145
(SELECT row_to_json(c)
146
FROM (
147
SELECT dc.id, dc.resolution_state_id, dc.ended_on_utc, dc.status_id
148
FROM public.delinquency_cycles dc
149
WHERE dc.id = p_cycle_id
150
) c)
151
);
152
END;
153
$function$
|
|||||
| Function | create_discussion_message | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_discussion_message(p_organization_id uuid, p_cycle_id uuid, p_sender_id uuid, p_sender_type text, p_message_text text, p_sent_on_utc timestamp with time zone, p_actor uuid)
2
RETURNS jsonb
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_message_id uuid := gen_random_uuid();
7
v_result jsonb;
8
BEGIN
9
-- Verify the cycle exists and belongs to the organization
10
IF NOT EXISTS(
11
SELECT 1
12
FROM public.delinquency_cycles dc
13
WHERE dc.id = p_cycle_id
14
AND dc.organization_id = p_organization_id
15
AND dc.is_deleted = false
16
) THEN
17
RAISE EXCEPTION 'Delinquency cycle % not found or does not belong to organization %', p_cycle_id, p_organization_id;
18
END IF;
19
20
-- Validate sender_type
21
IF p_sender_type NOT IN ('RESIDENT', 'FACILITY_MANAGER') THEN
22
RAISE EXCEPTION 'Invalid sender_type: %. Must be RESIDENT or FACILITY_MANAGER', p_sender_type;
23
END IF;
24
25
-- Insert the message
26
INSERT INTO public.delinquency_discussion_messages (
27
id,
28
organization_id,
29
cycle_id,
30
sender_id,
31
sender_type,
32
message_text,
33
sent_on_utc,
34
created_by,
35
created_on_utc,
36
is_deleted
37
)
38
VALUES (
39
v_message_id,
40
p_organization_id,
41
p_cycle_id,
42
p_sender_id,
43
p_sender_type,
44
p_message_text,
45
p_sent_on_utc,
46
p_actor,
47
now(),
48
false
49
);
50
51
-- Return the created message
52
v_result := jsonb_build_object(
53
'message',
54
(SELECT row_to_json(msg)
55
FROM (SELECT ddm.id,
56
ddm.cycle_id,
57
ddm.sender_id,
58
ddm.sender_type,
59
ddm.message_text,
60
ddm.sent_on_utc
61
FROM public.delinquency_discussion_messages ddm
62
WHERE ddm.id = v_message_id) msg)
63
);
64
65
RETURN v_result;
66
END;
67
$function$
|
|||||
| Function | get_defaulter_contact | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_defaulter_contact(p_customer_id uuid)
2
RETURNS text
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_contact_number text;
7
BEGIN
8
SELECT
9
r.contact_number INTO v_contact_number
10
FROM
11
fdw_community.units u
12
INNER JOIN
13
fdw_community.resident_units ru ON u.id = ru.unit_id
14
INNER JOIN
15
fdw_community.residents r ON ru.resident_id = r.id
16
WHERE
17
u.customer_id = p_customer_id
18
AND u.is_deleted = false
19
AND ru.is_deleted = false
20
AND r.is_deleted = false
21
AND r.contact_number IS NOT NULL
22
LIMIT 1;
23
24
RETURN v_contact_number;
25
END;
26
$function$
|
|||||
| Function | get_defaulter_list | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_defaulter_list(p_company_id uuid)
2
RETURNS TABLE(id integer, cycle_id uuid, customer_id uuid, snapshot_id uuid, overdue_amount numeric, days_past_due integer, aging_bucket text, resolution_state text, is_anonymized boolean, can_reveal_unit boolean, next_expected_action_date date, customer_name text, resident_contact text, oldest_due_date date)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_organization_id uuid;
7
BEGIN
8
/* ------------------------------------------------------------
9
1️⃣ Resolve organization_id
10
------------------------------------------------------------ */
11
SELECT c.organization_id
12
INTO v_organization_id
13
FROM public.companies c
14
WHERE c.id = p_company_id;
15
16
/* ------------------------------------------------------------
17
2️⃣ Main query
18
------------------------------------------------------------ */
19
RETURN QUERY
20
WITH policy AS (
21
SELECT
22
COALESCE(cp.grace_days, 0)::integer AS grace_days,
23
COALESCE(cp.anonymize_until_days, 0)::integer AS anonymize_until_days,
24
COALESCE(cp.reveal_unit_after_days, 0)::integer AS reveal_unit_after_days
25
FROM public.collection_policies cp
26
WHERE cp.organization_id = v_organization_id
27
AND cp.is_deleted = false
28
ORDER BY cp.created_on_utc DESC
29
LIMIT 1
30
),
31
32
open_cycles AS (
33
SELECT
34
dc.id,
35
dc.customer_id,
36
dc.resolution_state_id
37
FROM public.delinquency_cycles dc
38
WHERE dc.company_id = p_company_id
39
AND dc.ended_on_utc IS NULL
40
AND dc.is_deleted = false
41
),
42
43
active_snapshots AS (
44
SELECT
45
ds.id,
46
ds.cycle_id,
47
ds.overdue_amount,
48
ds.days_past_due,
49
ds.aging_bucket::text,
50
ds.oldest_due_date::date
51
FROM public.delinquency_snapshots ds
52
WHERE ds.company_id = p_company_id
53
AND ds.is_deleted = false
54
),
55
56
last_action AS (
57
SELECT DISTINCT ON (da.cycle_id)
58
da.cycle_id,
59
da.meta,
60
da.action_on_utc
61
FROM public.delinquency_actions da
62
WHERE da.company_id = p_company_id
63
AND da.is_deleted = false
64
ORDER BY da.cycle_id, da.action_on_utc DESC
65
),
66
67
base AS (
68
SELECT
69
/* row_number() → bigint → integer */
70
row_number() OVER (
71
ORDER BY ds.days_past_due DESC, ds.overdue_amount DESC
72
)::integer AS row_no,
73
74
oc.id AS cycle_id,
75
oc.customer_id,
76
ds.id AS snapshot_id,
77
ds.overdue_amount,
78
ds.days_past_due,
79
ds.aging_bucket,
80
rs.code::text AS resolution_state_code,
81
82
/* Policy-driven flags */
83
(ds.days_past_due < COALESCE(p.anonymize_until_days, 0)) AS is_anonymized,
84
(ds.days_past_due >= COALESCE(p.reveal_unit_after_days, 0)) AS can_reveal_unit,
85
86
/* JSON → text → date */
87
NULLIF(la.meta ->> 'expectedDate', '')::date
88
AS next_expected_action_date,
89
90
cust.name::text AS customer_name,
91
r.contact_number::text AS resident_contact,
92
ds.oldest_due_date
93
94
FROM open_cycles oc
95
JOIN active_snapshots ds
96
ON ds.cycle_id = oc.id
97
98
LEFT JOIN policy p ON TRUE
99
LEFT JOIN last_action la ON la.cycle_id = oc.id
100
LEFT JOIN public.delinquency_resolution_states rs
101
ON rs.id = oc.resolution_state_id
102
LEFT JOIN public.customers cust
103
ON cust.id = oc.customer_id
104
105
/* ---------- Community FDW joins ---------- */
106
107
LEFT JOIN fdw_community.units u
108
ON u.customer_id = oc.customer_id
109
AND u.is_deleted = false
110
111
/* Pick exactly ONE resident per unit */
112
LEFT JOIN LATERAL (
113
SELECT DISTINCT ON (ru.unit_id)
114
ru.unit_id,
115
ru.resident_id
116
FROM fdw_community.resident_units ru
117
JOIN fdw_community.residents r2
118
ON r2.id = ru.resident_id
119
AND r2.is_deleted = false
120
AND r2.contact_number IS NOT NULL
121
WHERE ru.unit_id = u.id
122
AND ru.is_deleted = false
123
ORDER BY
124
ru.unit_id,
125
ru.is_primary_owner DESC,
126
ru.created_on_utc ASC
127
) ru ON TRUE
128
129
LEFT JOIN fdw_community.residents r
130
ON r.id = ru.resident_id
131
132
/* Visibility rule */
133
WHERE ds.days_past_due >= COALESCE(p.reveal_unit_after_days, 0)
134
)
135
136
SELECT
137
b.row_no AS id,
138
b.cycle_id,
139
b.customer_id,
140
b.snapshot_id,
141
b.overdue_amount,
142
b.days_past_due,
143
b.aging_bucket,
144
COALESCE(b.resolution_state_code, 'NONE')::text AS resolution_state,
145
b.is_anonymized,
146
b.can_reveal_unit,
147
b.next_expected_action_date,
148
b.customer_name,
149
b.resident_contact,
150
b.oldest_due_date
151
FROM base b
152
ORDER BY
153
b.days_past_due DESC,
154
b.overdue_amount DESC;
155
156
END;
157
$function$
|
|||||
| Function | get_defaulter_details | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_defaulter_details(p_company_id uuid, p_customer_id uuid)
2
RETURNS jsonb
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_cycle_id uuid;
7
v_snapshot_id uuid;
8
BEGIN
9
SELECT dc.id
10
INTO v_cycle_id
11
FROM public.delinquency_cycles dc
12
WHERE dc.company_id = p_company_id
13
AND dc.customer_id = p_customer_id
14
AND dc.ended_on_utc IS NULL
15
AND dc.is_deleted = false
16
ORDER BY dc.started_on_utc DESC
17
LIMIT 1;
18
19
IF v_cycle_id IS NULL THEN
20
RETURN '{}'::jsonb;
21
END IF;
22
23
SELECT ds.id
24
INTO v_snapshot_id
25
FROM public.delinquency_snapshots ds
26
WHERE ds.cycle_id = v_cycle_id
27
AND ds.is_deleted = false
28
ORDER BY ds.created_on_utc DESC
29
LIMIT 1;
30
31
RETURN jsonb_build_object(
32
'cycle',
33
(SELECT row_to_json(c)
34
FROM (
35
SELECT dc.id, dc.organization_id, dc.company_id, dc.customer_id,
36
dc.start_due_date, dc.started_on_utc, dc.ended_on_utc,
37
dc.status_id, dc.resolution_state_id
38
FROM public.delinquency_cycles dc
39
WHERE dc.id = v_cycle_id
40
) c),
41
'snapshot',
42
(SELECT row_to_json(s)
43
FROM (
44
SELECT ds.id, ds.cycle_id, ds.company_id, ds.customer_id,
45
ds.overdue_amount, ds.oldest_due_date,
46
ds.days_past_due, ds.aging_bucket,
47
ds.last_evaluated_on
48
FROM public.delinquency_snapshots ds
49
WHERE ds.id = v_snapshot_id
50
) s),
51
'invoices',
52
(SELECT COALESCE(jsonb_agg(row_to_json(inv)), '[]'::jsonb)
53
FROM (
54
SELECT dsi.invoice_id, dsi.snapshot_id, dsi.company_id,
55
dsi.invoice_date, dsi.due_date,
56
dsi.total_amount, dsi.settled_amount, dsi.outstanding_amount,
57
ih.invoice_number, ih.note
58
FROM public.delinquency_snapshot_invoices dsi
59
JOIN public.invoice_headers ih ON ih.id = dsi.invoice_id
60
WHERE dsi.snapshot_id = v_snapshot_id
61
AND dsi.is_deleted = false
62
) inv),
63
'actions',
64
(SELECT COALESCE(jsonb_agg(row_to_json(act) ORDER BY act.action_on_utc DESC), '[]'::jsonb)
65
FROM (
66
SELECT da.id, da.cycle_id, da.customer_id, da.organization_id, da.snapshot_id,
67
dat.code AS action_type_code,
68
drs.code AS resolution_state_code,
69
da.notes, da.meta, da.action_on_utc
70
FROM public.delinquency_actions da
71
JOIN public.delinquency_action_types dat ON dat.id = da.action_type_id
72
LEFT JOIN public.delinquency_resolution_states drs ON drs.id = da.resolution_state_id
73
WHERE da.cycle_id = v_cycle_id
74
AND da.is_deleted = false
75
) act)
76
);
77
END;
78
$function$
|
|||||
| Function | get_canonical_overdue_invoices | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_canonical_overdue_invoices(p_company_id uuid)
2
RETURNS TABLE(company_id uuid, customer_id uuid, invoice_id uuid, invoice_date date, due_date date, total_amount numeric, settled_amount numeric, outstanding_amount numeric, days_past_due integer)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
SELECT
7
ih.company_id,
8
ih.customer_id,
9
ih.id AS invoice_id,
10
ih.invoice_date::date,
11
ih.due_date::date,
12
ih.total_amount,
13
COALESCE(ih.settled_amount, 0) AS settled_amount,
14
(ih.total_amount - COALESCE(ih.settled_amount, 0)) AS outstanding_amount,
15
(CURRENT_DATE - (ih.due_date::date + cp.grace_days))::int AS days_past_due
16
FROM public.invoice_headers ih
17
JOIN public.companies co
18
ON co.id = ih.company_id
19
AND co.is_deleted = false
20
JOIN public.collection_policies cp
21
ON cp.organization_id = co.organization_id
22
AND cp.is_deleted = false
23
WHERE ih.company_id = p_company_id
24
AND ih.is_deleted = false
25
AND (ih.total_amount - COALESCE(ih.settled_amount, 0)) > 0
26
AND (ih.due_date::date + cp.grace_days) < CURRENT_DATE;
27
$function$
|
|||||
| Function | get_defaulter_summary | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_defaulter_summary(p_company_id uuid)
2
RETURNS TABLE(id integer, total_defaulters_count integer, total_outstanding numeric)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
SELECT
7
1 AS id,
8
COUNT(DISTINCT customer_id)::int AS total_defaulters_count,
9
SUM(outstanding_amount) AS total_outstanding
10
FROM public.get_canonical_overdue_invoices(p_company_id);
11
$function$
|
|||||
| Procedure | insert_invoice_by_excel | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_invoice_by_excel(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_invoice_date timestamp without time zone, IN p_invoice_number text, IN p_payment_term integer, IN p_invoice_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_so_no text, IN p_so_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_invoice_voucher text, IN p_created_by uuid, IN p_invoice_details jsonb, IN p_type integer, IN p_settled_amount numeric DEFAULT 0)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
detail RECORD;
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
v_invoice_number text;
9
BEGIN
10
-- Set default values for warehouses
11
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
12
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
13
14
IF p_invoice_number IS NULL OR p_invoice_number = '' THEN
15
v_invoice_number := get_new_invoice_number(p_company_id, p_invoice_date::date);
16
ELSE
17
v_invoice_number = p_invoice_number;
18
END IF;
19
20
-- Validate JSON structure
21
BEGIN
22
PERFORM jsonb_array_elements(p_invoice_details)::jsonb;
23
EXCEPTION
24
WHEN others THEN
25
RAISE EXCEPTION 'Invalid JSON format for invoice details';
26
END;
27
RAISE NOTICE 'Processing Invoice: %', p_id;
28
-- Insert into the invoice header
29
INSERT INTO
30
public.invoice_headers(
31
id,
32
company_id,
33
customer_id,
34
credit_account_id,
35
debit_account_id,
36
invoice_number,
37
invoice_date,
38
due_date,
39
payment_term,
40
taxable_amount,
41
cgst_amount,
42
sgst_amount,
43
igst_amount,
44
total_amount,
45
discount,
46
fees,
47
round_off,
48
currency_id,
49
note,
50
invoice_status_id,
51
created_by,
52
invoice_voucher,
53
source_warehouse_id,
54
destination_warehouse_id,
55
created_on_utc,
56
so_no,
57
so_date,
58
type
59
)
60
VALUES (
61
p_id,
62
p_company_id,
63
p_customer_id,
64
p_credit_account_id,
65
p_debit_account_id,
66
v_invoice_number,
67
p_invoice_date,
68
p_due_date,
69
p_payment_term,
70
p_taxable_amount,
71
p_cgst_amount,
72
p_sgst_amount,
73
p_igst_amount,
74
p_total_amount,
75
p_discount,
76
p_fees,
77
p_round_off,
78
p_currency_id,
79
p_note,
80
p_invoice_status_id,
81
p_created_by,
82
p_invoice_voucher,
83
p_source_warehouse_id,
84
p_destination_warehouse_id,
85
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
86
p_so_no,
87
p_so_date,
88
p_type
89
);
90
RAISE NOTICE 'Invoice header inserted with ID: %', p_id;
91
92
-- Loop through the JSONB array of invoice details
93
FOR detail IN
94
SELECT * FROM jsonb_to_recordset(p_invoice_details) AS (
95
id uuid, product_id uuid, price numeric, quantity numeric,
96
fees numeric, discount numeric, taxable_amount numeric,
97
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
98
total_amount numeric, serial_number integer
99
)
100
LOOP
101
-- Insert into invoice details
102
INSERT INTO public.invoice_details (
103
id, invoice_header_id, product_id, price, quantity, fees, discount,
104
taxable_amount, sgst_amount, cgst_amount, igst_amount, total_amount,
105
serial_number, is_deleted
106
) VALUES (
107
detail.id, p_id, detail.product_id, detail.price, detail.quantity, detail.fees,
108
detail.discount, detail.taxable_amount, detail.sgst_amount, detail.cgst_amount,
109
detail.igst_amount, detail.total_amount, detail.serial_number, false
110
);
111
112
RAISE NOTICE 'Invoice detail inserted with ID: %', detail.id;
113
END LOOP;
114
115
-- No explicit COMMIT here
116
RAISE NOTICE 'Transaction completed successfully';
117
118
EXCEPTION
119
-- Catch all other exceptions
120
WHEN OTHERS THEN
121
RAISE EXCEPTION 'An error occurred: %', SQLERRM;
122
123
END;
124
$procedure$
|
|||||
| Procedure | create_apartment_invoice_template | Match | ||||||
| Procedure | create_draft_invoice_detail | Match | ||||||
| Procedure | create_invoice_template | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_invoice_template(IN p_invoiceheader_id uuid, IN p_frequency_cycle text, IN p_month_of_year integer, IN p_day_of_month integer, IN p_day_of_week integer, IN p_time_of_day time without time zone, IN p_starts_from date, IN p_end_date date, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_template_id uuid := gen_random_uuid(); -- Generate a new UUID for the template ID
6
BEGIN
7
-- Insert the new template into the invoice_template table
8
INSERT INTO public.invoice_template (
9
id, invoice_header_id, frequency_cycle, month_of_year, day_of_month,
10
day_of_week, time_of_day, starts_from, end_date, created_on_utc, created_by, is_deleted
11
)
12
VALUES (
13
v_template_id, p_invoiceheader_id, p_frequency_cycle, p_month_of_year, p_day_of_month,
14
p_day_of_week, p_time_of_day, p_starts_from, p_end_date, NOW(), p_created_by, false
15
);
16
17
RAISE NOTICE 'Invoice template created successfully with ID: %', v_template_id;
18
END;
19
$procedure$
|
|||||
| Procedure | create_multiple_invoice_payments | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_multiple_invoice_payments(IN p_invoice_payments jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
payment RECORD;
6
payment_statuses TEXT[] := ARRAY[]::TEXT[];
7
BEGIN
8
-- Loop through each payment in the JSONB array
9
FOR payment IN
10
SELECT * FROM jsonb_to_recordset(p_invoice_payments) AS (
11
invoice_payment_header_id uuid,
12
company_id uuid,
13
customer_id uuid,
14
received_date date,
15
mode_of_payment text,
16
credit_account_id uuid,
17
debit_account_id uuid,
18
reference text,
19
received_amount numeric,
20
grand_total_amount numeric,
21
advance_amount numeric,
22
description text,
23
tds_amount numeric,
24
created_by uuid,
25
invoice_payment_details jsonb
26
)
27
LOOP
28
BEGIN
29
IF NOT EXISTS (
30
SELECT 1
31
FROM public.invoice_payment_headers
32
WHERE reference = payment.reference
33
AND company_id = payment.company_id
34
) THEN
35
CALL public.create_invoice_payment(
36
payment.invoice_payment_header_id,
37
payment.company_id,
38
payment.customer_id,
39
payment.received_date,
40
payment.mode_of_payment,
41
payment.credit_account_id,
42
payment.debit_account_id,
43
payment.reference,
44
payment.received_amount,
45
payment.grand_total_amount,
46
payment.advance_amount,
47
payment.description,
48
payment.tds_amount,
49
payment.created_by,
50
payment.invoice_payment_details
51
);
52
payment_statuses := payment_statuses || format('%s:success', payment.invoice_payment_header_id);
53
ELSE
54
RAISE NOTICE 'Duplicate invoice payment found for reference: %, company_id: %. Skipping.',
55
payment.reference, payment.company_id;
56
payment_statuses := payment_statuses || format('%s:duplicate', payment.invoice_payment_header_id);
57
CONTINUE;
58
END IF;
59
EXCEPTION
60
WHEN OTHERS THEN
61
RAISE NOTICE 'Error processing payment ID: %, Error: %',
62
payment.invoice_payment_header_id, SQLERRM;
63
payment_statuses := payment_statuses || format('%s:failed', payment.invoice_payment_header_id);
64
END;
65
END LOOP;
66
67
-- Final status notice (for app to parse)
68
RAISE NOTICE 'PAYMENT_STATUS:%', to_json(payment_statuses);
69
70
RAISE NOTICE 'All invoice payments processed.';
71
END;
72
$procedure$
|
|||||
| Procedure | delete_apartment_invoice_template | Match | ||||||
| Procedure | delete_invoice_data_by_company_id | Match | ||||||
| Procedure | initialize_invoice_header_ids | Match | ||||||
| Procedure | get_all_customer_names | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.get_all_customer_names(IN p_company_id uuid, OUT customer_list jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
-- Fetch customer details (Id, Name) for the given CompanyId
6
SELECT jsonb_agg(
7
jsonb_build_object(
8
'Id', c.id,
9
'Name', c.name
10
)
11
)
12
INTO customer_list
13
FROM public.customers c
14
WHERE c.company_id = p_company_id;
15
16
-- If no customers are found, return an empty array
17
IF customer_list IS NULL THEN
18
customer_list := '[]'::jsonb;
19
END IF;
20
END;
21
$procedure$
|
|||||
| Procedure | initialize_group_invoice_header_ids | Match | ||||||
| Procedure | hard_delete_org_sales | Match | ||||||
| Procedure | hard_delete_organization | Match | ||||||
| Procedure | initialize_company | Match | ||||||
| Procedure | initialize_customer_note_work_flows | Match | ||||||
| Procedure | initialize_invoice_workflow | Match | ||||||
| Procedure | log_invoice_approval | Match | ||||||
| Procedure | run_batches | Match | ||||||
| Procedure | run_grouped_invoices | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.run_grouped_invoices(IN p_template_id uuid)
1
CREATE OR REPLACE PROCEDURE public.run_grouped_invoices(IN p_template_id uuid)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
DECLARE
4
DECLARE
5
schedule_record RECORD;
5
schedule_record RECORD;
6
v_unit RECORD;
6
v_unit RECORD;
7
v_group_invoice_header_id UUID;
7
v_group_invoice_header_id UUID;
8
v_invoice_header_id UUID;
8
v_invoice_header_id UUID;
9
v_total_amount NUMERIC;
9
v_total_amount NUMERIC;
10
v_status TEXT;
10
v_status TEXT;
11
v_error_message TEXT;
11
v_error_message TEXT;
12
v_total_count INTEGER;
12
v_total_count INTEGER;
13
v_success_count INTEGER;
13
v_success_count INTEGER;
14
v_calculation_type INTEGER;
14
v_calculation_type INTEGER;
15
v_company_id UUID;
15
v_company_id UUID;
16
v_sales_account_id UUID;
16
v_sales_account_id UUID;
17
v_account_receivable_id UUID;
17
v_account_receivable_id UUID;
18
v_invoice_date DATE;
18
v_invoice_date DATE;
19
v_starts_from DATE;
19
v_starts_from DATE;
20
v_end_date DATE;
20
v_end_date DATE;
21
v_due_date DATE;
21
v_due_date DATE;
22
v_payment_term INTEGER;
22
v_payment_term INTEGER;
23
v_currency_id INTEGER;
23
v_currency_id INTEGER;
24
v_fixed_product_id UUID;
24
v_fixed_product_id UUID;
25
v_bhk_product_id UUID;
25
v_bhk_product_id UUID;
26
v_sqft_product_id UUID;
26
v_sqft_product_id UUID;
27
v_fixed_product_rate NUMERIC;
27
v_fixed_product_rate NUMERIC;
28
v_bhk_product_rate NUMERIC;
28
v_bhk_product_rate NUMERIC;
29
v_sqft_product_rate NUMERIC;
29
v_sqft_product_rate NUMERIC;
30
v_note TEXT;
30
v_note TEXT;
31
v_invoice_status_id INTEGER;
31
v_invoice_status_id INTEGER;
32
v_due_days INTEGER;
32
v_due_days INTEGER;
33
v_billing_cycle TEXT;
33
v_billing_cycle TEXT;
34
v_active_status BOOLEAN;
34
v_active_status BOOLEAN;
35
v_half_year INTEGER;
36
v_quarters_of_month INTEGER;
37
v_bi_month INTEGER;
35
v_day_of_week INTEGER;
38
v_day_of_week INTEGER;
36
v_day_of_month INTEGER;
39
v_day_of_month INTEGER;
37
v_month_of_year INTEGER;
40
v_month_of_year INTEGER;
38
v_time_of_day TIME;
41
v_time_of_day TIME; -- Change to store time directly
39
v_created_by UUID;
42
v_created_by UUID; -- Store the system user ID
40
v_group_invoice_number VARCHAR;
43
v_group_invoice_number VARCHAR;
41
v_serial_number INTEGER := 1;
44
v_serial_number INTEGER := 1; -- Initialize serial number
42
v_product_rate NUMERIC;
45
v_product_rate NUMERIC;
43
v_quantity NUMERIC;
46
v_quantity NUMERIC;
44
v_product_id UUID;
47
v_product_id UUID;
45
v_template_exists BOOL;
46
v_template_is_deleted BOOL;
47
BEGIN
48
BEGIN
48
RAISE NOTICE '[START] run_grouped_invoices(template_id=%)', p_template_id;
49
-- Set the current time for execution
50
v_time_of_day := CURRENT_TIME;
49
51
50
-- Extra visibility on template state
52
-- Validate input: Ensure p_template_id is valid
51
SELECT EXISTS (SELECT 1 FROM public.group_invoice_templates WHERE id = p_template_id) AS exists,
52
COALESCE( (SELECT is_deleted FROM public.group_invoice_templates WHERE id = p_template_id LIMIT 1), NULL) AS is_deleted
53
INTO v_template_exists, v_template_is_deleted;
54
55
RAISE NOTICE '[CHECK] template exists? %, is_deleted? %', v_template_exists, v_template_is_deleted;
56
57
IF NOT EXISTS (
53
IF NOT EXISTS (
58
SELECT 1 FROM public.group_invoice_templates
54
SELECT 1
55
FROM public.group_invoice_templates
59
WHERE id = p_template_id AND is_deleted = FALSE
56
WHERE id = p_template_id AND is_deleted = FALSE
60
) THEN
57
) THEN
61
RAISE EXCEPTION 'Invalid template_id: % (exists=%, is_deleted=%)', p_template_id, v_template_exists, v_template_is_deleted;
58
RAISE EXCEPTION 'Invalid template_id: %', p_template_id;
62
END IF;
59
END IF;
63
60
64
v_time_of_day := CURRENT_TIME;
61
-- Loop through batch schedules for the specific template_id
65
RAISE NOTICE '[INFO] current_time=%', v_time_of_day;
66
67
FOR schedule_record IN
62
FOR schedule_record IN
68
SELECT *
63
SELECT *
69
FROM batch_schedules bs
64
FROM batch_schedules bs
70
WHERE bs.group_invoice_template_id = p_template_id
65
WHERE bs.group_invoice_template_id = p_template_id
71
AND bs.is_deleted = FALSE
66
AND bs.is_deleted = FALSE
72
AND bs.last_executed_at IS DISTINCT FROM CURRENT_DATE::timestamp
67
AND bs.last_executed_at IS DISTINCT FROM CURRENT_DATE::timestamp
73
LOOP
68
LOOP
74
RAISE NOTICE '[SCHEDULE] processing schedule_id=% last_executed_at=%', schedule_record.id, schedule_record.last_executed_at;
69
-- Fetch template details
75
76
SELECT calculation_type_id, company_id, sales_account_id, invoice_date, starts_from, grace_period, due_days,
70
SELECT calculation_type_id, company_id, sales_account_id, invoice_date, starts_from, grace_period, due_days,
77
fixed_rate, bhk_rate, sqft_rate, invoice_description, billing_cycle, account_receivable_id,
71
fixed_rate, bhk_rate, sqft_rate, invoice_description, billing_cycle, account_receivable_id,
78
currency_id, bhk_product_id, fixed_product_id, sqft_product_id, end_date, created_by, active_status
72
currency_id, bhk_product_id, fixed_product_id, sqft_product_id, end_date, created_by, active_status
79
INTO v_calculation_type, v_company_id, v_sales_account_id, v_invoice_date, v_starts_from, v_payment_term,
73
INTO v_calculation_type, v_company_id, v_sales_account_id, v_invoice_date, v_starts_from, v_payment_term,
80
v_due_days, v_fixed_product_rate, v_bhk_product_rate, v_sqft_product_rate, v_note, v_billing_cycle,
74
v_due_days, v_fixed_product_rate, v_bhk_product_rate, v_sqft_product_rate, v_note, v_billing_cycle,
81
v_account_receivable_id, v_currency_id, v_bhk_product_id, v_fixed_product_id, v_sqft_product_id, v_end_date,
75
v_account_receivable_id, v_currency_id, v_bhk_product_id, v_fixed_product_id, v_sqft_product_id, v_end_date,
82
v_created_by, v_active_status
76
v_created_by, v_active_status
83
FROM public.group_invoice_templates git
77
FROM public.group_invoice_templates git
84
WHERE git.id = p_template_id;
78
WHERE git.id = p_template_id;
85
79
86
RAISE NOTICE '[TEMPLATE] company_id=% calc_type=% inv_date=% starts_from=% due_days=% end_date=% active=%',
80
-- Fetch the system user ID from the users table
87
v_company_id, v_calculation_type, v_invoice_date, v_starts_from, v_due_days, v_end_date, v_active_status;
81
SELECT id
88
82
INTO v_created_by
89
-- Force System user
90
SELECT id INTO v_created_by
91
FROM users
83
FROM users
92
WHERE first_name = 'System'
84
WHERE first_name = 'System'
93
LIMIT 1;
85
LIMIT 1;
94
86
95
RAISE NOTICE '[USER] created_by (System) id=%', v_created_by;
87
-- Raise an exception if the system user is not found
96
97
IF v_created_by IS NULL THEN
88
IF v_created_by IS NULL THEN
98
RAISE EXCEPTION 'System user not found in the users table';
89
RAISE EXCEPTION 'System user not found in the users table';
99
END IF;
90
END IF;
100
91
92
-- Skip processing if the template is not active
101
IF NOT v_active_status THEN
93
IF NOT v_active_status THEN
102
RAISE NOTICE '[SKIP] template inactive';
94
RAISE NOTICE 'Skipping template with ID: %, as active_status is FALSE', p_template_id;
103
CONTINUE;
95
CONTINUE;
104
END IF;
96
END IF;
105
97
98
-- Calculate the due date
106
v_due_date := v_invoice_date + v_due_days;
99
v_due_date := v_invoice_date + v_due_days;
107
RAISE NOTICE '[DATES] invoice_date=% due_days=% due_date=%', v_invoice_date, v_due_days, v_due_date;
108
100
101
-- Skip processing if the due date is before the starts_from date
109
IF v_starts_from IS NOT NULL AND v_due_date < v_starts_from THEN
102
IF v_starts_from IS NOT NULL AND v_due_date < v_starts_from THEN
110
RAISE NOTICE '[SKIP] due_date % < starts_from %', v_due_date, v_starts_from;
111
CONTINUE;
103
CONTINUE;
112
END IF;
104
END IF;
113
105
106
-- Skip processing if the template has ended
114
IF v_end_date IS NOT NULL AND v_end_date < CURRENT_DATE THEN
107
IF v_end_date IS NOT NULL AND v_end_date < CURRENT_DATE THEN
115
RAISE NOTICE '[SKIP] end_date % < today %', v_end_date, CURRENT_DATE;
116
CONTINUE;
108
CONTINUE;
117
END IF;
109
END IF;
118
110
119
v_group_invoice_header_id := gen_random_uuid();
111
v_group_invoice_header_id := gen_random_uuid();
120
v_total_count := 0;
112
v_total_count := 0;
121
v_success_count := 0;
113
v_success_count := 0;
122
v_status := 'pending';
114
v_status := 'pending';
123
v_error_message := '';
115
v_error_message := '';
124
116
117
125
v_group_invoice_number := get_new_group_invoice_number(v_company_id, v_invoice_date);
118
v_group_invoice_number := get_new_group_invoice_number(v_company_id, v_invoice_date);
126
RAISE NOTICE '[GROUP] new group_invoice_header_id=% number=%', v_group_invoice_header_id, v_group_invoice_number;
127
119
120
-- Insert into group_invoice_headers
128
INSERT INTO public.group_invoice_headers (
121
INSERT INTO public.group_invoice_headers (
129
id, group_invoice_template_id, execution_date, success_count,
122
id, group_invoice_template_id, execution_date, success_count, created_on_utc, created_by, total_count, error_message, company_id, group_invoice_number
130
created_on_utc, created_by, total_count, error_message, company_id, group_invoice_number
131
)
123
)
132
VALUES (
124
VALUES (
133
v_group_invoice_header_id, p_template_id, NOW(), v_success_count,
125
v_group_invoice_header_id, p_template_id, NOW(), v_success_count, NOW(), v_created_by, v_total_count, v_error_message, v_company_id, v_group_invoice_number
134
NOW(), v_created_by, v_total_count, v_error_message, v_company_id, v_group_invoice_number
135
);
126
);
136
127
128
-- Fetch invoice status with status = 3 for a specific company
137
SELECT status
129
SELECT status
138
INTO v_invoice_status_id
130
INTO v_invoice_status_id
139
FROM public.invoice_workflow iw
131
FROM public.invoice_workflow iw
140
WHERE iw.company_id = v_company_id
132
WHERE iw.company_id = v_company_id
141
AND iw.is_deleted = FALSE
133
AND iw.is_deleted = FALSE
142
AND iw.status = 3
134
AND iw.status = 3
143
LIMIT 1;
135
LIMIT 1;
144
136
145
RAISE NOTICE '[WORKFLOW] invoice_status_id=%', v_invoice_status_id;
137
-- Process units associated with the template_id
146
147
FOR v_unit IN
138
FOR v_unit IN
148
SELECT unit_id, bhk, sqft_area, customer_id
139
SELECT unit_id, bhk, sqft_area, customer_id
149
FROM public.group_invoice_template_customers gitc
140
FROM public.group_invoice_template_customers gitc
150
WHERE gitc.group_invoice_template_id = p_template_id
141
WHERE gitc.group_invoice_template_id = p_template_id
151
LOOP
142
LOOP
152
BEGIN
143
BEGIN
153
v_total_count := v_total_count + 1;
144
v_total_count := v_total_count + 1;
154
v_invoice_header_id := gen_random_uuid();
145
v_invoice_header_id := gen_random_uuid();
155
v_total_amount := 0;
146
v_total_amount := 0;
156
v_serial_number := 1;
147
v_serial_number := 1;
148
149
-- Crete invoice detail
150
CASE v_calculation_type
151
WHEN 1 THEN -- Fixed method
152
v_total_amount := v_fixed_product_rate;
153
v_product_rate := v_fixed_product_rate;
154
v_quantity := 1; -- Fixed rate always has a quantity of 1
155
v_product_id := v_fixed_product_id;
157
156
158
RAISE NOTICE ' [UNIT] unit_id=% customer_id=% bhk=% sqft=%', v_unit.unit_id, v_unit.customer_id, v_unit.bhk, v_unit.sqft_area;
157
-- Insert Fixed Rate detail
158
CALL public.create_invoice_detail(
159
v_invoice_header_id,
160
v_product_rate, -- Product rate for Fixed
161
v_quantity, -- Quantity (1)
162
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
163
v_product_rate, -- Taxable amount
164
v_total_amount, -- Total amount
165
v_serial_number, -- Serial number (1)
166
v_product_id -- Product ID for Fixed
167
);
168
169
WHEN 2 THEN -- BHK method
170
v_total_amount := v_bhk_product_rate * (v_unit.bhk)::NUMERIC;
171
v_product_rate := v_total_amount; -- Total amount acts as the product rate
172
v_quantity := 1; -- Single entry for BHK calculation
173
v_product_id := v_bhk_product_id;
174
175
-- Insert BHK Rate detail
176
CALL public.create_invoice_detail(
177
v_invoice_header_id,
178
v_product_rate, -- Product rate for BHK
179
v_quantity, -- Quantity (1)
180
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
181
v_product_rate, -- Taxable amount
182
v_total_amount, -- Total amount
183
v_serial_number, -- Serial number (1)
184
v_product_id -- Product ID for BHK
185
);
186
187
WHEN 3 THEN -- SFT method
188
v_total_amount := v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC;
189
v_product_rate := v_sqft_product_rate; -- the product rate
190
v_quantity := v_unit.sqft_area; -- Single entry for SFT calculation
191
v_product_id := v_sqft_product_id;
192
193
-- Insert SFT Rate detail
194
CALL public.create_invoice_detail(
195
v_invoice_header_id,
196
v_product_rate, -- Product rate for SFT
197
v_quantity, -- Quantity
198
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
199
v_product_rate * v_quantity, -- Taxable amount
200
v_total_amount, -- Total amount
201
v_serial_number, -- Serial number (1)
202
v_product_id -- Product ID for SFT
203
);
204
205
WHEN 4 THEN -- Fixed + BHK
206
-- Total amount is a combination of fixed rate and BHK calculation
207
v_total_amount := v_fixed_product_rate + (v_bhk_product_rate * (v_unit.bhk)::NUMERIC);
159
208
160
-- Compute total first
209
-- Insert Fixed rate detail
161
CASE v_calculation_type
210
CALL public.create_invoice_detail(
162
WHEN 1 THEN v_total_amount := v_fixed_product_rate;
211
v_invoice_header_id,
163
WHEN 2 THEN v_total_amount := v_bhk_product_rate * (v_unit.bhk)::NUMERIC;
212
v_fixed_product_rate,
164
WHEN 3 THEN v_total_amount := v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC;
213
1, -- Quantity for fixed rate
165
WHEN 4 THEN v_total_amount := v_fixed_product_rate + (v_bhk_product_rate * (v_unit.bhk)::NUMERIC);
214
0, 0, 0, 0, 0,
166
WHEN 5 THEN v_total_amount := v_fixed_product_rate + (v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC);
215
v_fixed_product_rate, -- Taxable amount
167
ELSE RAISE EXCEPTION 'Invalid calculation type: %', v_calculation_type;
216
v_fixed_product_rate, -- Total amount
168
END CASE;
217
v_serial_number, -- Serial number
218
v_fixed_product_id
219
);
220
v_serial_number := v_serial_number + 1; -- Increment serial number
169
221
170
RAISE NOTICE ' [AMOUNT] calc_type=% total_amount=%', v_calculation_type, v_total_amount;
222
-- Prepare BHK rate detail for the second entry
223
v_product_rate := v_bhk_product_rate * (v_unit.bhk)::NUMERIC;
224
v_quantity := 1; -- Single entry for BHK calculation
225
v_product_id := v_bhk_product_id;
171
226
172
-- HEADER first (fixes FK)
227
-- Insert BHK Rate detail
173
CALL public.create_invoice_header(
228
CALL public.create_invoice_detail(
174
v_invoice_header_id,
229
v_invoice_header_id,
175
v_company_id,
230
v_product_rate, -- Product rate for BHK
176
v_unit.customer_id,
231
v_quantity, -- Quantity (1)
177
v_account_receivable_id,
232
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
178
v_sales_account_id,
233
v_product_rate, -- Taxable amount
179
v_invoice_date,
234
v_product_rate, -- Total amount
180
v_due_date,
235
v_serial_number, -- Serial number (2)
181
v_payment_term,
236
v_product_id -- Product ID for BHK
182
v_total_amount,
183
0,0,0,
184
v_total_amount,
185
0,0,0,
186
v_currency_id,
187
v_note,
188
v_invoice_status_id,
189
v_created_by,
190
'IVAP000',
191
'00000000-0000-0000-0000-000000000000',
192
'00000000-0000-0000-0000-000000000000',
193
'AP',
194
v_invoice_date,
195
3
196
);
237
);
197
RAISE NOTICE ' [HEADER CREATED] invoice_header_id=%', v_invoice_header_id;
198
238
199
-- DETAILS now
239
WHEN 5 THEN -- Fixed + SFT
200
CASE v_calculation_type
240
-- Total amount is a combination of fixed rate and SFT calculation
201
WHEN 1 THEN
241
v_total_amount := v_fixed_product_rate + (v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC);
202
v_product_rate := v_fixed_product_rate; v_quantity := 1; v_product_id := v_fixed_product_id;
203
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
204
RAISE NOTICE ' [DETAIL ADDED] fixed rate line';
205
242
206
WHEN 2 THEN
243
-- Insert Fixed rate detail
207
v_product_rate := v_bhk_product_rate * (v_unit.bhk)::NUMERIC; v_quantity := 1; v_product_id := v_bhk_product_id;
244
CALL public.create_invoice_detail(
208
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
245
v_invoice_header_id,
209
RAISE NOTICE ' [DETAIL ADDED] bhk line';
246
v_fixed_product_rate,
247
1, -- Quantity for fixed rate
248
0, 0, 0, 0, 0,
249
v_fixed_product_rate, -- Taxable amount
250
v_fixed_product_rate, -- Total amount
251
v_serial_number, -- Serial number
252
v_fixed_product_id
253
);
254
v_serial_number := v_serial_number + 1; -- Increment serial number
210
255
211
WHEN 3 THEN
256
-- Prepare SFT rate detail for the second entry
212
v_product_rate := v_sqft_product_rate; v_quantity := v_unit.sqft_area; v_product_id := v_sqft_product_id;
257
v_product_rate := v_sqft_product_rate; -- Per unit rate (rate per sqft)
213
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate*v_quantity, v_product_rate*v_quantity, v_serial_number, v_product_id);
258
v_quantity := v_unit.sqft_area; -- Use sqft_area as quantity
214
RAISE NOTICE ' [DETAIL ADDED] sqft line';
259
v_product_id := v_sqft_product_id;
215
260
216
WHEN 4 THEN
261
-- Insert SFT Rate detail
217
-- fixed
262
CALL public.create_invoice_detail(
218
v_product_rate := v_fixed_product_rate; v_quantity := 1; v_product_id := v_fixed_product_id;
263
v_invoice_header_id,
219
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
264
v_product_rate, -- Product rate per sqft
220
v_serial_number := v_serial_number + 1;
265
v_quantity, -- Quantity (sqft_area)
221
-- bhk
266
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
222
v_product_rate := v_bhk_product_rate * (v_unit.bhk)::NUMERIC; v_quantity := 1; v_product_id := v_bhk_product_id;
267
v_product_rate * v_quantity, -- Taxable amount (rate × quantity)
223
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
268
v_product_rate * v_quantity, -- Total amount (rate × quantity)
224
RAISE NOTICE ' [DETAIL ADDED] fixed + bhk lines';
269
v_serial_number, -- Serial number (2)
270
v_product_id -- Product ID for SFT
271
);
225
272
226
WHEN 5 THEN
273
ELSE
227
-- fixed
274
RAISE NOTICE 'Invalid calculation type: %', v_calculation_type;
228
v_product_rate := v_fixed_product_rate; v_quantity := 1; v_product_id := v_fixed_product_id;
275
RETURN;
229
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
230
v_serial_number := v_serial_number + 1;
231
-- sqft
232
v_product_rate := v_sqft_product_rate; v_quantity := v_unit.sqft_area; v_product_id := v_sqft_product_id;
233
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate*v_quantity, v_product_rate*v_quantity, v_serial_number, v_product_id);
234
RAISE NOTICE ' [DETAIL ADDED] fixed + sqft lines';
235
END CASE;
276
END CASE;
277
278
-- Crete invoice herader
279
CALL public.create_invoice_header(
280
v_invoice_header_id, -- p_id
281
v_company_id, -- p_company_id
282
v_unit.customer_id, -- p_customer_id
283
v_account_receivable_id, -- p_debit_account_id
284
v_sales_account_id, -- p_credit_account_id
285
v_invoice_date, -- p_invoice_date
286
v_due_date, -- p_due_date
287
v_payment_term, -- p_payment_term
288
v_total_amount, -- p_taxable_amount
289
0, -- p_cgst_amount (set as needed)
290
0, -- p_sgst_amount (set as needed)
291
0, -- p_igst_amount (set as needed)
292
v_total_amount, -- p_total_amount
293
0, -- p_discount
294
0, -- p_fees
295
0, -- p_round_off
296
v_currency_id, -- p_currency_id
297
v_note, -- p_note
298
v_invoice_status_id, -- p_invoice_status_id
299
v_created_by, -- p_created_by
300
'IVAP000', -- p_invoice_voucher
301
'00000000-0000-0000-0000-000000000000', -- p_source_warehouse_id
302
'00000000-0000-0000-0000-000000000000', -- p_destination_warehouse_id
303
'AP', -- p_so_no
304
v_invoice_date, -- p_so_date
305
3 -- p_type
306
);
236
307
237
v_status := 'success';
308
v_status := 'success';
238
v_success_count := v_success_count + 1;
309
v_success_count := v_success_count + 1;
239
310
311
-- Insert into group_invoice_details
240
INSERT INTO public.group_invoice_details(
312
INSERT INTO public.group_invoice_details(
241
id, group_invoice_header_id, unit_id, invoice_header_id, status, posted_on,
313
id, group_invoice_header_id, unit_id, invoice_header_id, status, posted_on, error_message, created_on_utc, created_by, company_id
242
error_message, created_on_utc, created_by, company_id
243
)
314
)
244
VALUES (
315
VALUES (
245
gen_random_uuid(), v_group_invoice_header_id, v_unit.unit_id, v_invoice_header_id, v_status, NOW(),
316
gen_random_uuid(), v_group_invoice_header_id, v_unit.unit_id, v_invoice_header_id, v_status, NOW(), v_error_message, NOW(), v_created_by, v_company_id
246
'', NOW(), v_created_by, v_company_id
247
);
317
);
248
318
249
RAISE NOTICE ' [UNIT DONE] unit_id=% status=%', v_unit.unit_id, v_status;
250
251
EXCEPTION
319
EXCEPTION
252
WHEN OTHERS THEN
320
WHEN OTHERS THEN
253
v_status := 'failed';
321
v_status := 'failed';
254
v_error_message := COALESCE(SQLERRM, 'Unknown error');
322
v_error_message := COALESCE(SQLERRM, 'Unknown error');
255
256
RAISE NOTICE ' [UNIT ERROR] unit_id=% sqlstate=% errmsg=%', v_unit.unit_id, SQLSTATE, v_error_message;
257
258
INSERT INTO public.group_invoice_details(
323
INSERT INTO public.group_invoice_details(
259
id, group_invoice_header_id, unit_id, status, invoice_header_id, error_message, posted_on,
324
id, group_invoice_header_id, unit_id, status, invoice_header_id, error_message, posted_on, created_on_utc, created_by, company_id
260
created_on_utc, created_by, company_id
261
)
325
)
262
VALUES (
326
VALUES (
263
gen_random_uuid(), v_group_invoice_header_id, v_unit.unit_id, v_status, v_invoice_header_id,
327
gen_random_uuid(), v_group_invoice_header_id, v_unit.unit_id, v_status, v_invoice_header_id, v_error_message, NOW(), NOW(), v_created_by, v_company_id
264
v_error_message, NOW(), NOW(), v_created_by, v_company_id
265
);
328
);
266
END;
329
END;
267
END LOOP;
330
END LOOP;
268
331
332
-- Update group_invoice_headers with final status
269
UPDATE public.group_invoice_headers
333
UPDATE public.group_invoice_headers
270
SET success_count = v_success_count,
334
SET success_count = v_success_count,
271
total_count = v_total_count,
335
total_count = v_total_count,
272
modified_on_utc = NOW(),
336
modified_on_utc = NOW(),
273
modified_by = v_created_by,
337
modified_by = v_created_by,
274
error_message = COALESCE(v_error_message, '')
338
error_message = COALESCE(v_error_message, '')
275
WHERE id = v_group_invoice_header_id;
339
WHERE id = v_group_invoice_header_id;
276
340
277
RAISE NOTICE '[GROUP DONE] group_header_id=% success=% total=%', v_group_invoice_header_id, v_success_count, v_total_count;
341
-- Update last executed timestamp for the schedule
278
279
UPDATE batch_schedules
342
UPDATE batch_schedules
280
SET last_executed_at = NOW()
343
SET last_executed_at = NOW()
281
WHERE id = schedule_record.id;
344
WHERE id = schedule_record.id;
282
283
RAISE NOTICE '[SCHEDULE DONE] schedule_id=% last_executed_at updated', schedule_record.id;
284
END LOOP;
345
END LOOP;
285
286
RAISE NOTICE '[END] run_grouped_invoices(template_id=%)', p_template_id;
287
END;
346
END;
288
$procedure$
347
$procedure$
|
|||||
| Procedure | run_sales_invoice_schedule | Match | ||||||
| Procedure | save_company_prefix_for_invoice_and_payment | Match | ||||||
| Procedure | transfer_draft_to_invoice | Match | ||||||
| Procedure | update_account_level_approval_configuration | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_account_level_approval_configuration(IN p_company_id uuid, IN p_account_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
1
CREATE OR REPLACE PROCEDURE public.update_account_level_approval_configuration(IN p_company_id uuid, IN p_account_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
4
DECLARE
5
DECLARE
5
approval_item JSONB;
6
approval_item JSONB;
6
existing_id INT;
7
existing_id INT;
7
BEGIN
8
BEGIN
8
-- 1️⃣ Soft delete old records not present in incoming approvals:
9
-- 1️⃣ Soft delete old records not present in incoming approvals:
9
UPDATE invoice_approval_users_account
10
UPDATE invoice_approval_users_account
10
SET is_deleted = TRUE,
11
SET is_deleted = TRUE,
11
deleted_on_utc = NOW(),
12
deleted_on_utc = NOW(),
12
modified_on_utc = NOW(),
13
modified_on_utc = NOW(),
13
modified_by = p_modified_by
14
modified_by = p_modified_by
14
WHERE company_id = p_company_id
15
WHERE company_id = p_company_id
15
AND account_id = p_account_id
16
AND account_id = p_account_id
16
AND NOT EXISTS (
17
AND NOT EXISTS (
17
SELECT 1
18
SELECT 1
18
FROM jsonb_array_elements(p_approvals) AS elem
19
FROM jsonb_array_elements(p_approvals) AS elem
19
WHERE (elem->>'userId')::UUID = invoice_approval_users_account.user_id
20
WHERE (elem->>'userId')::UUID = invoice_approval_users_account.user_id
20
AND (elem->>'approvalLevel')::INT = invoice_approval_users_account.approval_level
21
AND (elem->>'approvalLevel')::INT = invoice_approval_users_account.approval_level
21
);
22
);
22
23
23
-- 2️⃣ Upsert incoming approvals:
24
-- 2️⃣ Upsert incoming approvals:
24
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
25
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
25
LOOP
26
LOOP
26
SELECT id INTO existing_id
27
SELECT id INTO existing_id
27
FROM invoice_approval_users_account
28
FROM invoice_approval_users_account
28
WHERE company_id = p_company_id
29
WHERE company_id = p_company_id
29
AND account_id = p_account_id
30
AND account_id = p_account_id
30
AND user_id = (approval_item->>'userId')::UUID
31
AND user_id = (approval_item->>'userId')::UUID
31
AND approval_level = (approval_item->>'approvalLevel')::INT
32
AND approval_level = (approval_item->>'approvalLevel')::INT
32
AND is_deleted = FALSE
33
AND is_deleted = FALSE
33
LIMIT 1;
34
LIMIT 1;
34
35
35
IF existing_id IS NOT NULL THEN
36
IF existing_id IS NOT NULL THEN
36
UPDATE invoice_approval_users_account
37
UPDATE invoice_approval_users_account
37
SET status_id = (approval_item->>'statusId')::INT,
38
SET status_id = (approval_item->>'statusId')::INT,
38
modified_on_utc = NOW(),
39
modified_on_utc = NOW(),
39
modified_by = p_modified_by
40
modified_by = p_modified_by
40
WHERE id = existing_id;
41
WHERE id = existing_id;
41
ELSE
42
ELSE
42
INSERT INTO invoice_approval_users_account (
43
INSERT INTO invoice_approval_users_account (
43
company_id,
44
company_id,
44
account_id,
45
account_id,
45
status_id,
46
status_id,
46
user_id,
47
user_id,
47
approval_level,
48
approval_level,
48
is_deleted,
49
is_deleted,
49
created_on_utc,
50
created_on_utc,
50
created_by
51
created_by
51
)
52
)
52
VALUES (
53
VALUES (
53
p_company_id,
54
p_company_id,
54
p_account_id,
55
p_account_id,
55
(approval_item->>'statusId')::INT,
56
(approval_item->>'statusId')::INT,
56
(approval_item->>'userId')::UUID,
57
(approval_item->>'userId')::UUID,
57
(approval_item->>'approvalLevel')::INT,
58
(approval_item->>'approvalLevel')::INT,
58
FALSE,
59
FALSE,
59
NOW(),
60
NOW(),
60
p_modified_by
61
p_modified_by
61
);
62
);
62
END IF;
63
END IF;
63
END LOOP;
64
END LOOP;
64
65
65
-- 3️⃣ Upsert invoice_account_approval_levels:
66
-- 3️⃣ Upsert invoice_account_approval_levels:
66
IF EXISTS (
67
IF EXISTS (
67
SELECT 1
68
SELECT 1
68
FROM invoice_account_approval_levels
69
FROM invoice_account_approval_levels
69
WHERE company_id = p_company_id
70
WHERE company_id = p_company_id
70
AND account_id = p_account_id
71
AND account_id = p_account_id
71
AND status_id = 2 -- temparary updating only for status 2
72
AND status_id = p_status_id
72
) THEN
73
) THEN
73
UPDATE invoice_account_approval_levels
74
UPDATE invoice_account_approval_levels
74
SET approval_level_required = p_required_approval_levels,
75
SET approval_level_required = p_required_approval_levels,
75
status_id = 2,
76
modified_on_utc = NOW(),
76
modified_on_utc = NOW(),
77
modified_by = p_modified_by
77
modified_by = p_modified_by
78
WHERE company_id = p_company_id
78
WHERE company_id = p_company_id
79
AND account_id = p_account_id;
79
AND account_id = p_account_id
80
--AND status_id = p_status_id;
80
AND status_id = p_status_id;
81
ELSE
81
ELSE
82
INSERT INTO invoice_account_approval_levels (
82
INSERT INTO invoice_account_approval_levels (
83
company_id,
83
company_id,
84
account_id,
84
account_id,
85
status_id,
85
status_id,
86
approval_level_required,
86
approval_level_required,
87
created_on_utc,
87
created_on_utc,
88
created_by
88
created_by
89
)
89
)
90
VALUES (
90
VALUES (
91
p_company_id,
91
p_company_id,
92
p_account_id,
92
p_account_id,
93
2,
93
p_status_id,
94
p_required_approval_levels,
94
p_required_approval_levels,
95
NOW(),
95
NOW(),
96
p_modified_by
96
p_modified_by
97
);
97
);
98
END IF;
98
END IF;
99
99
100
END;
100
END;
101
$procedure$
101
$procedure$
|
|||||
| Procedure | update_apartment_invoice_template | Match | ||||||
| Procedure | update_company_level_approval_configuration | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_company_level_approval_configuration(IN p_company_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
1
CREATE OR REPLACE PROCEDURE public.update_company_level_approval_configuration(IN p_company_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
4
5
DECLARE
5
DECLARE
6
approval_item JSONB;
6
approval_item JSONB;
7
existing_id INT;
7
existing_id INT;
8
BEGIN
8
BEGIN
9
-- 1️⃣ Soft delete old records not present in incoming approvals:
9
-- 1️⃣ Soft delete old records not present in incoming approvals:
10
UPDATE invoice_approval_user_company
10
UPDATE invoice_approval_user_company
11
SET is_deleted = TRUE,
11
SET is_deleted = TRUE,
12
deleted_on_utc = NOW(),
12
deleted_on_utc = NOW(),
13
modified_on_utc = NOW(),
13
modified_on_utc = NOW(),
14
modified_by = p_modified_by
14
modified_by = p_modified_by
15
WHERE company_id = p_company_id
15
WHERE company_id = p_company_id
16
AND NOT EXISTS (
16
AND NOT EXISTS (
17
SELECT 1
17
SELECT 1
18
FROM jsonb_array_elements(p_approvals) AS elem
18
FROM jsonb_array_elements(p_approvals) AS elem
19
WHERE (elem->>'userId')::UUID = invoice_approval_user_company.user_id
19
WHERE (elem->>'userId')::UUID = invoice_approval_user_company.user_id
20
AND (elem->>'statusId')::INT = invoice_approval_user_company.status_id
21
AND (elem->>'approvalLevel')::INT = invoice_approval_user_company.approval_level
20
AND (elem->>'approvalLevel')::INT = invoice_approval_user_company.approval_level
22
);
21
);
23
22
24
-- 2️⃣ Upsert incoming approvals:
23
-- 2️⃣ Upsert incoming approvals:
25
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
24
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
26
LOOP
25
LOOP
27
SELECT id INTO existing_id
26
SELECT id INTO existing_id
28
FROM invoice_approval_user_company
27
FROM invoice_approval_user_company
29
WHERE company_id = p_company_id
28
WHERE company_id = p_company_id
30
AND user_id = (approval_item->>'userId')::UUID
29
AND user_id = (approval_item->>'userId')::UUID
31
AND status_id = (approval_item->>'statusId')::INT
32
AND approval_level = (approval_item->>'approvalLevel')::INT
30
AND approval_level = (approval_item->>'approvalLevel')::INT
33
AND is_deleted = FALSE
31
AND is_deleted = FALSE
34
LIMIT 1;
32
LIMIT 1;
35
33
36
IF existing_id IS NOT NULL THEN
34
IF existing_id IS NOT NULL THEN
37
UPDATE invoice_approval_user_company
35
UPDATE invoice_approval_user_company
38
SET status_id = (approval_item->>'statusId')::INT,
36
SET status_id = (approval_item->>'statusId')::INT,
39
modified_on_utc = NOW(),
37
modified_on_utc = NOW(),
40
modified_by = p_modified_by
38
modified_by = p_modified_by
41
WHERE id = existing_id;
39
WHERE id = existing_id;
42
ELSE
40
ELSE
43
INSERT INTO invoice_approval_user_company (
41
INSERT INTO invoice_approval_user_company (
44
company_id,
42
company_id,
45
status_id,
43
status_id,
46
user_id,
44
user_id,
47
approval_level,
45
approval_level,
48
is_deleted,
46
is_deleted,
49
created_on_utc,
47
created_on_utc,
50
created_by
48
created_by
51
)
49
)
52
VALUES (
50
VALUES (
53
p_company_id,
51
p_company_id,
54
(approval_item->>'statusId')::INT,
52
(approval_item->>'statusId')::INT,
55
(approval_item->>'userId')::UUID,
53
(approval_item->>'userId')::UUID,
56
(approval_item->>'approvalLevel')::INT,
54
(approval_item->>'approvalLevel')::INT,
57
FALSE,
55
FALSE,
58
NOW(),
56
NOW(),
59
p_modified_by
57
p_modified_by
60
);
58
);
61
END IF;
59
END IF;
62
END LOOP;
60
END LOOP;
63
61
64
-- 3️⃣ Update workflow approval level:
62
-- 3️⃣ Update workflow approval level:
65
UPDATE invoice_workflow
63
UPDATE invoice_workflow
66
SET approval_level = p_required_approval_levels
64
SET approval_level = p_required_approval_levels
67
WHERE company_id = p_company_id
65
WHERE company_id = p_company_id
68
-- AND status = p_status_id; -- 🔔 use p_status_id here unless deliberately hardcoded as 2.
66
AND status = p_status_id; -- 🔔 use p_status_id here unless deliberately hardcoded as 2.
69
AND status = 2; -- 🔔 use p_status_id here unless deliberately hardcoded as 2.
70
67
71
END;
68
END;
72
$procedure$
69
$procedure$
|
|||||
| Procedure | update_draft_invoice_next_status | Match | ||||||
| Procedure | update_draft_invoice_next_status | Match | ||||||
| Procedure | update_invoice_approval_user | Match | ||||||
| Procedure | update_invoice_company_approval_user | Match | ||||||
| Procedure | upsert_invoice_status_company_config_json | Match | ||||||
| Procedure | update_invoice_next_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_invoice_next_status(IN p_company_id uuid, IN p_invoice_status_id integer, IN p_invoice_ids uuid[], IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_draft_invoice RECORD;
6
v_next_status integer;
7
v_account_id uuid;
8
v_has_account_workflow BOOLEAN;
9
v_has_user_account_level_approval BOOLEAN;
10
v_has_user_company_level_approval BOOLEAN;
11
v_is_data_available_for_company BOOLEAN;
12
APPROVED_STATUS CONSTANT INTEGER := 3;
13
FIRST_LEVEL_APPROVAL CONSTANT INTEGER := 8;
14
SECOND_LEVEL_APPROVAL CONSTANT INTEGER := 9;
15
BEGIN
16
FOR v_draft_invoice IN
17
SELECT id, invoice_status_id,credit_account_id
18
FROM public.draft_invoice_headers
19
WHERE id = ANY(p_invoice_ids)
20
LOOP
21
v_account_id := v_draft_invoice.credit_account_id;
22
23
SELECT EXISTS (
24
SELECT 1 FROM public.invoice_account_workflows
25
WHERE company_id = p_company_id
26
AND account_id = v_account_id
27
AND status = v_draft_invoice.invoice_status_id
28
AND is_deleted = false
29
) INTO v_has_account_workflow;
30
31
IF v_has_account_workflow THEN
32
33
SELECT next_status INTO v_next_status
34
FROM public.invoice_account_workflows
35
WHERE company_id = p_company_id
36
AND account_id = v_account_id
37
AND status = v_draft_invoice.invoice_status_id
38
AND is_deleted = false
39
LIMIT 1;
40
41
SELECT EXISTS (
42
SELECT 1 FROM public.invoice_account_approval_users
43
WHERE company_id = p_company_id
44
AND account_id = v_account_id
45
AND status = v_next_status
46
AND user_id = p_modified_by
47
) INTO v_has_user_account_level_approval;
48
49
IF NOT v_has_user_account_level_approval THEN
50
RAISE EXCEPTION 'User % is not authorized to approve at this level for account % ', p_modified_by, v_account_id ;
51
EXIT;
52
END IF;
53
54
ELSE
55
v_next_status := p_invoice_status_id;
56
57
SELECT EXISTS (
58
SELECT 1 FROM public.invoice_approvals
59
WHERE company_id = p_company_id
60
) INTO v_is_data_available_for_company;
61
62
IF v_is_data_available_for_company THEN
63
64
SELECT EXISTS (
65
SELECT 1 FROM public.invoice_approvals
66
WHERE company_id = p_company_id
67
AND status_id = v_next_status
68
AND user_id = p_modified_by
69
) INTO v_has_user_company_level_approval;
70
71
IF NOT v_has_user_company_level_approval THEN
72
RAISE EXCEPTION 'User % is not authorized to approve at this level', p_modified_by;
73
EXIT;
74
END IF;
75
76
END IF;
77
78
END IF;
79
80
81
UPDATE public.draft_invoice_headers
82
SET invoice_status_id = v_next_status,
83
modified_by = p_modified_by,
84
modified_on_utc = now()
85
WHERE id = v_draft_invoice.id;
86
87
IF v_next_status = APPROVED_STATUS OR v_next_status = FIRST_LEVEL_APPROVAL OR v_next_status = SECOND_LEVEL_APPROVAL THEN
88
INSERT INTO public.invoice_approval_logs(
89
invoice_id,
90
status_id,
91
approved_by,
92
approved_on,
93
"comment",
94
created_on_utc,
95
created_by
96
)
97
VALUES(
98
v_draft_invoice.id,
99
v_next_status,
100
p_modified_by,
101
now(),
102
CASE
103
WHEN v_next_status = FIRST_LEVEL_APPROVAL THEN 'Level 1 Approval'
104
WHEN v_next_status = SECOND_LEVEL_APPROVAL THEN 'Level 2 Approval'
105
WHEN v_next_status = APPROVED_STATUS THEN 'Final Approval'
106
ELSE 'Status updated'
107
END,
108
now(),
109
p_modified_by
110
);
111
END IF;
112
113
IF p_invoice_status_id = APPROVED_STATUS THEN
114
CALL transfer_draft_to_invoice(v_draft_invoice.id, p_modified_by);
115
116
RAISE NOTICE 'Draft Invoice Approved. Transferring to Final Invoice: %', v_draft_invoice.id;
117
END IF;
118
END LOOP;
119
END
120
$procedure$
|
|||||
| Procedure | update_invoice_next_status_for_invoice_header | Match | ||||||
| Procedure | update_invoice_next_status_for_invoice_header | Match | ||||||
| Procedure | update_invoice_previous_status | Match | ||||||
| Procedure | clean_up_org_sales | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.clean_up_org_sales(IN p_organization_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_company_id uuid;
6
v_customer_id uuid;
7
v_customer_note_header_id uuid;
8
v_invoice_header_id uuid;
9
v_invoice_payment_header_id uuid;
10
v_draft_invoice_header_id uuid;
11
v_group_invoice_header_id uuid;
12
v_gate_pass_header_id uuid;
13
v_user_id uuid;
14
BEGIN
15
FOR v_company_id IN
16
SELECT id FROM companies WHERE organization_id = p_organization_id
17
LOOP
18
-- Customers and all their detail tables
19
FOR v_customer_id IN SELECT id FROM customers WHERE company_id = v_company_id LOOP
20
DELETE FROM customer_contacts WHERE customer_id = v_customer_id;
21
DELETE FROM customer_bank_accounts WHERE customer_id = v_customer_id;
22
DELETE FROM customer_upis WHERE customer_id = v_customer_id;
23
DELETE FROM customer_default_accounts WHERE customer_id = v_customer_id;
24
FOR v_customer_note_header_id IN SELECT id FROM customer_note_headers WHERE customer_id = v_customer_id LOOP
25
DELETE FROM customer_note_details WHERE customer_note_header_id = v_customer_note_header_id;
26
END LOOP;
27
DELETE FROM customer_note_headers WHERE customer_id = v_customer_id;
28
END LOOP;
29
DELETE FROM customers WHERE company_id = v_company_id;
30
DELETE FROM customer_note_statuses WHERE created_by = v_company_id OR modified_by = v_company_id;
31
DELETE FROM customer_note_work_flows WHERE company_id = v_company_id;
32
33
-- Draft Invoices and their details
34
FOR v_draft_invoice_header_id IN SELECT id FROM draft_invoice_headers WHERE company_id = v_company_id LOOP
35
DELETE FROM draft_invoice_details WHERE invoice_header_id = v_draft_invoice_header_id;
36
END LOOP;
37
DELETE FROM draft_invoice_headers WHERE company_id = v_company_id;
38
DELETE FROM draft_invoice_header_ids WHERE company_id = v_company_id;
39
40
-- Group Invoices and their details/templates
41
FOR v_group_invoice_header_id IN SELECT id FROM group_invoice_headers WHERE company_id = v_company_id LOOP
42
DELETE FROM group_invoice_details WHERE group_invoice_header_id = v_group_invoice_header_id;
43
END LOOP;
44
DELETE FROM group_invoice_headers WHERE company_id = v_company_id;
45
DELETE FROM group_invoice_header_ids WHERE company_id = v_company_id;
46
DELETE FROM group_invoice_templates WHERE company_id = v_company_id;
47
DELETE FROM group_invoice_template_customers WHERE customer_id IN (
48
SELECT id FROM customers WHERE company_id = v_company_id
49
);
50
51
-- Invoice Headers and all their details
52
FOR v_invoice_header_id IN SELECT id FROM invoice_headers WHERE company_id = v_company_id LOOP
53
DELETE FROM invoice_details WHERE invoice_header_id = v_invoice_header_id;
54
DELETE FROM invoice_approval_logs WHERE invoice_id = v_invoice_header_id;
55
DELETE FROM invoice_approval_issue_log WHERE invoice_id = v_invoice_header_id;
56
DELETE FROM invoice_penalties WHERE invoice_header_id = v_invoice_header_id;
57
DELETE FROM penalty_processing_logs WHERE invoice_id = v_invoice_header_id;
58
DELETE FROM recurring_sales_schedules WHERE invoice_header_id = v_invoice_header_id;
59
DELETE FROM group_invoice_details WHERE invoice_header_id = v_invoice_header_id;
60
END LOOP;
61
DELETE FROM invoice_headers WHERE company_id = v_company_id;
62
DELETE FROM invoice_header_ids WHERE company_id = v_company_id;
63
64
-- Invoice payments and their details
65
FOR v_invoice_payment_header_id IN SELECT id FROM invoice_payment_headers WHERE company_id = v_company_id LOOP
66
DELETE FROM invoice_payment_details WHERE invoice_payment_header_id = v_invoice_payment_header_id;
67
END LOOP;
68
DELETE FROM invoice_payment_headers WHERE company_id = v_company_id;
69
DELETE FROM invoice_payment_header_ids WHERE company_id = v_company_id;
70
71
-- Invoice workflow/configs
72
DELETE FROM invoice_workflow WHERE company_id = v_company_id;
73
DELETE FROM invoice_status_company_configs WHERE company_id = v_company_id;
74
DELETE FROM invoice_account_approval_levels WHERE company_id = v_company_id;
75
DELETE FROM invoice_approval_users_account WHERE company_id = v_company_id;
76
DELETE FROM invoice_approval_user_company WHERE company_id = v_company_id;
77
78
-- Invoice voucher ids
79
DELETE FROM invoice_voucher_ids WHERE company_id = v_company_id;
80
81
-- Penalties and configs
82
DELETE FROM penalty_configs WHERE company_id = v_company_id;
83
DELETE FROM penalty_frequencies WHERE created_by = v_company_id OR modified_by = v_company_id;
84
85
-- Warehouses (corrected)
86
DELETE FROM warehouses WHERE customer_id IN (
87
SELECT id FROM customers WHERE company_id = v_company_id
88
);
89
90
-- Gate pass and their details
91
FOR v_gate_pass_header_id IN SELECT id FROM gate_pass_headers WHERE company_id = v_company_id LOOP
92
DELETE FROM gate_pass_details WHERE gate_pass_header_id = v_gate_pass_header_id;
93
END LOOP;
94
DELETE FROM gate_pass_headers WHERE company_id = v_company_id;
95
96
-- Users and user roles
97
FOR v_user_id IN SELECT id FROM users WHERE company_id = v_company_id LOOP
98
DELETE FROM user_roles WHERE user_id = v_user_id;
99
END LOOP;
100
DELETE FROM users WHERE company_id = v_company_id;
101
102
-- Finally, company itself
103
DELETE FROM companies WHERE id = v_company_id;
104
END LOOP;
105
106
-- Organization record itself
107
DELETE FROM organizations WHERE id = p_organization_id;
108
109
RAISE NOTICE 'Organization % and all related sales data deleted.', p_organization_id;
110
END;
111
$procedure$
|
|||||
| Procedure | copy_draft_invoices | Match | ||||||
| Procedure | copy_invoices | Match | ||||||
| Procedure | create_invoice_detail | Match | ||||||
| Procedure | create_invoice_header | Match | ||||||
| Procedure | edit_draft_invoice | Match | ||||||
| Procedure | handle_invoice_update | Match | ||||||
| Procedure | initialize_draft_invoice_header_ids | Match | ||||||
| Procedure | initialize_invoice_payment_header_ids | Match | ||||||
| Procedure | initialize_organization | Match | ||||||
| Procedure | update_bill_next_status_for_bill_header | Match | ||||||
| Procedure | update_invoice_account_approval_user | Match | ||||||
| Procedure | update_invoice_header | Match | ||||||
| Procedure | via_excel_grouped_invoices | Match | ||||||
| Procedure | purge_sales_organization_data | Match | ||||||
| Procedure | close_resolved_delinquency_cycles | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.close_resolved_delinquency_cycles(IN p_organization_id uuid, IN p_company_ids uuid[], IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
CYCLE_STATUS_CLOSE CONSTANT int := 2;
6
BEGIN
7
UPDATE delinquency_cycles dc
8
SET
9
ended_on_utc = CURRENT_TIMESTAMP,
10
status_id = CYCLE_STATUS_CLOSE, -- CLOSED
11
modified_on_utc = CURRENT_TIMESTAMP,
12
modified_by = p_modified_by
13
WHERE dc.organization_id = p_organization_id
14
AND dc.ended_on_utc IS NULL
15
AND dc.is_deleted = false
16
AND NOT EXISTS (
17
SELECT 1
18
FROM invoice_headers ih
19
WHERE ih.company_id = ANY (p_company_ids)
20
AND ih.customer_id = dc.customer_id
21
AND ih.is_deleted = false
22
AND (ih.total_amount - ih.settled_amount) > 0
23
);
24
END;
25
$procedure$
|
|||||
| Procedure | schedule_invoice | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.schedule_invoice()
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
templateRecord RECORD;
6
executionDate DATE;
7
nextExecutionDate DATE;
8
newDraftInvoiceId UUID;
9
invoiceHeaderRecord RECORD;
10
invoiceDetailRecord RECORD;
11
BEGIN
12
-- Loop through all active templates in the invoice_template table
13
FOR templateRecord IN
14
SELECT *
15
FROM public.invoice_template
16
WHERE is_deleted = false
17
AND starts_from <= CURRENT_DATE
18
AND end_date >= CURRENT_DATE
19
LOOP
20
-- Initialize executionDate to the start date of the template
21
executionDate := templateRecord.starts_from;
22
23
-- Fetch data from invoice_headers or draft_invoice_headers based on invoiceHeaderId
24
SELECT * INTO invoiceHeaderRecord
25
FROM public.invoice_headers
26
WHERE id = templateRecord.invoice_header_id AND is_deleted = false;
27
28
IF NOT FOUND THEN
29
-- If not found in invoice_headers, check in draft_invoice_headers
30
SELECT * INTO invoiceHeaderRecord
31
FROM public.draft_invoice_headers
32
WHERE id = templateRecord.invoice_header_id AND is_deleted = false;
33
END IF;
34
35
-- Loop through the date range to create execution dates
36
WHILE executionDate <= templateRecord.end_date LOOP
37
-- Calculate the next execution date based on frequency
38
CASE templateRecord.frequency_cycle
39
WHEN 'daily' THEN
40
nextExecutionDate := executionDate + INTERVAL '1 day';
41
WHEN 'weekly' THEN
42
nextExecutionDate := executionDate + INTERVAL '1 week';
43
WHEN 'monthly' THEN
44
nextExecutionDate := executionDate + INTERVAL '1 month';
45
WHEN 'yearly' THEN
46
nextExecutionDate := executionDate + INTERVAL '1 year';
47
ELSE
48
RAISE EXCEPTION 'Invalid frequency cycle: %', templateRecord.frequency_cycle;
49
END CASE;
50
51
-- Create a new draft invoice
52
newDraftInvoiceId := gen_random_uuid();
53
CALL public.create_draft_invoice_header(
54
newDraftInvoiceId,
55
invoiceHeaderRecord.company_id,
56
invoiceHeaderRecord.customer_id,
57
invoiceHeaderRecord.debit_account_id,
58
invoiceHeaderRecord.credit_account_id,
59
executionDate,
60
executionDate + INTERVAL '30 days', -- Set due date
61
invoiceHeaderRecord.payment_term,
62
invoiceHeaderRecord.taxable_amount,
63
invoiceHeaderRecord.cgst_amount,
64
invoiceHeaderRecord.sgst_amount,
65
invoiceHeaderRecord.igst_amount,
66
invoiceHeaderRecord.total_amount,
67
invoiceHeaderRecord.discount,
68
invoiceHeaderRecord.fees,
69
invoiceHeaderRecord.round_off,
70
invoiceHeaderRecord.currency_id,
71
invoiceHeaderRecord.note,
72
1, -- Set initial status to 'draft'
73
invoiceHeaderRecord.created_by,
74
invoiceHeaderRecord.invoice_voucher,
75
invoiceHeaderRecord.source_warehouse_id,
76
invoiceHeaderRecord.destination_warehouse_id,
77
invoiceHeaderRecord.so_no,
78
invoiceHeaderRecord.so_date,
79
invoiceHeaderRecord.type
80
);
81
82
-- Insert related draft invoice details
83
FOR invoiceDetailRecord IN
84
SELECT * FROM public.invoice_details
85
WHERE invoice_header_id = templateRecord.invoice_header_id AND is_deleted = false
86
LOOP
87
CALL public.create_draft_invoice_detail(
88
newDraftInvoiceId,
89
invoiceDetailRecord.price,
90
invoiceDetailRecord.quantity,
91
invoiceDetailRecord.discount,
92
invoiceDetailRecord.fees,
93
invoiceDetailRecord.cgst_amount,
94
invoiceDetailRecord.igst_amount,
95
invoiceDetailRecord.sgst_amount,
96
invoiceDetailRecord.taxable_amount,
97
invoiceDetailRecord.total_amount,
98
invoiceDetailRecord.serial_number,
99
invoiceDetailRecord.product_id
100
);
101
END LOOP;
102
103
-- Insert into apartment_invoice_template_executions
104
INSERT INTO public.apartment_invoice_template_executions (
105
id, template_id, execution_date, error_message, success_count,
106
total_count, created_on_utc, created_by, is_deleted
107
)
108
VALUES (
109
gen_random_uuid(), templateRecord.id, executionDate, '', 1, 1,
110
NOW(), invoiceHeaderRecord.created_by, false
111
);
112
113
-- Update executionDate for the next iteration
114
executionDate := nextExecutionDate;
115
END LOOP;
116
END LOOP;
117
118
RAISE NOTICE 'Invoice scheduling completed successfully.';
119
END;
120
$procedure$
|
|||||
| Procedure | test_run_batches | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.test_run_batches()
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
schedule_record RECORD;
6
v_unit RECORD;
7
v_execution_id UUID;
8
v_invoice_header_id UUID;
9
v_total_amount NUMERIC;
10
v_status TEXT;
11
v_error_message TEXT;
12
v_total_count INTEGER;
13
v_success_count INTEGER;
14
v_calculation_type INTEGER;
15
v_company_id UUID;
16
v_sales_account_id UUID;
17
v_account_receivable_id UUID;
18
v_invoice_date DATE;
19
v_starts_from DATE;
20
v_end_date DATE;
21
v_due_date DATE;
22
v_payment_term INTEGER;
23
v_currency_id INTEGER;
24
v_fixed_product_id UUID;
25
v_bhk_product_id UUID;
26
v_sqft_product_id UUID;
27
v_fixed_product_rate NUMERIC;
28
v_bhk_product_rate NUMERIC;
29
v_sqft_product_rate NUMERIC;
30
v_note TEXT;
31
v_invoice_status_id INTEGER;
32
v_due_days INTEGER;
33
v_billing_cycle TEXT;
34
v_half_year INTEGER;
35
v_quarters_of_month INTEGER;
36
v_bi_month INTEGER;
37
v_day_of_week INTEGER;
38
v_day_of_month INTEGER;
39
v_month_of_year INTEGER;
40
v_time_of_day INTERVAL;
41
v_created_by UUID;
42
v_time_as_time TIME;
43
44
BEGIN
45
-- Loop through all non-deleted schedules that should run today
46
FOR schedule_record IN
47
SELECT *
48
FROM batch_schedules
49
WHERE is_deleted = FALSE
50
AND template_id IN (
51
SELECT id
52
FROM public.apartment_invoice_templates
53
WHERE is_deleted = FALSE
54
AND (starts_from <= CURRENT_DATE
55
AND (end_date IS NULL OR end_date >= CURRENT_DATE))
56
)
57
AND last_executed_at IS DISTINCT FROM CURRENT_DATE::timestamp
58
LOOP
59
-- Assign values from schedule_record
60
v_half_year := schedule_record.half_year;
61
v_quarters_of_month := schedule_record.quarters_of_month;
62
v_bi_month := schedule_record.bi_month;
63
v_day_of_week := schedule_record.day_of_week;
64
v_day_of_month := schedule_record.day_of_month;
65
v_month_of_year := schedule_record.month_of_year;
66
v_time_of_day := schedule_record.time_of_day;
67
68
-- Convert v_time_of_day interval to time by casting extracted values to integer
69
v_time_as_time := make_time(
70
EXTRACT(HOUR FROM v_time_of_day)::INTEGER,
71
EXTRACT(MINUTE FROM v_time_of_day)::INTEGER,
72
EXTRACT(SECOND FROM v_time_of_day)::INTEGER
73
);
74
75
-- Log important information before entering the IF block
76
RAISE NOTICE 'Checking if batch should run for template_id: %', schedule_record.template_id;
77
78
-- Process based on billing cycle and check if current time matches the set time
79
IF (
80
-- Daily schedules
81
schedule_record.billing_cycle = 'Daily'
82
OR (schedule_record.billing_cycle = 'Weekly' AND v_day_of_week = EXTRACT(DOW FROM CURRENT_DATE))
83
OR (schedule_record.billing_cycle = 'Monthly' AND v_day_of_month = EXTRACT(DAY FROM CURRENT_DATE) AND CURRENT_DATE <= v_end_date)
84
OR (schedule_record.billing_cycle = 'Bi-Monthly' AND v_bi_month = CEIL(EXTRACT(MONTH FROM CURRENT_DATE) / 2) AND CURRENT_DATE <= v_end_date)
85
OR (schedule_record.billing_cycle = 'Quarterly' AND v_quarters_of_month = CEIL(EXTRACT(MONTH FROM CURRENT_DATE) / 3) AND CURRENT_DATE <= v_end_date)
86
OR (schedule_record.billing_cycle = 'Half-yearly' AND v_half_year = CASE WHEN EXTRACT(MONTH FROM CURRENT_DATE) <= 6 THEN 1 ELSE 2 END AND CURRENT_DATE <= v_end_date)
87
OR (schedule_record.billing_cycle = 'Yearly' AND v_month_of_year = EXTRACT(MONTH FROM CURRENT_DATE) AND CURRENT_DATE <= v_end_date)
88
)
89
AND CURRENT_TIME >= v_time_as_time THEN
90
-- Log the execution details
91
RAISE NOTICE 'Batch should run for template_id: % at time: %', schedule_record.template_id, v_time_as_time;
92
93
-- Begin processing the batch for this template
94
v_execution_id := gen_random_uuid();
95
v_total_count := 0;
96
v_success_count := 0;
97
v_status := 'pending';
98
v_error_message := '';
99
100
-- Insert into apartment_invoice_template_execution
101
BEGIN
102
INSERT INTO public.apartment_invoice_template_executions (
103
id, template_id, execution_date, success_count, created_on_utc, created_by, total_count, error_message
104
)
105
VALUES (
106
v_execution_id, schedule_record.template_id, NOW(), v_success_count, NOW(), v_created_by, v_total_count, v_error_message
107
);
108
RAISE NOTICE 'Inserted into apartment_invoice_template_executions for template_id: % with execution_id: %', schedule_record.template_id, v_execution_id;
109
EXCEPTION
110
WHEN OTHERS THEN
111
RAISE NOTICE 'Error inserting into apartment_invoice_template_executions for template_id: % - Error: %', schedule_record.template_id, SQLERRM;
112
END;
113
114
-- Select Status from the invoice workflow
115
SELECT MIN(status)
116
INTO v_invoice_status_id
117
FROM public.invoice_workflow
118
WHERE company_id = v_company_id
119
AND is_deleted = FALSE;
120
121
-- Loop through units associated with the template
122
FOR v_unit IN
123
SELECT unit_id, bhk, sqft_area, customer_id
124
FROM public.apartment_invoice_template_units
125
WHERE template_id = schedule_record.template_id
126
LOOP
127
BEGIN
128
v_total_count := v_total_count + 1;
129
v_invoice_header_id := gen_random_uuid();
130
v_total_amount := 0;
131
132
-- Calculate the total amount based on the calculation type
133
CASE v_calculation_type
134
WHEN 1 THEN -- Fixed method
135
v_total_amount := v_fixed_product_rate;
136
WHEN 2 THEN -- BHK
137
v_total_amount := v_bhk_product_rate * (v_unit.bhk)::NUMERIC;
138
WHEN 3 THEN -- SFT
139
v_total_amount := v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC;
140
WHEN 4 THEN -- Fixed + BHK
141
v_total_amount := v_fixed_product_rate + (v_bhk_product_rate * (v_unit.bhk)::NUMERIC);
142
WHEN 5 THEN -- Fixed + SFT
143
v_total_amount := v_fixed_product_rate + (v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC);
144
END CASE;
145
146
-- Call to create invoice header
147
CALL public.create_invoice_header(
148
v_invoice_header_id,
149
v_company_id,
150
v_unit.customer_id,
151
v_account_receivable_id,
152
v_sales_account_id,
153
v_invoice_date,
154
v_due_date, -- Now passing the calculated due_date
155
v_payment_term,
156
v_total_amount,
157
0.0,
158
0.0,
159
0.0,
160
v_total_amount,
161
0.0,
162
0.0,
163
0.0,
164
v_currency_id,
165
v_note,
166
v_invoice_status_id,
167
v_created_by,
168
'APINV',
169
'00000000-0000-0000-0000-000000000000',
170
'00000000-0000-0000-0000-000000000000',
171
(CURRENT_TIMESTAMP AT TIME ZONE 'UTC'), -- created_on_utc set to current UTC time
172
COALESCE(NULLIF('', ''), ''), -- so_no as empty string
173
v_invoice_date,
174
3, -- Apartment type
175
v_created_by
176
);
177
178
-- Log success for invoice header creation
179
RAISE NOTICE 'Created invoice header for unit_id: %', v_unit.unit_id;
180
181
EXCEPTION
182
WHEN OTHERS THEN
183
-- Handle any errors during invoice posting
184
v_status := 'failed';
185
v_error_message := COALESCE(SQLERRM, 'Unknown error');
186
RAISE NOTICE 'Error creating invoice header for unit_id: % - Error: %', v_unit.unit_id, v_error_message;
187
END;
188
END LOOP;
189
190
-- Final logging after processing template
191
RAISE NOTICE 'Processing complete for template_id: %', schedule_record.template_id;
192
ELSE
193
RAISE NOTICE 'Batch skipped for template_id: %', schedule_record.template_id;
194
END IF;
195
END LOOP;
196
END;
197
$procedure$
|
|||||
| Procedure | create_customer_note | Match | ||||||
| Procedure | run_sales_invoice_schedule | Match | ||||||
| Procedure | create_draft_invoice_header | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_draft_invoice_header(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_invoice_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_currency_id integer, IN p_note character varying, IN p_invoice_status_id integer, IN p_created_by uuid, IN p_invoice_voucher character varying, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_so_no character varying, IN p_so_date date, IN p_type integer)
1
CREATE OR REPLACE PROCEDURE public.create_draft_invoice_header(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_invoice_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_currency_id integer, IN p_note character varying, IN p_invoice_status_id integer, IN p_created_by uuid, IN p_invoice_voucher character varying, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_so_no character varying, IN p_so_date date, IN p_type integer)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
DECLARE
4
DECLARE
5
invoice_number character varying;
5
invoice_number character varying;
6
BEGIN
6
BEGIN
7
7
8
invoice_number := get_new_draft_invoice_number(p_company_id,p_invoice_date);
8
invoice_number := get_new_draft_invoice_number(p_company_id,p_invoice_date);
9
9
10
RAISE NOTICE 'Value: %', invoice_number;
10
RAISE NOTICE 'Value: %', invoice_number;
11
11
12
INSERT INTO
12
INSERT INTO
13
public.draft_invoice_headers(
13
public.draft_invoice_headers(
14
id,
14
id,
15
company_id,
15
company_id,
16
customer_id,
16
customer_id,
17
credit_account_id,
17
credit_account_id,
18
debit_account_id,
18
debit_account_id,
19
invoice_number,
19
invoice_number,
20
invoice_date,
20
invoice_date,
21
due_date,
21
due_date,
22
payment_term,
22
payment_term,
23
taxable_amount,
23
taxable_amount,
24
cgst_amount,
24
cgst_amount,
25
sgst_amount,
25
sgst_amount,
26
igst_amount,
26
igst_amount,
27
total_amount,
27
total_amount,
28
discount,
28
discount,
29
fees,
29
fees,
30
round_off,
30
round_off,
31
currency_id,
31
currency_id,
32
note,
32
note,
33
invoice_status_id,
33
invoice_status_id,
34
created_by,
34
created_by,
35
invoice_voucher,
35
invoice_voucher,
36
source_warehouse_id,
36
source_warehouse_id,
37
destination_warehouse_id,
37
destination_warehouse_id,
38
created_on_utc,
38
created_on_utc,
39
so_no,
39
so_no,
40
so_date,
40
so_date,
41
type,
41
type,
42
current_approval_level,
42
current_approval_level)
43
payment_status_id)
44
VALUES (
43
VALUES (
45
p_id,
44
p_id,
46
p_company_id,
45
p_company_id,
47
p_customer_id,
46
p_customer_id,
48
p_debit_account_id,
47
p_debit_account_id,
49
p_credit_account_id,
48
p_credit_account_id,
50
invoice_number,
49
invoice_number,
51
p_invoice_date,
50
p_invoice_date,
52
p_due_date,
51
p_due_date,
53
p_payment_term,
52
p_payment_term,
54
p_taxable_amount,
53
p_taxable_amount,
55
p_cgst_amount,
54
p_cgst_amount,
56
p_sgst_amount,
55
p_sgst_amount,
57
p_igst_amount,
56
p_igst_amount,
58
p_total_amount,
57
p_total_amount,
59
p_discount,
58
p_discount,
60
p_fees,
59
p_fees,
61
p_round_off,
60
p_round_off,
62
p_currency_id,
61
p_currency_id,
63
p_note,
62
p_note,
64
p_invoice_status_id,
63
p_invoice_status_id,
65
p_created_by,
64
p_created_by,
66
p_invoice_voucher,
65
p_invoice_voucher,
67
p_source_warehouse_id,
66
p_source_warehouse_id,
68
p_destination_warehouse_id,
67
p_destination_warehouse_id,
69
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
68
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
70
p_so_no,
69
p_so_no,
71
p_so_date,
70
p_so_date,
72
p_type,
71
p_type,
73
0,
74
0
72
0
75
);
73
);
76
END;
74
END;
77
$procedure$
75
$procedure$
|
|||||
| Procedure | run_scheduled_invoice | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.run_scheduled_invoice(IN p_invoice_header_id uuid, IN p_draft_invoice_header_id uuid, IN p_schedule_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_system_user_id uuid;
6
v_execution_log_id uuid := gen_random_uuid();
7
v_related_schedule_id uuid;
8
v_new_draft_invoice_id uuid;
9
v_source_is_live boolean := false;
10
v_source_invoice_id uuid;
11
src_header RECORD;
12
src_detail RECORD;
13
BEGIN
14
-- ✅ 1. Find "System" user
15
SELECT u.id
16
INTO v_system_user_id
17
FROM public.users AS u
18
WHERE u.is_deleted = false
19
AND lower(u.first_name) = 'system'
20
ORDER BY u.created_on_utc NULLS LAST, u.id
21
LIMIT 1;
22
23
IF v_system_user_id IS NULL THEN
24
RAISE NOTICE 'No user with first_name="System" found; proceeding with NULL triggered_by.';
25
END IF;
26
27
-- ✅ 2. Determine which ID to use as source
28
IF p_invoice_header_id IS NOT NULL THEN
29
v_source_invoice_id := p_invoice_header_id;
30
v_source_is_live := true;
31
ELSIF p_draft_invoice_header_id IS NOT NULL THEN
32
v_source_invoice_id := p_draft_invoice_header_id;
33
v_source_is_live := false;
34
ELSE
35
RAISE NOTICE 'Both invoice_header_id and draft_invoice_header_id are NULL; aborting.';
36
RETURN;
37
END IF;
38
39
-- ✅ 3. Get related schedule_id
40
SELECT s.id
41
INTO v_related_schedule_id
42
FROM public.recurring_sales_schedules AS s
43
WHERE s.is_deleted = false
44
AND (
45
(v_source_is_live AND s.invoice_header_id = v_source_invoice_id)
46
OR (NOT v_source_is_live AND s.draft_invoice_header_id = v_source_invoice_id)
47
)
48
ORDER BY s.starts_from DESC
49
LIMIT 1;
50
51
-- ✅ 4. Fetch source header from appropriate table
52
IF v_source_is_live THEN
53
SELECT * INTO src_header
54
FROM public.invoice_headers
55
WHERE id = v_source_invoice_id
56
AND is_deleted = false;
57
ELSE
58
SELECT * INTO src_header
59
FROM public.draft_invoice_headers
60
WHERE id = v_source_invoice_id
61
AND is_deleted = false;
62
END IF;
63
64
IF NOT FOUND THEN
65
INSERT INTO public.schedule_execution_logs (
66
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
67
) VALUES (
68
v_execution_log_id, v_related_schedule_id, (p_schedule_date::date), NOW(),
69
'Failure', 'Source invoice header not found in live or draft tables.', v_system_user_id, false
70
);
71
RAISE NOTICE 'Source invoice header % not found; aborting for date %.', v_source_invoice_id, (p_schedule_date::date);
72
RETURN;
73
END IF;
74
75
-- ✅ 5. Generate new draft invoice header
76
v_new_draft_invoice_id := gen_random_uuid();
77
78
CALL public.create_draft_invoice_header(
79
v_new_draft_invoice_id,
80
src_header.company_id,
81
src_header.customer_id,
82
src_header.debit_account_id,
83
src_header.credit_account_id,
84
(p_schedule_date::date),
85
((p_schedule_date::date) + COALESCE(src_header.payment_term, 10)),
86
src_header.payment_term,
87
src_header.taxable_amount,
88
src_header.cgst_amount,
89
src_header.sgst_amount,
90
src_header.igst_amount,
91
src_header.total_amount,
92
src_header.discount,
93
src_header.fees,
94
src_header.round_off,
95
src_header.currency_id,
96
src_header.note,
97
1,
98
v_system_user_id,
99
src_header.invoice_voucher,
100
src_header.source_warehouse_id,
101
src_header.destination_warehouse_id,
102
src_header.so_no,
103
(p_schedule_date::date),
104
src_header.type
105
);
106
107
-- ✅ 6. Clone all details
108
IF v_source_is_live THEN
109
FOR src_detail IN
110
SELECT * FROM public.invoice_details
111
WHERE invoice_header_id = v_source_invoice_id
112
LOOP
113
CALL public.create_draft_invoice_detail(
114
v_new_draft_invoice_id,
115
src_detail.price, src_detail.quantity, src_detail.discount, src_detail.fees,
116
src_detail.cgst_amount, src_detail.igst_amount, src_detail.sgst_amount,
117
src_detail.taxable_amount, src_detail.total_amount,
118
src_detail.serial_number, src_detail.product_id
119
);
120
END LOOP;
121
ELSE
122
FOR src_detail IN
123
SELECT * FROM public.draft_invoice_details
124
WHERE invoice_header_id = v_source_invoice_id
125
LOOP
126
CALL public.create_draft_invoice_detail(
127
v_new_draft_invoice_id,
128
src_detail.price, src_detail.quantity, src_detail.discount, src_detail.fees,
129
src_detail.cgst_amount, src_detail.igst_amount, src_detail.sgst_amount,
130
src_detail.taxable_amount, src_detail.total_amount,
131
src_detail.serial_number, src_detail.product_id
132
);
133
END LOOP;
134
END IF;
135
136
-- ✅ 7. Log success
137
INSERT INTO public.schedule_execution_logs (
138
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
139
) VALUES (
140
v_execution_log_id, v_related_schedule_id, (p_schedule_date::date), NOW(),
141
'Success', '', v_system_user_id, false
142
);
143
144
RAISE NOTICE 'Draft invoice % created from % (live=%), schedule_id %, on %.',
145
v_new_draft_invoice_id, v_source_invoice_id, v_source_is_live, v_related_schedule_id, (p_schedule_date::date);
146
147
EXCEPTION WHEN OTHERS THEN
148
INSERT INTO public.schedule_execution_logs (
149
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
150
) VALUES (
151
COALESCE(v_execution_log_id, gen_random_uuid()), v_related_schedule_id, (p_schedule_date::date), NOW(),
152
'Failure', SQLERRM, v_system_user_id, false
153
);
154
155
RAISE NOTICE 'Error while cloning invoice % on %: %',
156
v_source_invoice_id, (p_schedule_date::date), SQLERRM;
157
END;
158
$procedure$
|
|||||
| Procedure | insert_draft_invoice | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_draft_invoice(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_invoice_date timestamp without time zone, IN p_payment_term integer, IN p_invoice_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_so_no text, IN p_so_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_invoice_voucher text, IN p_created_by uuid, IN p_invoice_details jsonb, IN p_type integer, IN p_settled_amount numeric DEFAULT 0)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
detail RECORD;
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
BEGIN
9
-- Set default values for warehouses
10
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
11
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
12
13
-- Insert into the invoice header
14
CALL public.create_draft_invoice_header (
15
p_id,
16
p_company_id,
17
p_customer_id,
18
p_credit_account_id,
19
p_debit_account_id,
20
p_invoice_date::date,
21
p_due_date::date,
22
p_payment_term,
23
p_taxable_amount,
24
p_cgst_amount,
25
p_sgst_amount,
26
p_igst_amount,
27
p_total_amount,
28
p_discount,
29
p_fees,
30
p_round_off,
31
p_currency_id,
32
p_note,
33
p_invoice_status_id,
34
p_created_by,
35
p_invoice_voucher,
36
v_source_warehouse_id,
37
v_destination_warehouse_id,
38
p_so_no,
39
p_so_date::date,
40
p_type
41
);
42
43
RAISE NOTICE 'Invoice header inserted with ID: %', p_id;
44
45
-- Loop through the JSONB array of invoice details
46
FOR detail IN
47
SELECT * FROM jsonb_to_recordset(p_invoice_details) AS (
48
id uuid, product_id uuid, price numeric, quantity numeric,
49
fees numeric, discount numeric, taxable_amount numeric,
50
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
51
total_amount numeric, serial_number integer
52
)
53
LOOP
54
-- Insert into invoice details
55
INSERT INTO public.draft_invoice_details (
56
id, invoice_header_id, product_id, price, quantity, fees, discount,
57
taxable_amount, sgst_amount, cgst_amount, igst_amount, total_amount,
58
serial_number, is_deleted
59
) VALUES (
60
detail.id, p_id, detail.product_id, detail.price, detail.quantity, detail.fees,
61
detail.discount, detail.taxable_amount, detail.sgst_amount, detail.cgst_amount,
62
detail.igst_amount, detail.total_amount, detail.serial_number, false
63
);
64
65
RAISE NOTICE 'Invoice detail inserted with ID: %', detail.id;
66
END LOOP;
67
68
-- No explicit COMMIT here
69
RAISE NOTICE 'Transaction completed successfully';
70
71
EXCEPTION
72
-- Handle unique violation (duplicate key)
73
WHEN unique_violation THEN
74
RAISE NOTICE 'Duplicate entry: %, rolling back', SQLERRM;
75
ROLLBACK;
76
77
-- Handle foreign key violation
78
WHEN foreign_key_violation THEN
79
RAISE NOTICE 'Foreign key violation: %, rolling back', SQLERRM;
80
ROLLBACK;
81
82
-- Catch all other exceptions
83
WHEN OTHERS THEN
84
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
85
ROLLBACK;
86
END;
87
$procedure$
|
|||||
| Procedure | create_schedule_invoices | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_schedule_invoices(IN p_company_id uuid, IN p_frequency_cycle text, IN p_starts_from date, IN p_end_date date, IN p_created_by uuid, IN p_invoice_header_id uuid DEFAULT NULL::uuid, IN p_draft_invoice_header_id uuid DEFAULT NULL::uuid, IN p_month_of_year integer DEFAULT NULL::integer, IN p_day_of_month integer DEFAULT NULL::integer, IN p_day_of_week integer DEFAULT NULL::integer, IN p_time_of_day interval DEFAULT '00:00:00'::interval)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_recurring_schedule_id uuid := gen_random_uuid(); -- New ID for recurring_sales_schedules
6
v_schedule_status text;
7
-- normalized copies (treat all-zero GUID as NULL)
8
v_invoice_header_id uuid := NULLIF(p_invoice_header_id, '00000000-0000-0000-0000-000000000000'::uuid);
9
v_draft_invoice_header_id uuid := NULLIF(p_draft_invoice_header_id, '00000000-0000-0000-0000-000000000000'::uuid);
10
11
v_day_of_week integer := COALESCE(p_day_of_week, 0);
12
v_day_of_month integer := COALESCE(p_day_of_month, 0);
13
v_month_of_year integer := COALESCE(p_month_of_year, 0);
14
v_time_of_day interval := COALESCE(p_time_of_day, '00:00:00'::interval);
15
BEGIN
16
----------------------------------------------------------------
17
-- Validate: exactly ONE of invoice_header_id / draft_invoice_header_id
18
----------------------------------------------------------------
19
IF (v_invoice_header_id IS NULL AND v_draft_invoice_header_id IS NULL) THEN
20
RAISE EXCEPTION 'Either invoice_header_id or draft_invoice_header_id must be provided.';
21
ELSIF (v_invoice_header_id IS NOT NULL AND v_draft_invoice_header_id IS NOT NULL) THEN
22
RAISE EXCEPTION 'Provide only one of invoice_header_id or draft_invoice_header_id, not both.';
23
END IF;
24
25
-- Determine the initial schedule status
26
IF p_starts_from > CURRENT_DATE THEN
27
v_schedule_status := 'inactive';
28
ELSE
29
v_schedule_status := 'active';
30
END IF;
31
32
-- Insert into the recurring_sales_schedules table
33
INSERT INTO public.recurring_sales_schedules (
34
id, invoice_header_id, draft_invoice_header_id, company_id, frequency_cycle, schedule_status, starts_from, end_date,
35
created_on_utc, created_by, is_deleted
36
)
37
VALUES (
38
v_recurring_schedule_id, v_invoice_header_id, v_draft_invoice_header_id, p_company_id, p_frequency_cycle, v_schedule_status,
39
p_starts_from, p_end_date, NOW(), p_created_by, false
40
);
41
42
-- Insert into the public.recurrence_schedule_details table based on p_frequency_cycle
43
IF p_frequency_cycle = 'Daily' THEN
44
INSERT INTO public.recurrence_schedule_details (
45
schedule_id , frequency_cycle, time_of_day, is_deleted
46
)
47
VALUES (
48
v_recurring_schedule_id, p_frequency_cycle, v_time_of_day, false
49
);
50
51
ELSIF p_frequency_cycle = 'Weekly' THEN
52
INSERT INTO public.recurrence_schedule_details (
53
schedule_id, frequency_cycle, day_of_week, time_of_day, is_deleted
54
)
55
VALUES (
56
v_recurring_schedule_id, p_frequency_cycle, v_day_of_week, v_time_of_day, false
57
);
58
59
ELSIF p_frequency_cycle = 'Monthly' THEN
60
INSERT INTO public.recurrence_schedule_details (
61
schedule_id, frequency_cycle, day_of_month, time_of_day, is_deleted
62
)
63
VALUES (
64
v_recurring_schedule_id, p_frequency_cycle, v_day_of_month, v_time_of_day, false
65
);
66
67
ELSIF p_frequency_cycle = 'Yearly' THEN
68
INSERT INTO public.recurrence_schedule_details (
69
schedule_id, frequency_cycle, month_of_year, day_of_month, time_of_day, is_deleted
70
)
71
VALUES (
72
v_recurring_schedule_id, p_frequency_cycle, v_month_of_year, v_day_of_month, v_time_of_day, false
73
);
74
75
ELSE
76
RAISE NOTICE 'Invalid frequency cycle: %', p_frequency_cycle;
77
END IF;
78
79
RAISE NOTICE 'Recurring schedule created successfully with ID: %, Status: %', v_recurring_schedule_id, v_schedule_status;
80
END;
81
$procedure$
|
|||||
| Procedure | insert_invoice | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_invoice(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_invoice_date timestamp without time zone, IN p_payment_term integer, IN p_invoice_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_so_no text, IN p_so_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_invoice_voucher text, IN p_created_by uuid, IN p_invoice_details jsonb, IN p_type integer, IN p_settled_amount numeric DEFAULT 0)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
detail RECORD;
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
BEGIN
9
-- Set default values for warehouses
10
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
11
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
12
13
-- Insert into the invoice header
14
CALL public.create_invoice_header (
15
p_id,
16
p_company_id,
17
p_customer_id,
18
p_credit_account_id,
19
p_debit_account_id,
20
p_invoice_date::date,
21
p_due_date::date,
22
p_payment_term,
23
p_taxable_amount,
24
p_cgst_amount,
25
p_sgst_amount,
26
p_igst_amount,
27
p_total_amount,
28
p_discount,
29
p_fees,
30
p_round_off,
31
p_currency_id,
32
p_note,
33
p_invoice_status_id,
34
p_created_by,
35
p_invoice_voucher,
36
v_source_warehouse_id,
37
v_destination_warehouse_id,
38
p_so_no,
39
p_so_date::date,
40
p_type
41
);
42
43
RAISE NOTICE 'Invoice header inserted with ID: %', p_id;
44
45
-- Loop through the JSONB array of invoice details
46
FOR detail IN
47
SELECT * FROM jsonb_to_recordset(p_invoice_details) AS (
48
id uuid, product_id uuid, price numeric, quantity numeric,
49
fees numeric, discount numeric, taxable_amount numeric,
50
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
51
total_amount numeric, serial_number integer
52
)
53
LOOP
54
-- Insert into invoice details
55
INSERT INTO public.invoice_details (
56
id, invoice_header_id, product_id, price, quantity, fees, discount,
57
taxable_amount, sgst_amount, cgst_amount, igst_amount, total_amount,
58
serial_number, is_deleted
59
) VALUES (
60
detail.id, p_id, detail.product_id, detail.price, detail.quantity, detail.fees,
61
detail.discount, detail.taxable_amount, detail.sgst_amount, detail.cgst_amount,
62
detail.igst_amount, detail.total_amount, detail.serial_number, false
63
);
64
65
RAISE NOTICE 'Invoice detail inserted with ID: %', detail.id;
66
END LOOP;
67
68
-- No explicit COMMIT here
69
RAISE NOTICE 'Transaction completed successfully';
70
71
EXCEPTION
72
-- Handle unique violation (duplicate key)
73
WHEN unique_violation THEN
74
RAISE NOTICE 'Duplicate entry: %, rolling back', SQLERRM;
75
ROLLBACK;
76
77
-- Handle foreign key violation
78
WHEN foreign_key_violation THEN
79
RAISE NOTICE 'Foreign key violation: %, rolling back', SQLERRM;
80
ROLLBACK;
81
82
-- Catch all other exceptions
83
WHEN OTHERS THEN
84
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
85
ROLLBACK;
86
END;
87
$procedure$
|
|||||
| Procedure | insert_multiple_invoices_by_excel | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_multiple_invoices_by_excel(IN p_invoices jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
invoice RECORD;
6
v_created_by uuid;
7
results TEXT[] := ARRAY[]::TEXT[];
8
record_status TEXT;
9
BEGIN
10
-- Optional: default created_by fallback
11
SELECT id INTO v_created_by
12
FROM users
13
WHERE first_name = 'System'
14
LIMIT 1;
15
16
FOR invoice IN
17
SELECT * FROM jsonb_to_recordset(p_invoices) AS (
18
invoice_id uuid,
19
company_id uuid,
20
customer_id uuid,
21
discount numeric,
22
currency_id integer,
23
invoice_date timestamp without time zone,
24
invoice_number text,
25
payment_term integer,
26
invoice_status_id integer,
27
due_date timestamp without time zone,
28
total_amount numeric,
29
taxable_amount numeric,
30
fees numeric,
31
sgst_amount numeric,
32
cgst_amount numeric,
33
igst_amount numeric,
34
note text,
35
round_off numeric,
36
debit_account_id uuid,
37
credit_account_id uuid,
38
so_no text,
39
so_date timestamp without time zone,
40
source_warehouse_id uuid,
41
destination_warehouse_id uuid,
42
invoice_voucher text,
43
created_by uuid,
44
lines jsonb,
45
type integer,
46
settled_amount numeric
47
)
48
LOOP
49
BEGIN
50
-- Duplicate? mark & skip
51
IF EXISTS (
52
SELECT 1
53
FROM public.invoice_headers
54
WHERE invoice_number = invoice.invoice_number
55
AND company_id = invoice.company_id
56
) THEN
57
record_status := 'duplicate';
58
results := array_append(results, invoice.invoice_number || ':' || record_status);
59
CONTINUE;
60
END IF;
61
62
-- Insert via your existing routine
63
CALL public.insert_invoice_by_excel(
64
invoice.invoice_id,
65
invoice.company_id,
66
invoice.customer_id,
67
invoice.discount,
68
invoice.currency_id,
69
invoice.invoice_date,
70
invoice.invoice_number,
71
invoice.payment_term,
72
invoice.invoice_status_id,
73
invoice.due_date,
74
invoice.total_amount,
75
invoice.taxable_amount,
76
invoice.fees,
77
invoice.sgst_amount,
78
invoice.cgst_amount,
79
invoice.igst_amount,
80
invoice.note,
81
invoice.round_off,
82
invoice.debit_account_id,
83
invoice.credit_account_id,
84
invoice.so_no,
85
invoice.so_date,
86
invoice.source_warehouse_id,
87
invoice.destination_warehouse_id,
88
invoice.invoice_voucher,
89
COALESCE(invoice.created_by, v_created_by),
90
invoice.lines::jsonb,
91
invoice.type,
92
invoice.settled_amount
93
);
94
record_status := 'success';
95
results := array_append(results, invoice.invoice_number || ':' || record_status);
96
97
EXCEPTION WHEN OTHERS THEN
98
record_status := 'failed';
99
results := array_append(results, invoice.invoice_number || ':' || record_status);
100
CONTINUE;
101
END;
102
END LOOP;
103
104
-- Emit overall status as before
105
IF array_position(results, '%:failed') IS NOT NULL THEN
106
RAISE NOTICE 'STATUS:error';
107
ELSIF array_position(results, '%:duplicate') IS NOT NULL THEN
108
RAISE NOTICE 'STATUS:duplicate';
109
ELSE
110
RAISE NOTICE 'STATUS:success';
111
END IF;
112
113
-- Emit per-record status (as a JSONB array for convenience)
114
RAISE NOTICE 'DETAILS:%', to_jsonb(results);
115
116
END;
117
$procedure$
|
|||||
| Procedure | create_invoice_payment | Mismatch |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_invoice_payment(IN p_invoice_payment_header_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_received_date date, IN p_mode_of_payment text, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_reference text, IN p_received_amount numeric, IN p_grand_total_amount numeric, IN p_advance_amount numeric, IN p_description text, IN p_tds_amount numeric, IN p_created_by uuid, IN p_invoice_payment_details jsonb)
1
CREATE OR REPLACE PROCEDURE public.create_invoice_payment(IN p_invoice_payment_header_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_received_date date, IN p_mode_of_payment text, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_reference text, IN p_received_amount numeric, IN p_grand_total_amount numeric, IN p_advance_amount numeric, IN p_description text, IN p_tds_amount numeric, IN p_created_by uuid, IN p_invoice_payment_details jsonb)
2
LANGUAGE plpgsql
2
LANGUAGE plpgsql
3
AS $procedure$
3
AS $procedure$
4
5
DECLARE
4
DECLARE
6
v_total_paid_amount NUMERIC;
5
v_total_paid_amount NUMERIC;
7
v_settled_amount NUMERIC;
6
v_settled_amount NUMERIC;
8
v_invoice_status_id INT;
7
v_invoice_status_id INT;
9
v_payment_status_id INT;
10
v_invoice_header_id UUID;
8
v_invoice_header_id UUID;
11
v_payment_amount NUMERIC;
9
v_payment_amount NUMERIC;
12
v_tds_amount NUMERIC;
10
v_tds_amount NUMERIC;
13
v_serial_number INT;
11
v_serial_number INT;
14
v_row JSONB;
12
v_row JSONB;
15
v_payment_number VARCHAR;
13
v_payment_number VARCHAR;
16
v_invoice_ids UUID[] := '{}';
14
v_invoice_ids UUID[] := '{}'; -- Initialize empty array
17
BEGIN
15
BEGIN
18
v_payment_number := get_new_payment_number(p_company_id, p_received_date);
16
v_payment_number := get_new_payment_number(p_company_id, p_received_date);
19
17
18
-- Insert into invoice_payment_headers
20
INSERT INTO public.invoice_payment_headers (
19
INSERT INTO public.invoice_payment_headers (
21
id,
20
id,
22
company_id,
21
company_id,
23
customer_id,
22
customer_id,
24
received_date,
23
received_date,
25
mode_of_payment,
24
mode_of_payment,
26
credit_account_id,
25
credit_account_id,
27
debit_account_id,
26
debit_account_id,
28
reference,
27
reference,
29
received_amount,
28
received_amount,
30
description,
29
description,
31
tds_amount,
30
tds_amount,
32
advance_amount,
31
advance_amount,
33
grand_total_amount,
32
grand_total_amount,
34
payment_number,
33
payment_number,
35
created_by,
34
created_by,
36
created_on_utc,
35
created_on_utc,
37
is_deleted
36
is_deleted
38
)
37
)
39
VALUES (
38
VALUES (
40
p_invoice_payment_header_id,
39
p_invoice_payment_header_id,
41
p_company_id,
40
p_company_id,
42
p_customer_id,
41
p_customer_id,
43
p_received_date,
42
p_received_date,
44
p_mode_of_payment,
43
p_mode_of_payment,
45
p_credit_account_id,
44
p_credit_account_id,
46
p_debit_account_id,
45
p_debit_account_id,
47
p_reference,
46
p_reference,
48
p_received_amount,
47
p_received_amount,
49
p_description,
48
p_description,
50
p_tds_amount,
49
p_tds_amount,
51
p_advance_amount,
50
p_advance_amount,
52
p_grand_total_amount,
51
p_grand_total_amount,
53
v_payment_number,
52
v_payment_number,
54
p_created_by,
53
p_created_by,
55
now(),
54
now(),
56
false
55
false
57
);
56
);
58
57
58
-- Process each detail row
59
FOR v_row IN SELECT * FROM jsonb_array_elements(p_invoice_payment_details)
59
FOR v_row IN SELECT * FROM jsonb_array_elements(p_invoice_payment_details)
60
LOOP
60
LOOP
61
v_invoice_header_id := (v_row->>'header_id')::UUID;
61
v_invoice_header_id := (v_row->>'header_id')::UUID;
62
v_payment_amount := COALESCE((v_row->>'payment_amount')::NUMERIC, 0);
62
v_payment_amount := (v_row->>'payment_amount')::NUMERIC;
63
v_tds_amount := COALESCE((v_row->>'tds_amount')::NUMERIC, 0);
63
v_tds_amount := (v_row->>'tds_amount')::NUMERIC;
64
v_serial_number := (v_row->>'serial_number')::INT;
64
v_serial_number := (v_row->>'serial_number')::INT;
65
v_total_paid_amount := 0;
65
v_total_paid_amount := 0;
66
v_payment_status_id := 1;
67
v_invoice_status_id := 1;
68
66
69
INSERT INTO public.invoice_payment_details (
67
INSERT INTO public.invoice_payment_details (
70
id,
68
id,
71
invoice_payment_header_id,
69
invoice_payment_header_id,
72
invoice_header_id,
70
invoice_header_id,
73
received_amount,
71
received_amount,
74
tds_amount,
72
tds_amount,
75
serial_number,
73
serial_number,
76
created_by,
74
created_by,
77
created_on_utc
75
created_on_utc
78
)
76
)
79
VALUES (
77
VALUES (
80
(v_row->>'id')::UUID,
78
(v_row->>'id')::UUID,
81
p_invoice_payment_header_id,
79
p_invoice_payment_header_id,
82
v_invoice_header_id,
80
v_invoice_header_id,
83
v_payment_amount,
81
v_payment_amount,
84
v_tds_amount,
82
v_tds_amount,
85
v_serial_number,
83
v_serial_number,
86
p_created_by,
84
p_created_by,
87
now()
85
now()
88
);
86
);
89
87
90
SELECT settled_amount INTO v_settled_amount
88
-- Update settled amount and status
89
SELECT setteled_amount INTO v_settled_amount
91
FROM public.invoice_headers
90
FROM public.invoice_headers
92
WHERE id = v_invoice_header_id;
91
WHERE id = v_invoice_header_id;
93
92
94
v_total_paid_amount := COALESCE(v_settled_amount, 0) + v_payment_amount + v_tds_amount;
93
v_total_paid_amount := v_settled_amount + v_payment_amount + v_tds_amount;
95
94
96
IF (SELECT total_amount FROM public.invoice_headers WHERE id = v_invoice_header_id) > v_total_paid_amount THEN
95
IF (SELECT total_amount FROM public.invoice_headers WHERE id = v_invoice_header_id) > v_total_paid_amount THEN
97
v_invoice_status_id := 4; -- PARTIALLY_PAID
96
v_invoice_status_id := 4; -- PARTIALLY_PAID
98
v_payment_status_id := 2; -- PartiallyPaid
99
ELSE
97
ELSE
100
v_invoice_status_id := 5; -- PAID
98
v_invoice_status_id := 5; -- PAID
101
v_payment_status_id := 3; -- FullyPaid
102
END IF;
99
END IF;
103
100
104
UPDATE public.invoice_headers
101
UPDATE public.invoice_headers
105
SET settled_amount = v_total_paid_amount,
102
SET setteled_amount = v_total_paid_amount,
106
invoice_status_id = v_invoice_status_id,
103
invoice_status_id = v_invoice_status_id
107
payment_status_id = v_payment_status_id
108
WHERE id = v_invoice_header_id;
104
WHERE id = v_invoice_header_id;
109
105
106
-- Collect invoice header id for next status procedure
110
v_invoice_ids := array_append(v_invoice_ids, v_invoice_header_id);
107
v_invoice_ids := array_append(v_invoice_ids, v_invoice_header_id);
111
END LOOP;
108
END LOOP;
112
109
110
-- Call next status procedure if any invoices were updated
113
IF array_length(v_invoice_ids, 1) > 0 THEN
111
IF array_length(v_invoice_ids, 1) > 0 THEN
114
CALL public.update_invoice_next_status_for_invoice_header(
112
CALL public.update_invoice_next_status_for_invoice_header(
115
p_company_id,
113
p_company_id,
116
v_invoice_ids,
114
v_invoice_ids,
117
p_created_by
115
p_created_by
118
);
116
);
119
END IF;
117
END IF;
120
118
121
RAISE NOTICE 'Invoice payment inserted successfully';
119
RAISE NOTICE 'Invoice payment inserted successfully';
122
END;
120
END;
123
$procedure$
121
$procedure$
|
|||||
| Procedure | create_invoice_payment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_invoice_payment(IN p_invoice_payment_header_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_received_date date, IN p_mode_of_payment text, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_reference text, IN p_received_amount numeric, IN p_grand_total_amount numeric, IN p_advance_amount numeric, IN p_description text, IN p_tds_amount numeric, IN p_created_by uuid, IN p_payment_status_id integer, IN p_invoice_payment_details jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_total_paid_amount NUMERIC;
6
v_settled_amount NUMERIC;
7
v_invoice_status_id INT;
8
v_payment_status_id INT;
9
v_invoice_header_id UUID;
10
v_payment_amount NUMERIC;
11
v_tds_amount NUMERIC;
12
v_serial_number INT;
13
v_row JSONB;
14
v_payment_number VARCHAR;
15
v_invoice_ids UUID[] := '{}';
16
BEGIN
17
-- Generate payment number
18
v_payment_number := get_new_payment_number(p_company_id, p_received_date);
19
20
-- Insert payment header
21
INSERT INTO public.invoice_payment_headers (
22
id,
23
company_id,
24
customer_id,
25
received_date,
26
mode_of_payment,
27
credit_account_id,
28
debit_account_id,
29
reference,
30
received_amount,
31
description,
32
tds_amount,
33
advance_amount,
34
grand_total_amount,
35
payment_number,
36
created_by,
37
created_on_utc,
38
is_deleted,
39
payment_status_id
40
)
41
VALUES (
42
p_invoice_payment_header_id,
43
p_company_id,
44
p_customer_id,
45
p_received_date,
46
p_mode_of_payment,
47
p_credit_account_id,
48
p_debit_account_id,
49
p_reference,
50
p_received_amount,
51
p_description,
52
p_tds_amount,
53
p_advance_amount,
54
p_grand_total_amount,
55
v_payment_number,
56
p_created_by,
57
now(),
58
false,
59
p_payment_status_id
60
);
61
62
-- Process invoice-wise payments
63
FOR v_row IN SELECT * FROM jsonb_array_elements(p_invoice_payment_details)
64
LOOP
65
v_invoice_header_id := (v_row->>'header_id')::UUID;
66
v_payment_amount := COALESCE((v_row->>'payment_amount')::NUMERIC, 0);
67
v_tds_amount := COALESCE((v_row->>'tds_amount')::NUMERIC, 0);
68
v_serial_number := (v_row->>'serial_number')::INT;
69
70
INSERT INTO public.invoice_payment_details (
71
id,
72
invoice_payment_header_id,
73
invoice_header_id,
74
received_amount,
75
tds_amount,
76
serial_number,
77
created_by,
78
created_on_utc
79
)
80
VALUES (
81
(v_row->>'id')::UUID,
82
p_invoice_payment_header_id,
83
v_invoice_header_id,
84
v_payment_amount,
85
v_tds_amount,
86
v_serial_number,
87
p_created_by,
88
now()
89
);
90
91
SELECT settled_amount
92
INTO v_settled_amount
93
FROM public.invoice_headers
94
WHERE id = v_invoice_header_id;
95
96
v_total_paid_amount :=
97
COALESCE(v_settled_amount, 0)
98
+ v_payment_amount
99
+ v_tds_amount;
100
101
IF (SELECT total_amount FROM public.invoice_headers WHERE id = v_invoice_header_id)
102
> v_total_paid_amount THEN
103
v_invoice_status_id := 4; -- Partially Paid
104
v_payment_status_id := 2; -- PartiallyPaid
105
ELSE
106
v_invoice_status_id := 5; -- Paid
107
v_payment_status_id := 3; -- FullyPaid
108
END IF;
109
110
UPDATE public.invoice_headers
111
SET
112
settled_amount = v_total_paid_amount,
113
invoice_status_id = v_invoice_status_id,
114
payment_status_id = v_payment_status_id
115
WHERE id = v_invoice_header_id;
116
117
v_invoice_ids := array_append(v_invoice_ids, v_invoice_header_id);
118
END LOOP;
119
120
-- Move invoice to next workflow status
121
IF array_length(v_invoice_ids, 1) > 0 THEN
122
CALL public.update_invoice_next_status_for_invoice_header(
123
p_company_id,
124
v_invoice_ids,
125
p_created_by
126
);
127
END IF;
128
129
RAISE NOTICE 'Invoice payment inserted successfully';
130
END;
131
$procedure$
|
|||||
| Procedure | insert_delinquency_snapshot | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_delinquency_snapshot(IN p_organization_id uuid, IN p_customer_id uuid, IN p_cycle_id uuid, IN p_overdue_amount numeric, IN p_oldest_due_date date, IN p_days_past_due integer, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
6
-- 🛑 Guard: one snapshot per cycle per day
7
IF EXISTS (
8
SELECT 1
9
FROM delinquency_snapshots ds
10
WHERE ds.cycle_id = p_cycle_id
11
AND ds.created_on_utc::date = CURRENT_DATE
12
AND ds.is_deleted = false
13
) THEN
14
RETURN;
15
END IF;
16
17
INSERT INTO delinquency_snapshots (
18
id,
19
organization_id,
20
customer_id,
21
cycle_id,
22
overdue_amount,
23
oldest_due_date,
24
days_past_due,
25
aging_bucket,
26
last_evaluated_on,
27
created_on_utc,
28
created_by,
29
is_deleted
30
)
31
VALUES (
32
gen_random_uuid(),
33
p_organization_id,
34
p_customer_id,
35
p_cycle_id,
36
p_overdue_amount,
37
p_oldest_due_date,
38
p_days_past_due,
39
get_aging_bucket(p_days_past_due),
40
CURRENT_TIMESTAMP,
41
CURRENT_TIMESTAMP,
42
p_created_by,
43
false
44
);
45
END;
46
$procedure$
|
|||||
| View | invoice_approval_level_view | Match | ||||||
| View | vw_invoice_approval_permissions | Match | ||||||
| View | v_run_defaulter_fdw | Missing in Target |
Source Script
Target Script
1
SELECT result
2
FROM run_defaulter_fdw() run_defaulter_fdw(result);
|
-- Table: invoice_approval_user_company
-- ForeignKeys: MissingInSource
ALTER TABLE "invoice_approval_user_company" ADD CONSTRAINT "fk_invoice_approval_user_company_company_id" FOREIGN KEY (company_id) REFERENCES companies(id);
-- Table: invoice_header_ids
-- SOURCE
CREATE TABLE "invoice_header_ids" ("id" integer NOT NULL, "company_id" uuid NOT NULL, "fin_year" integer NOT NULL, "invoice_prefix" text NOT NULL, "invoice_length" integer NOT NULL, "last_invoice_id" integer NOT NULL, PRIMARY KEY ("id"));
-- TARGET
CREATE TABLE "invoice_header_ids" ("id" integer NOT NULL, "company_id" uuid NOT NULL, "fin_year" integer NOT NULL, "invoice_prefix" text NOT NULL, "invoice_length" integer NOT NULL, "last_invoice_id" integer NOT NULL, PRIMARY KEY ("id"));
-- Table: customer_note_statuses
-- 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|PK:|IDX:btree:unique:id:|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 "customer_note_statuses" ("id" integer NOT NULL, "name" text 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"));
-- TARGET SCRIPT
CREATE TABLE "customer_note_statuses" ("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: customer_upis
-- SOURCE
CREATE TABLE "customer_upis" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "upi_id" uuid NOT NULL, PRIMARY KEY ("id"));
-- TARGET
CREATE TABLE "customer_upis" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "upi_id" uuid NOT NULL, PRIMARY KEY ("id"));
-- Table: invoice_statuses
-- SOURCE
CREATE TABLE "invoice_statuses" ("id" integer NOT NULL, "name" varchar(100) 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"));
-- TARGET
CREATE TABLE "invoice_statuses" ("id" integer NOT NULL, "name" varchar(100) 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: customers
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:balance_type:character varying:True:::False|COL:billing_address_id:uuid:True:::False|COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:default_account_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:gstin:text:True:::False|COL:has_gstin:boolean:True::false:False|COL:id:uuid:False:::False|COL:interest_percentage:numeric:True:::False|COL:is_deleted:boolean:False::false:False|COL:is_non_work:boolean:True:::False|COL:member_type_id:integer:False::0:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:name:character varying:False:100::False|COL:opening_balance:numeric:True:::False|COL:outstanding_limit:numeric:True:::False|COL:pan:text:True:::False|COL:proprietor_name:text:True:::False|COL:shipping_address_id:uuid:True:::False|COL:short_name:text:True:::False|COL:tan:text:True:::False|PK:|IDX:btree:unique:id:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:balance_type:character varying:True:::False|COL:billing_address_id:uuid:True:::False|COL:company_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:default_account_id:uuid:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:gstin:text:True:::False|COL:has_gstin:boolean:True::false:False|COL:id:uuid:False:::False|COL:interest_percentage:numeric:True:::False|COL:is_deleted:boolean:False::false:False|COL:is_non_work:boolean:True:::False|COL:member_type_id:integer:False::0:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:name:character varying:False:100::False|COL:opening_balance:numeric:True:::False|COL:outstanding_limit:numeric:True:::False|COL:pan:text:True:::False|COL:proprietor_name:text:True:::False|COL:shipping_address_id:uuid:True:::False|COL:short_name:text:True:::False|COL:tan:text:True:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "customers" ("id" uuid NOT NULL, "name" varchar(100) NOT NULL, "company_id" uuid NOT NULL, "billing_address_id" uuid, "shipping_address_id" uuid, "gstin" text, "pan" text, "tan" text, "short_name" text, "proprietor_name" text, "outstanding_limit" numeric(,), "is_non_work" boolean, "interest_percentage" numeric(,), "has_gstin" boolean DEFAULT false, "opening_balance" numeric(,), "balance_type" varchar(), "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, "member_type_id" integer DEFAULT 0 NOT NULL, "default_account_id" uuid, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "customers" ("id" uuid NOT NULL, "name" varchar(100) NOT NULL, "company_id" uuid NOT NULL, "billing_address_id" uuid, "shipping_address_id" uuid, "gstin" text, "short_name" text, "pan" text, "tan" text, "proprietor_name" text, "outstanding_limit" numeric(,), "is_non_work" boolean, "interest_percentage" numeric(,), "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, "has_gstin" boolean DEFAULT false, "opening_balance" numeric(9,2), "balance_type" varchar(), "member_type_id" integer DEFAULT 0 NOT NULL, "default_account_id" uuid, PRIMARY KEY ("id"));
-- Table: draft_invoice_headers
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:cgst_amount:numeric:False:::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_account_id:uuid:False:::False|COL:currency_id:integer:False::0:False|COL:current_approval_level:integer:False::0:False|COL:customer_id:uuid:False:::False|COL:debit_account_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:destination_warehouse_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:discount:numeric:False:::False|COL:due_date:timestamp without time zone:False:::False|COL:fees:numeric:True:::False|COL:fin_entry_status_id:integer:True::1:False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False:::False|COL:invoice_date:timestamp without time zone:False:::False|COL:invoice_number:text:False:::False|COL:invoice_status_id:integer:False:::False|COL:invoice_voucher:text:True:::False|COL:is_created_by_scheduler:boolean:False::false:False|COL:is_deleted:boolean:False::false:False|COL:is_editable:boolean:True:::False|COL:is_gate_pass_created:boolean:True::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:note:text:False:::False|COL:payment_status_id:integer:False::0:False|COL:payment_term:integer:False:::False|COL:penalty_config_id:integer:True:::False|COL:round_off:numeric:False:::False|COL:settled_amount:numeric:True::0.0:False|COL:sgst_amount:numeric:False:::False|COL:source_warehouse_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:so_date:timestamp without time zone:False::'0001-01-01 00:00:00'::timestamp without time zone:False|COL:so_no:text:True:::False|COL:taxable_amount:numeric:False:::False|COL:total_amount:numeric:False:::False|COL:type:integer:False:::False|PK:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:cgst_amount:numeric:False:::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_account_id:uuid:False:::False|COL:currency_id:integer:False::0:False|COL:current_approval_level:integer:False::0:False|COL:customer_id:uuid:False:::False|COL:debit_account_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:destination_warehouse_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:discount:numeric:False:::False|COL:due_date:timestamp without time zone:False:::False|COL:fees:numeric:True:::False|COL:fin_entry_status_id:integer:True::1:False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False:::False|COL:invoice_date:timestamp without time zone:False:::False|COL:invoice_number:text:False:::False|COL:invoice_status_id:integer:False:::False|COL:invoice_voucher:text:True:::False|COL:is_created_by_scheduler:boolean:False::false:False|COL:is_deleted:boolean:False::false:False|COL:is_editable:boolean:True:::False|COL:is_gate_pass_created:boolean:True::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:note:text:False:::False|COL:payment_term:integer:False:::False|COL:penalty_config_id:integer:True:::False|COL:round_off:numeric:False:::False|COL:setteled_amount:numeric:True::0.0:False|COL:sgst_amount:numeric:False:::False|COL:source_warehouse_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:so_date:timestamp without time zone:False::'0001-01-01 00:00:00'::timestamp without time zone:False|COL:so_no:text:True:::False|COL:taxable_amount:numeric:False:::False|COL:total_amount:numeric:False:::False|COL:type:integer:False:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "draft_invoice_headers" ("id" uuid NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "currency_id" integer DEFAULT 0 NOT NULL, "invoice_voucher" text, "invoice_date" timestamp without time zone NOT NULL, "payment_term" integer NOT NULL, "invoice_status_id" integer NOT NULL, "due_date" timestamp without time zone NOT NULL, "total_amount" numeric(,) NOT NULL, "settled_amount" numeric(,) DEFAULT 0.0, "taxable_amount" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,), "sgst_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "round_off" numeric(,) NOT NULL, "note" text NOT NULL, "debit_account_id" uuid NOT NULL, "credit_account_id" uuid NOT NULL, "so_no" text, "so_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "source_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "destination_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::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, "invoice_number" text NOT NULL, "type" integer NOT NULL, "is_editable" boolean, "penalty_config_id" integer, "is_created_by_scheduler" boolean DEFAULT false NOT NULL, "fin_entry_status_id" integer DEFAULT 1, "is_gate_pass_created" boolean DEFAULT false, "current_approval_level" integer DEFAULT 0 NOT NULL, "payment_status_id" integer DEFAULT 0 NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "draft_invoice_headers" ("id" uuid NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "currency_id" integer DEFAULT 0 NOT NULL, "invoice_number" text NOT NULL, "invoice_voucher" text, "invoice_date" timestamp without time zone NOT NULL, "payment_term" integer NOT NULL, "invoice_status_id" integer NOT NULL, "due_date" timestamp without time zone NOT NULL, "total_amount" numeric(,) NOT NULL, "setteled_amount" numeric(,) DEFAULT 0.0, "taxable_amount" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,), "sgst_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "round_off" numeric(,) NOT NULL, "note" text NOT NULL, "debit_account_id" uuid NOT NULL, "credit_account_id" uuid NOT NULL, "so_no" text, "so_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, "source_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "destination_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::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, "type" integer NOT NULL, "is_editable" boolean, "penalty_config_id" integer, "is_created_by_scheduler" boolean DEFAULT false NOT NULL, "fin_entry_status_id" integer DEFAULT 1, "is_gate_pass_created" boolean DEFAULT false, "current_approval_level" integer DEFAULT 0 NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "draft_invoice_headers" ADD COLUMN "settled_amount" numeric ;
-- Columns: MissingInTarget
ALTER TABLE "draft_invoice_headers" ADD COLUMN "payment_status_id" integer NOT NULL;
-- Table: schema_versions
-- SOURCE
CREATE TABLE "schema_versions" ("script_name" varchar(255) NOT NULL, "applied_on_utc" timestamp without time zone NOT NULL, "hash" varchar(64), PRIMARY KEY ("script_name"));
-- TARGET
CREATE TABLE "schema_versions" ("script_name" varchar(255) NOT NULL, "applied_on_utc" timestamp without time zone NOT NULL, "hash" varchar(64), PRIMARY KEY ("script_name"));
-- Table: upis
-- 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:uuid: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:rx_fund_account_id:text:True:::False|COL:upi_id:uuid:False:::False|PK:|IDX:btree:unique:id:|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:uuid: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:rx_fund_account_id:text:True:::False|COL:upi_id:uuid:False:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "upis" ("id" uuid NOT NULL, "upi_id" uuid NOT NULL, "rx_fund_account_id" text, "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"));
-- TARGET SCRIPT
CREATE TABLE "upis" ("id" uuid NOT NULL, "upi_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, "rx_fund_account_id" text, PRIMARY KEY ("id"));
-- Table: users
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
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:deleted_on_utc:timestamp without time zone:True:::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:password_hash:text:True:::False|COL:phone_number:character varying:False:256:''::character varying:False|PK:|IDX:btree:unique:email,phone_number:|IDX:btree:unique:email:|IDX:btree:unique:id:|IDX:btree:unique:id:
-- TARGET SIGNATURE
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:deleted_on_utc:timestamp without time zone:True:::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:password_hash:text:True:::False|COL:phone_number:character varying:False:256:''::character varying:False|PK:|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, "phone_number" varchar(256) DEFAULT ''::character varying NOT NULL, "password_hash" text, "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, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid 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, "phone_number" varchar(256) DEFAULT ''::character varying NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, PRIMARY KEY ("id"));
-- Indexes: MissingInTarget
CREATE UNIQUE INDEX uq_users_email ON public.users USING btree (email)
-- Table: warehouses
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:address_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:gstin:text:False:::False|COL:id:uuid: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:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:address_id:uuid:False:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:customer_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:gstin:text:False:::False|COL:id:uuid: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 "warehouses" ("id" uuid NOT NULL, "name" text NOT NULL, "customer_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "address_id" uuid NOT NULL, "gstin" text 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"));
-- TARGET SCRIPT
CREATE TABLE "warehouses" ("id" uuid NOT NULL, "name" text NOT NULL, "address_id" uuid NOT NULL, "gstin" text 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, "customer_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, PRIMARY KEY ("id"));
-- Table: invoice_approval_users_account
-- ForeignKeys: MissingInSource
ALTER TABLE "invoice_approval_users_account" ADD CONSTRAINT "fk_invoice_approval_users_account_company_id" FOREIGN KEY (company_id) REFERENCES companies(id);
-- Table: addresses
-- ForeignKeys: MissingInTarget
ALTER TABLE "addresses" ADD CONSTRAINT "fk_addresses_country" FOREIGN KEY (country_id) REFERENCES countries(id);
-- ForeignKeys: MissingInTarget
ALTER TABLE "addresses" ADD CONSTRAINT "fk_addresses_created_by" FOREIGN KEY (created_by) REFERENCES users(id);
-- ForeignKeys: MissingInTarget
ALTER TABLE "addresses" ADD CONSTRAINT "fk_addresses_state" FOREIGN KEY (state_id) REFERENCES states(id);
-- Table: customer_contacts
-- SOURCE
CREATE TABLE "customer_contacts" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "contact_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"));
-- TARGET
CREATE TABLE "customer_contacts" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "contact_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: customer_note_headers
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:cgst_amount:numeric:False:::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_account_id:uuid:False:::False|COL:customer_id:uuid:False:::False|COL:customer_note_status_id:integer:False:::False|COL:debit_account_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:discount:numeric:False:::False|COL:fees:numeric:False:::False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False:::False|COL:invoice_id:uuid:False:::False|COL:is_debit_note:boolean: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:note:text:True:::False|COL:note_date:timestamp without time zone:False:::False|COL:round_off:numeric:False:::False|COL:sgst_amount:numeric:False:::False|COL:taxable_amount:numeric:False:::False|COL:total_amount:numeric:False:::False|PK:|IDX:btree:unique:id:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:cgst_amount:numeric:False:::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_account_id:uuid:False:::False|COL:customer_id:uuid:False:::False|COL:customer_note_status_id:integer:False:::False|COL:debit_account_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:discount:numeric:False:::False|COL:fees:numeric:False:::False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False:::False|COL:invoice_id:uuid:False:::False|COL:is_debit_note:boolean: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:note:text:True:::False|COL:note_date:timestamp without time zone:False:::False|COL:round_off:numeric:False:::False|COL:sgst_amount:numeric:False:::False|COL:taxable_amount:numeric:False:::False|COL:total_amount:numeric:False:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "customer_note_headers" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "company_id" uuid NOT NULL, "credit_account_id" uuid NOT NULL, "debit_account_id" uuid NOT NULL, "customer_note_status_id" integer NOT NULL, "note_date" timestamp without time zone NOT NULL, "invoice_id" uuid NOT NULL, "is_debit_note" boolean NOT NULL, "fees" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "sgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "note" text, "round_off" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "discount" numeric(,) NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "customer_note_headers" ("id" uuid NOT NULL, "is_debit_note" boolean NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "note_date" timestamp without time zone NOT NULL, "invoice_id" uuid NOT NULL, "credit_account_id" uuid NOT NULL, "debit_account_id" uuid NOT NULL, "customer_note_status_id" integer NOT NULL, "fees" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "sgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "round_off" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "note" text, "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: customers_audit
-- SOURCE
CREATE TABLE "customers_audit" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "type" integer NOT NULL, "old_values" text NOT NULL, "new_values" text NOT NULL, "affected_columns" text NOT NULL, "primary_key" uuid NOT NULL, PRIMARY KEY ("id"));
-- TARGET
CREATE TABLE "customers_audit" ("id" uuid NOT NULL, "user_id" uuid NOT NULL, "type" integer NOT NULL, "old_values" text NOT NULL, "new_values" text NOT NULL, "affected_columns" text NOT NULL, "primary_key" uuid NOT NULL, PRIMARY KEY ("id"));
-- Table: payment_statuses
Exists in source, missing in target
-- Table: invoice_workflow
-- SOURCE
CREATE TABLE "invoice_workflow" ("id" integer NOT NULL, "company_id" uuid NOT NULL, "status" integer NOT NULL, "next_status" integer NOT NULL, "previous_status" integer NOT NULL, "is_initial" boolean 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, "approval_level" integer DEFAULT 0 NOT NULL, "is_enabled" boolean DEFAULT true NOT NULL, PRIMARY KEY ("id"));
-- TARGET
CREATE TABLE "invoice_workflow" ("id" integer NOT NULL, "company_id" uuid NOT NULL, "status" integer NOT NULL, "next_status" integer NOT NULL, "previous_status" integer NOT NULL, "is_initial" boolean 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, "approval_level" integer DEFAULT 0 NOT NULL, "is_enabled" boolean DEFAULT true NOT NULL, PRIMARY KEY ("id"));
-- Table: invoice_payment_details
-- 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:uuid:False:::False|COL:invoice_header_id:uuid:False:::False|COL:invoice_payment_header_id:uuid: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:received_amount:numeric:False:::False|COL:serial_number:integer:False::0:False|COL:tds_amount:numeric:False::0.0:False|PK:|FK:invoice_header_id→invoice_headers.id|FK:invoice_payment_header_id→invoice_payment_headers.id|IDX:btree:nonunique:invoice_header_id:|IDX:btree:nonunique:invoice_payment_header_id:|IDX:btree:nonunique:invoice_payment_header_id:|IDX:btree:unique:id:|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:uuid:False:::False|COL:invoice_header_id:uuid:False:::False|COL:invoice_payment_header_id:uuid: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:received_amount:numeric:False:::False|COL:serial_number:integer:False::0:False|COL:tds_amount:numeric:False::0.0:False|PK:|FK:invoice_header_id→invoice_headers.id|FK:invoice_payment_header_id→invoice_payment_headers.id|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "invoice_payment_details" ("id" uuid NOT NULL, "invoice_payment_header_id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "received_amount" numeric(,) NOT NULL, "serial_number" integer DEFAULT 0 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, "tds_amount" numeric(,) DEFAULT 0.0 NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "invoice_payment_details" ("id" uuid NOT NULL, "invoice_payment_header_id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "received_amount" numeric(,) NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "created_by" uuid NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "serial_number" integer DEFAULT 0 NOT NULL, "modified_by" uuid, "tds_amount" numeric(,) DEFAULT 0.0 NOT NULL, PRIMARY KEY ("id"));
-- Indexes: MissingInTarget
CREATE INDEX idx_invoice_payment_details_invoice_header_id ON public.invoice_payment_details USING btree (invoice_header_id)
-- Indexes: MissingInTarget
CREATE INDEX idx_invoice_payment_details_invoice_payment_header_id ON public.invoice_payment_details USING btree (invoice_payment_header_id)
-- Table: roles
-- SOURCE
CREATE TABLE "roles" ("name" varchar(50) NOT NULL, "description" varchar(200), PRIMARY KEY ("name"));
-- TARGET
CREATE TABLE "roles" ("name" varchar(50) NOT NULL, "description" varchar(200), PRIMARY KEY ("name"));
-- Table: states
-- SOURCE
CREATE TABLE "states" ("id" uuid NOT NULL, "name" text NOT NULL, "country_id" uuid 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"));
-- TARGET
CREATE TABLE "states" ("id" uuid NOT NULL, "name" text NOT NULL, "country_id" uuid 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: user_roles
-- SOURCE
CREATE TABLE "user_roles" ("user_id" uuid NOT NULL, "paid_modules" bigint NOT NULL, PRIMARY KEY ("user_id"));
-- TARGET
CREATE TABLE "user_roles" ("user_id" uuid NOT NULL, "paid_modules" bigint NOT NULL, PRIMARY KEY ("user_id"));
-- Table: recurring_sales_schedules
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
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:default_status:integer:False::1:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:draft_invoice_header_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:end_date:timestamp without time zone:False:::False|COL:frequency_cycle:text:False:::False|COL:id:uuid:False:::False|COL:invoice_header_id:uuid:True:::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:schedule_status:text:False:::False|COL:starts_from:timestamp without time zone:False:::False|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:modified_by→users.id|IDX:btree:unique:id:
-- TARGET SIGNATURE
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:default_status:integer:False::1:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:end_date:timestamp without time zone:False:::False|COL:frequency_cycle:text:False:::False|COL:id:uuid:False:::False|COL:invoice_header_id:uuid: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:schedule_status:text:False:::False|COL:starts_from:timestamp without time zone:False:::False|PK:|FK:company_id→companies.id|FK:created_by→users.id|FK:invoice_header_id→invoice_headers.id|FK:modified_by→users.id|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "recurring_sales_schedules" ("id" uuid NOT NULL, "invoice_header_id" uuid, "schedule_status" text NOT NULL, "starts_from" timestamp without time zone NOT NULL, "end_date" timestamp without time zone 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, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "default_status" integer DEFAULT 1 NOT NULL, "frequency_cycle" text NOT NULL, "draft_invoice_header_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "recurring_sales_schedules" ("id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "frequency_cycle" text NOT NULL, "schedule_status" text NOT NULL, "starts_from" timestamp without time zone NOT NULL, "end_date" timestamp without time zone 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, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "default_status" integer DEFAULT 1 NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "recurring_sales_schedules" ADD COLUMN "draft_invoice_header_id" uuid ;
-- ForeignKeys: MissingInSource
ALTER TABLE "recurring_sales_schedules" ADD CONSTRAINT "fk_recurring_sales_schedules_invoice_header_id" FOREIGN KEY (invoice_header_id) REFERENCES invoice_headers(id);
-- Table: invoice_payment_headers
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:advance_amount:numeric:False:::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:credit_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:customer_id:uuid:False:::False|COL:debit_account_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:grand_total_amount:numeric:False:::False|COL:id:uuid:False:::False|COL:is_deleted:boolean:False::false:False|COL:is_posted:boolean:False::false:False|COL:mode_of_payment:text:False::''::text:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:payment_number:text:False::''::text:False|COL:payment_status_id:integer:False::0:False|COL:received_amount:numeric:False:::False|COL:received_date:timestamp without time zone:False:::False|COL:reference:text:False::''::text:False|COL:tds_amount:numeric:False::0.0:False|COL:transaction_id:bigint:True:::False|PK:|FK:customer_id→customers.id|IDX:btree:unique:id:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:advance_amount:numeric:False:::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:credit_account_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:customer_id:uuid:False:::False|COL:debit_account_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:description:text:False:::False|COL:grand_total_amount:numeric:False:::False|COL:id:uuid:False:::False|COL:is_deleted:boolean:False::false:False|COL:mode_of_payment:text:False::''::text:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:payment_number:text:False::''::text:False|COL:received_amount:numeric:False:::False|COL:received_date:timestamp without time zone:False:::False|COL:reference:text:False::''::text:False|COL:tds_amount:numeric:False::0.0:False|PK:|FK:customer_id→customers.id|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "invoice_payment_headers" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "mode_of_payment" text DEFAULT ''::text NOT NULL, "reference" text DEFAULT ''::text NOT NULL, "credit_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "received_date" timestamp without time zone NOT NULL, "received_amount" numeric(,) NOT NULL, "grand_total_amount" numeric(,) NOT NULL, "advance_amount" numeric(,) NOT NULL, "description" text NOT NULL, "debit_account_id" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "created_by" uuid NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "modified_by" uuid, "tds_amount" numeric(,) DEFAULT 0.0 NOT NULL, "payment_number" text DEFAULT ''::text NOT NULL, "is_posted" boolean DEFAULT false NOT NULL, "transaction_id" bigint, "payment_status_id" integer DEFAULT 0 NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "invoice_payment_headers" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "received_date" timestamp without time zone NOT NULL, "received_amount" numeric(,) NOT NULL, "grand_total_amount" numeric(,) NOT NULL, "advance_amount" numeric(,) NOT NULL, "description" text NOT NULL, "debit_account_id" uuid NOT NULL, "created_on_utc" timestamp without time zone NOT NULL, "created_by" uuid NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "company_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "mode_of_payment" text DEFAULT ''::text NOT NULL, "reference" text DEFAULT ''::text NOT NULL, "credit_account_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "modified_by" uuid, "tds_amount" numeric(,) DEFAULT 0.0 NOT NULL, "payment_number" text DEFAULT ''::text NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "invoice_payment_headers" ADD COLUMN "is_posted" boolean NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "invoice_payment_headers" ADD COLUMN "transaction_id" bigint ;
-- Columns: MissingInTarget
ALTER TABLE "invoice_payment_headers" ADD COLUMN "payment_status_id" integer NOT NULL;
-- Table: invoice_headers
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:cgst_amount:numeric:False:::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_account_id:uuid:False:::False|COL:currency_id:integer:False::0:False|COL:current_approval_level:integer:False::0:False|COL:customer_id:uuid:False:::False|COL:debit_account_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:destination_warehouse_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:discount:numeric:False:::False|COL:due_date:timestamp without time zone:False:::False|COL:fees:numeric:True:::False|COL:fin_entry_status_id:integer:True::1:False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False:::False|COL:invoice_date:timestamp without time zone:False:::False|COL:invoice_number:text:False:::False|COL:invoice_status_id:integer:False:::False|COL:invoice_voucher:text:True:::False|COL:is_created_by_scheduler:boolean:False::false:False|COL:is_deleted:boolean:False::false:False|COL:is_editable:boolean:True:::False|COL:is_gate_pass_created:boolean:False::false:False|COL:is_posted:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:note:text:False:::False|COL:payment_status_id:integer:False::1:False|COL:payment_term:integer:False:::False|COL:penalty_config_id:integer:True:::False|COL:round_off:numeric:False:::False|COL:settled_amount:numeric:True::0.0:False|COL:sgst_amount:numeric:False:::False|COL:source_warehouse_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:so_date:timestamp without time zone:True::'0001-01-01 00:00:00'::timestamp without time zone:False|COL:so_no:text:True:::False|COL:taxable_amount:numeric:False:::False|COL:total_amount:numeric:False:::False|COL:transaction_id:bigint:True:::False|COL:type:integer:False:::False|PK:|FK:customer_id→customers.id|FK:invoice_status_id→invoice_statuses.id|IDX:btree:nonunique:company_id,customer_id,due_date:|IDX:btree:unique:id:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:cgst_amount:numeric:False:::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_account_id:uuid:False:::False|COL:currency_id:integer:False::0:False|COL:current_approval_level:integer:False::0:False|COL:customer_id:uuid:False:::False|COL:debit_account_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:destination_warehouse_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:discount:numeric:False:::False|COL:due_date:timestamp without time zone:False:::False|COL:fees:numeric:True:::False|COL:fin_entry_status_id:integer:True::1:False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False:::False|COL:invoice_date:timestamp without time zone:False:::False|COL:invoice_number:text:False:::False|COL:invoice_status_id:integer:False:::False|COL:invoice_voucher:text:True:::False|COL:is_created_by_scheduler:boolean:False::false:False|COL:is_deleted:boolean:False::false:False|COL:is_editable:boolean:True:::False|COL:is_gate_pass_created:boolean:False::false:False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:note:text:False:::False|COL:payment_term:integer:False:::False|COL:penalty_config_id:integer:True:::False|COL:round_off:numeric:False:::False|COL:setteled_amount:numeric:True::0.0:False|COL:sgst_amount:numeric:False:::False|COL:source_warehouse_id:uuid:True::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:so_date:timestamp without time zone:True::'0001-01-01 00:00:00'::timestamp without time zone:False|COL:so_no:text:True:::False|COL:taxable_amount:numeric:False:::False|COL:total_amount:numeric:False:::False|COL:type:integer:False:::False|PK:|FK:customer_id→customers.id|FK:invoice_status_id→invoice_statuses.id|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "invoice_headers" ("id" uuid NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "destination_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "source_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "invoice_number" text NOT NULL, "invoice_voucher" text, "invoice_date" timestamp without time zone NOT NULL, "payment_term" integer NOT NULL, "invoice_status_id" integer NOT NULL, "due_date" timestamp without time zone NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,), "sgst_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "currency_id" integer DEFAULT 0 NOT NULL, "so_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone, "so_no" text, "round_off" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "settled_amount" numeric(,) DEFAULT 0.0, "note" text NOT NULL, "debit_account_id" uuid NOT NULL, "credit_account_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, "type" integer NOT NULL, "is_editable" boolean, "penalty_config_id" integer, "is_created_by_scheduler" boolean DEFAULT false NOT NULL, "is_gate_pass_created" boolean DEFAULT false NOT NULL, "fin_entry_status_id" integer DEFAULT 1, "current_approval_level" integer DEFAULT 0 NOT NULL, "payment_status_id" integer DEFAULT 1 NOT NULL, "is_posted" boolean DEFAULT false NOT NULL, "transaction_id" bigint, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "invoice_headers" ("id" uuid NOT NULL, "company_id" uuid NOT NULL, "customer_id" uuid NOT NULL, "invoice_number" text NOT NULL, "invoice_voucher" text, "invoice_date" timestamp without time zone NOT NULL, "payment_term" integer NOT NULL, "invoice_status_id" integer NOT NULL, "due_date" timestamp without time zone NOT NULL, "total_amount" numeric(,) NOT NULL, "setteled_amount" numeric(,) DEFAULT 0.0, "taxable_amount" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,), "sgst_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "round_off" numeric(,) NOT NULL, "note" text NOT NULL, "debit_account_id" uuid NOT NULL, "credit_account_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, "currency_id" integer DEFAULT 0 NOT NULL, "so_date" timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone, "so_no" text, "destination_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "source_warehouse_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid, "type" integer NOT NULL, "is_editable" boolean, "penalty_config_id" integer, "is_created_by_scheduler" boolean DEFAULT false NOT NULL, "is_gate_pass_created" boolean DEFAULT false NOT NULL, "fin_entry_status_id" integer DEFAULT 1, "current_approval_level" integer DEFAULT 0 NOT NULL, PRIMARY KEY ("id"));
-- Columns: MissingInTarget
ALTER TABLE "invoice_headers" ADD COLUMN "settled_amount" numeric ;
-- Columns: MissingInTarget
ALTER TABLE "invoice_headers" ADD COLUMN "payment_status_id" integer NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "invoice_headers" ADD COLUMN "is_posted" boolean NOT NULL;
-- Columns: MissingInTarget
ALTER TABLE "invoice_headers" ADD COLUMN "transaction_id" bigint ;
-- Indexes: MissingInTarget
CREATE INDEX idx_invoice_headers_outstanding ON public.invoice_headers USING btree (company_id, customer_id, due_date) WHERE ((is_deleted = false) AND ((total_amount - settled_amount) > (0)::numeric))
-- Table: customer_bank_accounts
-- SOURCE
CREATE TABLE "customer_bank_accounts" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "bank_account_id" uuid NOT NULL, PRIMARY KEY ("id"));
-- TARGET
CREATE TABLE "customer_bank_accounts" ("id" uuid NOT NULL, "customer_id" uuid NOT NULL, "bank_account_id" uuid NOT NULL, PRIMARY KEY ("id"));
-- Table: contacts
-- 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:email:text:True:::False|COL:first_name:text:False:::False|COL:id:uuid:False:::False|COL:is_deleted:boolean:False::false:False|COL:is_primary:boolean:False::false:False|COL:last_name:text:True:::False|COL:mobile_number:text:True:::False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:phone_number:text:True:::False|COL:rx_contact_id:text:True:::False|COL:salutation:text:False:::False|PK:|IDX:btree:unique:id:|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:email:text:True:::False|COL:first_name:text:False:::False|COL:id:uuid:False:::False|COL:is_deleted:boolean:False::false:False|COL:is_primary:boolean:False::false:False|COL:last_name:text:True:::False|COL:mobile_number:text:True:::False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:phone_number:text:True:::False|COL:rx_contact_id:text:True:::False|COL:salutation:text:False:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "contacts" ("id" uuid NOT NULL, "salutation" text NOT NULL, "first_name" text NOT NULL, "last_name" text, "email" text, "phone_number" text, "mobile_number" text, "rx_contact_id" text, "is_primary" boolean DEFAULT false 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"));
-- TARGET SCRIPT
CREATE TABLE "contacts" ("id" uuid NOT NULL, "salutation" text NOT NULL, "first_name" text NOT NULL, "last_name" text, "email" text, "phone_number" text, "mobile_number" text, "created_on_utc" timestamp without time zone NOT NULL, "modified_on_utc" timestamp without time zone, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "created_by" uuid NOT NULL, "modified_by" uuid, "is_primary" boolean DEFAULT false NOT NULL, "rx_contact_id" text, PRIMARY KEY ("id"));
-- Table: batch_schedules
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:billing_cycle:text:False:::False|COL:bi_month:integer:True:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:day_of_month:integer:True:::False|COL:day_of_week:integer:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:group_invoice_template_id:uuid:False:::False|COL:half_year:integer:True:::False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:last_executed_at:timestamp without time zone:True:::False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:month_of_year:integer:True:::False|COL:quarters_of_month:integer:True:::False|COL:time_of_day:interval:False:::False|PK:|FK:group_invoice_template_id→group_invoice_templates.id|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:billing_cycle:text:False:::False|COL:bi_month:integer:True:::False|COL:created_by:uuid:False:::False|COL:created_on_utc:timestamp without time zone:False:::False|COL:day_of_month:integer:True:::False|COL:day_of_week:integer:True:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:group_invoice_template_id:uuid:False:::False|COL:half_year:integer:True:::False|COL:id:integer:False:::False|COL:is_deleted:boolean:False::false:False|COL:last_executed_at:timestamp without time zone:True:::False|COL:modified_by:uuid:True:::False|COL:modified_on_utc:timestamp without time zone:True:::False|COL:month_of_year:integer:True:::False|COL:quarters_of_month:integer:True:::False|COL:time_of_day:interval:False:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "batch_schedules" ("id" integer NOT NULL, "group_invoice_template_id" uuid NOT NULL, "billing_cycle" text NOT NULL, "day_of_week" integer, "day_of_month" integer, "month_of_year" integer, "time_of_day" interval NOT NULL, "last_executed_at" timestamp without time zone, "half_year" integer, "quarters_of_month" integer, "bi_month" integer, "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, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "batch_schedules" ("id" integer NOT NULL, "group_invoice_template_id" uuid NOT NULL, "billing_cycle" text NOT NULL, "day_of_week" integer, "day_of_month" integer, "month_of_year" integer, "time_of_day" interval NOT NULL, "last_executed_at" timestamp without time zone, "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, "half_year" integer, "quarters_of_month" integer, "bi_month" integer, PRIMARY KEY ("id"));
-- ForeignKeys: MissingInTarget
ALTER TABLE "batch_schedules" ADD CONSTRAINT "fk_template_id" FOREIGN KEY (group_invoice_template_id) REFERENCES group_invoice_templates(id);
-- Table: __EFMigrationsHistory
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:migration_id:character varying:False:150::False|COL:product_version:character varying:False:32::False|IDX:btree:unique:migration_id:
-- TARGET SIGNATURE
COL:migration_id:character varying:False:150::False|COL:product_version:character varying:False:32::False|PK:|IDX:btree:unique:migration_id:
-- SOURCE SCRIPT
CREATE TABLE "__EFMigrationsHistory" ("migration_id" varchar(150) NOT NULL, "product_version" varchar(32) NOT NULL);
-- TARGET SCRIPT
CREATE TABLE "__EFMigrationsHistory" ("migration_id" varchar(150) NOT NULL, "product_version" varchar(32) NOT NULL, PRIMARY KEY ("migration_id"));
-- Table: cities
-- ForeignKeys: MissingInTarget
ALTER TABLE "cities" ADD CONSTRAINT "fk_cities_state" FOREIGN KEY (state_id) REFERENCES states(id);
-- Table: countries
-- 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:uuid:False:::False|COL:iso_alpha_code:text: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:|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:uuid:False:::False|COL:iso_alpha_code:text: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 "countries" ("id" uuid NOT NULL, "name" text NOT NULL, "iso_alpha_code" text 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"));
-- TARGET SCRIPT
CREATE TABLE "countries" ("id" uuid NOT NULL, "name" text NOT NULL, "iso_alpha_code" 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: banks
-- SOURCE
CREATE TABLE "banks" ("id" integer NOT NULL, "name" text 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"));
-- TARGET
CREATE TABLE "banks" ("id" integer NOT NULL, "name" text 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: customer_note_details
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:cgst_amount:numeric:False:::False|COL:customer_note_header_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:discount:numeric:False:::False|COL:fees:numeric:False:::False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False:::False|COL:is_deleted:boolean:False::false:False|COL:price:numeric:False:::False|COL:product_id:uuid:False:::False|COL:quantity:integer:False:::False|COL:sgst_amount:numeric:False:::False|COL:taxable_amount:numeric:False:::False|COL:total_amount:numeric:False:::False|PK:|IDX:btree:unique:id:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:cgst_amount:numeric:False:::False|COL:customer_note_header_id:uuid:False:::False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:discount:numeric:False:::False|COL:fees:numeric:False:::False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False:::False|COL:is_deleted:boolean:False::false:False|COL:price:numeric:False:::False|COL:product_id:uuid:False:::False|COL:quantity:integer:False:::False|COL:sgst_amount:numeric:False:::False|COL:taxable_amount:numeric:False:::False|COL:total_amount:numeric:False:::False|PK:|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "customer_note_details" ("id" uuid NOT NULL, "customer_note_header_id" uuid NOT NULL, "product_id" uuid NOT NULL, "quantity" integer NOT NULL, "price" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "sgst_amount" numeric(,) NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "customer_note_details" ("id" uuid NOT NULL, "customer_note_header_id" uuid NOT NULL, "product_id" uuid NOT NULL, "quantity" integer NOT NULL, "price" numeric(,) NOT NULL, "discount" numeric(,) NOT NULL, "fees" numeric(,) NOT NULL, "taxable_amount" numeric(,) NOT NULL, "cgst_amount" numeric(,) NOT NULL, "sgst_amount" numeric(,) NOT NULL, "igst_amount" numeric(,) NOT NULL, "total_amount" numeric(,) NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
-- Table: customer_note_work_flows
-- SOURCE
CREATE TABLE "customer_note_work_flows" ("id" integer NOT NULL, "company_id" uuid NOT NULL, "status" integer NOT NULL, "next_status" integer NOT NULL, "previous_status" integer NOT NULL, "is_initial" boolean 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"));
-- TARGET
CREATE TABLE "customer_note_work_flows" ("id" integer NOT NULL, "company_id" uuid NOT NULL, "status" integer NOT NULL, "next_status" integer NOT NULL, "previous_status" integer NOT NULL, "is_initial" boolean 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: delinquency_cycles
Exists in source, missing in target
-- Table: delinquency_snapshots
Exists in source, missing in target
-- Table: collection_policies
Exists in source, missing in target
-- Table: delinquency_discussion_messages
Exists in source, missing in target
-- Table: group_invoice_details
-- ForeignKeys: MissingInTarget
ALTER TABLE "group_invoice_details" ADD CONSTRAINT "fk_group_invoice_details_created_by" FOREIGN KEY (created_by) REFERENCES users(id);
-- Table: v_user_id
Exists in source, missing in target
-- Table: delinquency_actions
Exists in source, missing in target
-- Table: delinquency_resolution_window
Exists in source, missing in target
-- Table: delinquency_snapshot_invoices
Exists in source, missing in target
-- Table: invoice_approval_issue_log
-- ForeignKeys: MissingInTarget
ALTER TABLE "invoice_approval_issue_log" ADD CONSTRAINT "fk_invoice_approval_issue_log_created_by" FOREIGN KEY (created_by) REFERENCES users(id);
-- Table: invoice_details
-- StructuralDiff: Mismatch
-- SOURCE SIGNATURE
COL:cgst_amount:numeric:False::0.0:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:discount:numeric:True:::False|COL:fees:numeric:True:::False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False::0.0:False|COL:invoice_header_id:uuid:False:::False|COL:is_deleted:boolean:False::false:False|COL:price:numeric:False:::False|COL:product_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:quantity:numeric:False:::False|COL:serial_number:integer:False::0:False|COL:sgst_amount:numeric:False::0.0:False|COL:taxable_amount:numeric:False::0.0:False|COL:total_amount:numeric:False::0.0:False|PK:|FK:invoice_header_id→invoice_headers.id|IDX:btree:unique:id:|IDX:btree:unique:id:
-- TARGET SIGNATURE
COL:cgst_amount:numeric:False::0.0:False|COL:deleted_on_utc:timestamp without time zone:True:::False|COL:discount:numeric:True:::False|COL:fees:numeric:True:::False|COL:id:uuid:False:::False|COL:igst_amount:numeric:False::0.0:False|COL:invoice_header_id:uuid:False:::False|COL:is_deleted:boolean:False::false:False|COL:price:numeric:False:::False|COL:product_id:uuid:False::'00000000-0000-0000-0000-000000000000'::uuid:False|COL:quantity:numeric:False:::False|COL:serial_number:integer:False::0:False|COL:sgst_amount:numeric:False::0.0:False|COL:taxable_amount:numeric:False::0.0:False|COL:total_amount:numeric:False::0.0:False|PK:|FK:invoice_header_id→invoice_headers.id|IDX:btree:unique:id:
-- SOURCE SCRIPT
CREATE TABLE "invoice_details" ("id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "price" numeric(,) NOT NULL, "quantity" numeric(,) NOT NULL, "product_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, "cgst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "discount" numeric(,), "fees" numeric(,), "igst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "sgst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "taxable_amount" numeric(,) DEFAULT 0.0 NOT NULL, "total_amount" numeric(,) DEFAULT 0.0 NOT NULL, "serial_number" integer DEFAULT 0 NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, PRIMARY KEY ("id"));
-- TARGET SCRIPT
CREATE TABLE "invoice_details" ("id" uuid NOT NULL, "invoice_header_id" uuid NOT NULL, "price" numeric(,) NOT NULL, "quantity" numeric(,) NOT NULL, "deleted_on_utc" timestamp without time zone, "is_deleted" boolean DEFAULT false NOT NULL, "cgst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "discount" numeric(,), "fees" numeric(,), "igst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "sgst_amount" numeric(,) DEFAULT 0.0 NOT NULL, "taxable_amount" numeric(,) DEFAULT 0.0 NOT NULL, "total_amount" numeric(,) DEFAULT 0.0 NOT NULL, "serial_number" integer DEFAULT 0 NOT NULL, "product_id" uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL, PRIMARY KEY ("id"));
-- Table: recurrence_schedule_details
-- ForeignKeys: MissingInTarget
ALTER TABLE "recurrence_schedule_details" ADD CONSTRAINT "fk_recurrence_schedule_details_schedule_id" FOREIGN KEY (schedule_id) REFERENCES recurring_sales_schedules(id) ON UPDATE CASCADE ON DELETE CASCADE;
-- Table: delinquency_action_types
Exists in source, missing in target
-- Table: delinquency_resolution_states
Exists in source, missing in target
-- Table: v_contact_number
Exists in source, missing in target
-- Table: v_invoice_ids_draft
-- CreateScript: MissingInSource
CREATE TABLE "v_invoice_ids_draft" ("array_agg" ARRAY);
-- Columns: MissingInSource
ALTER TABLE "v_invoice_ids_draft" ADD COLUMN "array_agg" ARRAY ;
-- Function: generate_group_invoices_return_header_ids
CREATE OR REPLACE FUNCTION public.generate_group_invoices_return_header_ids(p_template_id uuid)
RETURNS uuid[]
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_ts timestamptz := now();
v_header_ids uuid[];
BEGIN
-- Call your existing (unchanged) procedure
CALL public.run_grouped_invoices(p_template_id);
-- Collect headers created in this invocation window
SELECT COALESCE(array_agg(h.id), '{}') INTO v_header_ids
FROM public.group_invoice_headers h
WHERE h.group_invoice_template_id = p_template_id
AND h.is_deleted = FALSE
AND h.created_on_utc >= v_start_ts; -- created_on_utc is already saved via NOW() in proc
RETURN v_header_ids;
END;
$function$
-- Function: get_executed_grouped_invoice
CREATE OR REPLACE FUNCTION public.get_executed_grouped_invoice(p_company_id uuid)
RETURNS TABLE(id uuid, template_id uuid, execution_date timestamp without time zone, error_message text, success_count integer, total_count integer, invoice_description text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
executions.id,
executions.template_id,
executions.execution_date,
executions.error_message,
executions.success_count,
executions.total_count,
templates.invoice_description
FROM
public.apartment_invoice_template_executions executions
INNER JOIN
public.apartment_invoice_templates templates
ON
executions.template_id = templates.id
WHERE
executions.company_id = p_company_id
ORDER BY
executions.execution_date DESC;
END;
$function$
-- Function: get_group_invoice_summary
CREATE OR REPLACE FUNCTION public.get_group_invoice_summary(p_group_invoice_id uuid)
RETURNS TABLE(id uuid, group_invoice_number text, execution_date timestamp without time zone, invoice_description text, paid_count integer, paid_amount numeric, unpaid_count integer, unpaid_amount numeric, overdue_count integer, overdue_amount numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
gih.id,
gih.group_invoice_number,
gih.execution_date,
git.invoice_description,
-- Paid
COALESCE(COUNT(CASE WHEN ih.invoice_status_id = 5 THEN 1 END), 0)::int AS paid_count,
COALESCE(SUM(CASE WHEN ih.invoice_status_id = 5 THEN ih.total_amount END), 0) AS paid_amount,
-- Unpaid
COALESCE(COUNT(CASE WHEN ih.invoice_status_id IN (1,2,3,4) THEN 1 END), 0)::int AS unpaid_count,
COALESCE(SUM(CASE WHEN ih.invoice_status_id IN (1,2,3,4) THEN ih.total_amount END), 0) AS unpaid_amount,
-- Overdue
COALESCE(COUNT(CASE WHEN ih.invoice_status_id NOT IN (5) AND ih.due_date < now() THEN 1 END), 0)::int AS overdue_count,
COALESCE(SUM(CASE WHEN ih.invoice_status_id NOT IN (5) AND ih.due_date < now() THEN ih.total_amount END), 0) AS overdue_amount
FROM group_invoice_headers gih
INNER JOIN group_invoice_templates git
ON gih.group_invoice_template_id = git.id
LEFT JOIN group_invoice_details gid
ON gid.group_invoice_header_id = gih.id AND gid.is_deleted = false
LEFT JOIN invoice_headers ih
ON ih.id = gid.invoice_header_id AND ih.is_deleted = false
WHERE gih.id = p_group_invoice_id
AND gih.is_deleted = false
GROUP BY gih.id, gih.group_invoice_number, gih.execution_date, git.invoice_description;
END;
$function$
-- Function: get_payment_distributions_for_invoice
CREATE OR REPLACE FUNCTION public.get_payment_distributions_for_invoice(p_invoice_header_id uuid)
RETURNS TABLE(payment_detail_id uuid, invoice_payment_header_id uuid, payment_number text, invoice_header_id uuid, received_amount numeric, payment_created_by uuid, payment_created_by_name text, payment_created_on timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ipd.id AS payment_detail_id,
ipd.invoice_payment_header_id,
iph.payment_number,
ipd.invoice_header_id,
ipd.received_amount,
ipd.created_by AS payment_created_by,
CONCAT(u.first_name, ' ', u.last_name) AS payment_created_by_name,
ipd.created_on_utc AS payment_created_on
FROM
public.invoice_payment_details ipd
JOIN public.invoice_payment_headers iph ON iph.id = ipd.invoice_payment_header_id
LEFT JOIN public.users u ON u.id = ipd.created_by
WHERE
ipd.invoice_header_id = p_invoice_header_id
AND ipd.is_deleted = false
ORDER BY
ipd.created_on_utc ASC;
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: upsert_invoice_status_company_config
-- SOURCE
CREATE OR REPLACE FUNCTION public.upsert_invoice_status_company_config(p_company_id uuid, p_status_id integer, p_is_enabled boolean, p_user_id uuid)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_id INT;
BEGIN
-- Try update first (only non-deleted)
UPDATE public.invoice_status_company_configs
SET
is_enabled = p_is_enabled,
modified_on_utc = now(),
modified_by = p_user_id
WHERE company_id = p_company_id
AND status_id = p_status_id
AND is_deleted = false
RETURNING id INTO v_id;
-- If not found, insert new with is_deleted = false
IF NOT FOUND THEN
INSERT INTO public.invoice_status_company_configs (
company_id,
status_id,
is_enabled,
created_on_utc,
created_by,
is_deleted
)
VALUES (
p_company_id,
p_status_id,
p_is_enabled,
now(),
p_user_id,
false
)
RETURNING id INTO v_id;
END IF;
RETURN v_id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.upsert_invoice_status_company_config(p_company_id uuid, p_status_id integer, p_is_enabled boolean)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_id INT;
BEGIN
UPDATE public.invoice_status_company_configs
SET is_enabled = p_is_enabled,
modified_on_utc = now()
WHERE company_id = p_company_id AND status_id = p_status_id
RETURNING id INTO v_id;
IF NOT FOUND THEN
INSERT INTO public.invoice_status_company_configs (
company_id,
status_id,
is_enabled,
created_on_utc
)
VALUES (
p_company_id,
p_status_id,
p_is_enabled,
now()
)
RETURNING id INTO v_id;
END IF;
RETURN v_id;
END;
$function$
-- Function: update_invoice_next_status_main
CREATE OR REPLACE FUNCTION public.update_invoice_next_status_main(p_company_id uuid, p_invoice_status_id integer, p_invoice_ids uuid[], p_modified_by uuid)
RETURNS TABLE(invoice_id uuid, status_name text, error_msg text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_error_message text;
v_invoice_ids_draft uuid[];
v_invoice_ids_pending uuid[];
v_invoice_ids_other uuid[];
v_next_temp_id int;
BEGIN
RAISE NOTICE 'START update_invoice_next_status_main: company_id=%, invoice_status_id=%, modified_by=%', p_company_id, p_invoice_status_id, p_modified_by;
RAISE NOTICE 'Incoming Invoice IDs: %', p_invoice_ids;
-- Classify invoices by current status
SELECT array_agg(id) INTO v_invoice_ids_draft
FROM public.draft_invoice_headers dih
WHERE dih.id = ANY(p_invoice_ids) AND dih.invoice_status_id = 1;
SELECT array_agg(id) INTO v_invoice_ids_pending
FROM public.draft_invoice_headers dih
WHERE dih.id = ANY(p_invoice_ids) AND dih.invoice_status_id = 2;
SELECT array_agg(id) INTO v_invoice_ids_other
FROM public.invoice_headers ih
WHERE ih.id = ANY(p_invoice_ids) AND ih.invoice_status_id >= 3;
RAISE NOTICE 'Draft invoices: %', COALESCE(v_invoice_ids_draft, '{}');
RAISE NOTICE 'Pending Approval invoices: %', COALESCE(v_invoice_ids_pending, '{}');
RAISE NOTICE 'Other (Approved or higher) invoices: %', COALESCE(v_invoice_ids_other, '{}');
BEGIN
-- Direct update draft invoices to Pending Approval
IF array_length(v_invoice_ids_draft, 1) > 0 THEN
RAISE NOTICE 'Directly updating Draft invoices to Pending Approval';
UPDATE public.draft_invoice_headers
SET invoice_status_id = 2,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = ANY(v_invoice_ids_draft);
-- Insert approval logs
INSERT INTO public.invoice_approval_logs(
invoice_id, status_id, approved_by, approved_on, "comment",
created_on_utc, created_by, approval_level
)
SELECT
dh.id, 2, p_modified_by, now(),
'Direct update from Draft to Pending Approval',
now(), p_modified_by, 0
FROM public.draft_invoice_headers dh
WHERE dh.id = ANY(v_invoice_ids_draft);
-- Generate next id for temp_invoice_next_status
SELECT COALESCE(MAX(id), 0) INTO v_next_temp_id FROM temp_invoice_next_status;
-- Insert into temp_invoice_next_status
INSERT INTO temp_invoice_next_status(id, invoice_id, status, error)
SELECT
row_number() OVER () + v_next_temp_id AS id,
dh.id,
'Pending Approval',
NULL
FROM public.draft_invoice_headers dh
WHERE dh.id = ANY(v_invoice_ids_draft);
END IF;
-- Call update_draft_invoice_next_status if Pending Approval invoices found and p_invoice_status_id = 2
IF array_length(v_invoice_ids_pending, 1) > 0 AND p_invoice_status_id = 2 THEN
RAISE NOTICE 'Calling update_draft_invoice_next_status for Pending Approval invoices';
CALL public.update_draft_invoice_next_status(p_company_id, p_invoice_status_id, v_invoice_ids_pending, p_modified_by);
END IF;
-- Call update_invoice_next_status_for_invoice_header for Approved or higher invoices
IF array_length(v_invoice_ids_other, 1) > 0 THEN
RAISE NOTICE 'Calling update_invoice_next_status_for_invoice_header for Approved or higher invoices';
CALL public.update_invoice_next_status_for_invoice_header(p_company_id, p_invoice_status_id, v_invoice_ids_other, p_modified_by);
END IF;
EXCEPTION WHEN OTHERS THEN
v_error_message := SQLERRM;
RAISE NOTICE 'Exception in child procedures: %', v_error_message;
END;
-- Return rows from temp_invoice_next_status
RAISE NOTICE 'Preparing to return rows from temp_invoice_next_status';
RETURN QUERY
SELECT tinv.invoice_id, tinv.status AS status_name, tinv.error AS error_msg
FROM temp_invoice_next_status tinv;
RAISE NOTICE 'END update_invoice_next_status_main';
END;
$function$
-- Function: get_all_invoice_company_approvals
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_all_invoice_company_approvals(p_company_id uuid)
RETURNS TABLE(id integer, status_id integer, user_id uuid, approval_level integer, required_approval_levels integer)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
MIN(ia.id) AS id,
ia.status_id,
ia.user_id,
ia.approval_level,
iw.approval_level AS required_approval_levels
FROM invoice_approval_user_company ia
JOIN invoice_workflow iw
ON ia.company_id = iw.company_id
WHERE ia.company_id = p_company_id
AND ia.is_deleted = false
AND iw.is_deleted = false
AND iw.is_enabled = TRUE
AND iw.status = 2
GROUP BY ia.status_id, ia.user_id, ia.approval_level,iw.approval_level
ORDER BY ia.status_id, ia.user_id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_all_invoice_company_approvals(p_company_id uuid)
RETURNS TABLE(id integer, status_id integer, user_id uuid, approval_level integer)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
MIN(ia.id) AS id,
ia.status_id,
ia.user_id,
ia.approval_level
FROM invoice_approval_user_company ia
WHERE ia.company_id = p_company_id
AND ia.is_deleted = false
GROUP BY ia.status_id, ia.user_id, ia.approval_level
ORDER BY ia.status_id, ia.user_id;
END;
$function$
-- Function: list_scheduled_group_invoice_template_ids
CREATE OR REPLACE FUNCTION public.list_scheduled_group_invoice_template_ids(p_run_date timestamp without time zone)
RETURNS TABLE(group_invoice_template_id uuid, company_id uuid, organization_id uuid)
LANGUAGE sql
STABLE
AS $function$
WITH params AS (
SELECT
(p_run_date)::date AS run_date,
EXTRACT(ISODOW FROM p_run_date)::int AS run_isodow,
EXTRACT(DAY FROM p_run_date)::int AS run_day,
EXTRACT(MONTH FROM p_run_date)::int AS run_month,
EXTRACT(DAY FROM (date_trunc('month', (p_run_date)::date)
+ interval '1 month - 1 day'))::int AS days_in_month
),
fy AS (
SELECT *,
CASE WHEN run_month >= 4 THEN run_month - 3 ELSE run_month + 9 END AS fy_month_index
FROM params
),
base AS (
SELECT
-- schedule fields (explicit; no bs.*)
bs.billing_cycle,
bs.day_of_week,
bs.day_of_month,
bs.bi_month,
bs.quarters_of_month,
bs.half_year,
bs.month_of_year,
bs.last_executed_at,
-- template & org linkage
git.id AS group_invoice_template_id,
c.id AS company_id,
o.id AS organization_id,
-- template window
git.starts_from,
git.end_date,
-- precomputed calendar
f.run_date,
f.run_isodow,
f.run_day,
f.days_in_month,
f.fy_month_index
FROM public.batch_schedules bs
JOIN public.group_invoice_templates git
ON git.id = bs.group_invoice_template_id
AND git.is_deleted = FALSE
AND git.active_status = TRUE
JOIN public.companies c
ON c.id = git.company_id
AND c.is_deleted = FALSE
JOIN public.organizations o
ON o.id = c.organization_id
AND o.is_deleted = FALSE
CROSS JOIN fy f
WHERE (bs.last_executed_at IS NULL OR bs.last_executed_at::date <> f.run_date)
AND (git.starts_from IS NULL OR f.run_date >= git.starts_from::date)
AND (git.end_date IS NULL OR f.run_date <= git.end_date::date)
),
calc AS (
SELECT
b.group_invoice_template_id,
b.company_id,
b.organization_id,
CASE b.billing_cycle
WHEN 'Daily' THEN TRUE
WHEN 'Weekly' THEN COALESCE(b.day_of_week, b.run_isodow) = b.run_isodow
WHEN 'Monthly' THEN b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
WHEN 'Bi-Monthly' THEN
COALESCE(b.bi_month, 1) IN (1,2)
AND (
(b.bi_month = 1 AND (b.fy_month_index % 2) = 1) OR
(b.bi_month = 2 AND (b.fy_month_index % 2) = 0)
)
AND b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
WHEN 'Quarterly' THEN
COALESCE(b.quarters_of_month, 1) BETWEEN 1 AND 4
AND b.fy_month_index = (CASE b.quarters_of_month
WHEN 1 THEN 1
WHEN 2 THEN 4
WHEN 3 THEN 7
ELSE 10 END)
AND b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
WHEN 'Half-yearly' THEN
COALESCE(b.half_year, 1) IN (1,2)
AND b.fy_month_index = (CASE b.half_year WHEN 1 THEN 1 ELSE 7 END)
AND b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
WHEN 'Yearly' THEN
COALESCE(b.month_of_year, b.fy_month_index) = b.fy_month_index
AND b.run_day = LEAST(COALESCE(b.day_of_month, 1), b.days_in_month)
ELSE FALSE
END AS is_due
FROM base b
)
SELECT
group_invoice_template_id,
company_id,
organization_id
FROM calc
WHERE is_due = TRUE
ORDER BY organization_id, company_id, group_invoice_template_id;
$function$
-- Function: get_grouped_invoice
CREATE OR REPLACE FUNCTION public.get_grouped_invoice(p_company_id uuid)
RETURNS TABLE(id uuid, template_id uuid, execution_date timestamp without time zone, error_message text, success_count integer, total_count integer, invoice_description text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
executions.id,
executions.template_id,
executions.execution_date,
executions.error_message,
executions.success_count,
executions.total_count,
templates.invoice_description
FROM
public.apartment_invoice_template_executions executions
INNER JOIN
public.apartment_invoice_templates templates
ON
executions.template_id = templates.id
WHERE
executions.company_id = p_company_id
ORDER BY
executions.execution_date DESC;
END;
$function$
-- Function: close_resolved_cycles
CREATE OR REPLACE FUNCTION public.close_resolved_cycles(p_organization_id uuid, p_company_ids uuid[])
RETURNS void
LANGUAGE plpgsql
AS $function$
BEGIN
UPDATE delinquency_cycles dc
SET
ended_on_utc = now(),
status_id = 2, -- CLOSED
modified_on_utc = now(),
modified_by = get_system_user_id()
WHERE dc.organization_id = p_organization_id
AND dc.ended_on_utc IS NULL
AND dc.is_deleted = false
AND NOT EXISTS (
SELECT 1
FROM invoice_headers ih
WHERE ih.company_id = ANY(p_company_ids)
AND ih.customer_id = dc.customer_id
AND ih.is_deleted = false
AND (ih.total_amount - ih.settled_amount) > 0
);
END;
$function$
-- Function: update_invoice_payment_status
CREATE OR REPLACE FUNCTION public.update_invoice_payment_status(p_company_id uuid, p_invoice_ids uuid[], p_payment_status_id integer, p_modified_by uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_invoice_header_id uuid; -- Declare a variable to hold each invoice_header_id
v_invoice_payment_header public.invoice_payment_headers%ROWTYPE; -- Declare ROWTYPE for full row
BEGIN
-- Loop over the provided array of Invoice Header IDs
FOREACH v_invoice_header_id IN ARRAY p_invoice_ids
LOOP
-- Log the current invoice being processed
RAISE NOTICE 'Processing InvoiceHeaderId: %, PaymentStatusId: %, CompanyId: %, ModifiedBy: %',
v_invoice_header_id, p_payment_status_id, p_company_id, p_modified_by;
-- Retrieve the corresponding InvoicePaymentDetail for the given invoice_header_id
SELECT invoice_payment_header_id INTO v_invoice_payment_header.id
FROM public.invoice_payment_details
WHERE invoice_header_id = v_invoice_header_id -- Match invoice header ID in invoice_payment_details
AND is_deleted = false
LIMIT 1;
-- If payment detail exists, proceed to update payment header
IF FOUND THEN
RAISE NOTICE 'Invoice Payment Detail found for InvoiceHeaderId: %', v_invoice_header_id;
-- Retrieve the corresponding InvoicePaymentHeader using the payment header id
SELECT * INTO v_invoice_payment_header
FROM public.invoice_payment_headers
WHERE id = v_invoice_payment_header.id -- Match invoice payment header ID
AND is_deleted = false -- Ensure not deleted
LIMIT 1;
-- If the payment header exists, update its PaymentStatusId
IF FOUND THEN
RAISE NOTICE 'Updating PaymentStatusId for InvoicePaymentHeaderId: %', v_invoice_payment_header.id;
UPDATE public.invoice_payment_headers
SET payment_status_id = p_payment_status_id,
modified_by = p_modified_by,
modified_on_utc = NOW()
WHERE id = v_invoice_payment_header.id;
RAISE NOTICE 'PaymentStatusId updated for InvoicePaymentHeaderId: %', v_invoice_payment_header.id;
ELSE
RAISE NOTICE 'InvoicePaymentHeader not found for InvoiceHeaderId: %', v_invoice_header_id;
END IF;
ELSE
RAISE NOTICE 'InvoicePaymentDetail not found for InvoiceHeaderId: %', v_invoice_header_id;
END IF;
END LOOP;
-- No need for COMMIT, as the transaction will be committed by the caller
RAISE NOTICE 'Updated payment status for % invoices.', array_length(p_invoice_ids, 1);
END;
$function$
-- Function: get_invoices_by_customer_id
CREATE OR REPLACE FUNCTION public.get_invoices_by_customer_id(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_customer_id uuid, p_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
RETURNS TABLE(id uuid, invoice_number character varying, type integer, invoice_date date, customer_id uuid, customer_name character varying, due_date date, total_amount numeric, settled_amount numeric, invoice_status_id integer, invoice_status character varying, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, created_by_name character varying, modified_by_name character varying, is_created_by_scheduler boolean, has_next_status_approval boolean, next_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
DRAFT_STATUS CONSTANT integer := 1;
BEGIN
RETURN QUERY
WITH invoice_data AS (
-- Posted/normal invoices
SELECT
ih.id,
CAST(ih.invoice_number AS varchar) AS invoice_number,
ih.type,
ih.invoice_date::date AS invoice_date,
ih.customer_id,
c.name AS customer_name,
ih.due_date::date AS due_date,
ih.total_amount,
ih.settled_amount,
ih.invoice_status_id,
s.name AS invoice_status,
ih.currency_id,
ih.created_on_utc,
ih.modified_on_utc,
ih.created_by,
ih.modified_by,
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
ih.is_created_by_scheduler,
ih.credit_account_id
FROM public.invoice_headers ih
LEFT JOIN public.customers c ON c.id = ih.customer_id
LEFT JOIN public.invoice_statuses s ON s.id = ih.invoice_status_id
LEFT JOIN public.users cu ON cu.id = ih.created_by
LEFT JOIN public.users mu ON mu.id = ih.modified_by
WHERE ih.company_id = p_company_id
AND ih.customer_id = p_customer_id
AND ih.is_deleted = false
AND (c.is_deleted = false OR c.is_deleted IS NULL)
AND ih.invoice_date BETWEEN v_start_date AND v_end_date
AND (p_type_id IS NULL OR p_type_id = 0 OR ih.type = p_type_id)
UNION ALL
-- Draft invoices
SELECT
dih.id,
CAST(dih.invoice_number AS varchar) AS invoice_number,
dih.type,
dih.invoice_date::date AS invoice_date,
dih.customer_id,
c.name AS customer_name,
dih.due_date::date AS due_date,
dih.total_amount,
dih.settled_amount,
dih.invoice_status_id,
s.name AS invoice_status,
dih.currency_id,
dih.created_on_utc,
dih.modified_on_utc,
dih.created_by,
dih.modified_by,
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
dih.is_created_by_scheduler,
dih.credit_account_id
FROM public.draft_invoice_headers dih
LEFT JOIN public.customers c ON c.id = dih.customer_id
LEFT JOIN public.invoice_statuses s ON s.id = dih.invoice_status_id
LEFT JOIN public.users cu ON cu.id = dih.created_by
LEFT JOIN public.users mu ON mu.id = dih.modified_by
WHERE dih.company_id = p_company_id
AND dih.customer_id = p_customer_id
AND dih.is_deleted = false
AND (c.is_deleted = false OR c.is_deleted IS NULL)
AND dih.invoice_date BETWEEN v_start_date AND v_end_date
AND (p_type_id IS NULL OR p_type_id = 0 OR dih.type = p_type_id)
)
SELECT
id.id,
id.invoice_number,
id.type,
id.invoice_date,
id.customer_id,
id.customer_name,
id.due_date,
id.total_amount,
id.settled_amount,
id.invoice_status_id,
id.invoice_status,
id.currency_id,
id.created_on_utc,
id.modified_on_utc,
id.created_by,
id.modified_by,
id.created_by_name,
id.modified_by_name,
id.is_created_by_scheduler,
/* Same approval logic as get_all_invoices_from_span */
CASE
WHEN id.invoice_status_id = DRAFT_STATUS THEN true
WHEN EXISTS (
SELECT 1
FROM public.invoice_approval_users_account a
WHERE a.account_id = id.credit_account_id
AND a.status_id = id.invoice_status_id
AND a.user_id = p_user_id
AND a.is_deleted = false
) THEN true
WHEN EXISTS (
SELECT 1
FROM public.invoice_approval_user_company c
WHERE c.company_id = p_company_id
AND c.status_id = id.invoice_status_id
AND c.user_id = p_user_id
AND c.is_deleted = false
) THEN true
ELSE false
END AS has_next_status_approval,
/* Same next-status resolution as get_all_invoices_from_span */
COALESCE(
(SELECT iw.next_status
FROM public.invoice_workflow iw
WHERE iw.company_id = p_company_id
AND iw.status = id.invoice_status_id
AND iw.is_deleted = false
LIMIT 1),
0
) AS next_status_id
FROM invoice_data id;
END;
$function$
-- Function: list_scheduled_invoice_ids
CREATE OR REPLACE FUNCTION public.list_scheduled_invoice_ids(p_schedule_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP)
RETURNS TABLE(invoice_header_id uuid, draft_invoice_header_id uuid, company_id uuid, organization_id uuid)
LANGUAGE sql
AS $function$
WITH base AS (
SELECT
s.id AS schedule_id,
s.invoice_header_id AS invoice_header_id,
s.draft_invoice_header_id AS draft_invoice_header_id,
s.company_id AS company_id,
c.organization_id AS organization_id,
d.frequency_cycle,
d.day_of_week,
d.day_of_month,
d.month_of_year,
s.starts_from,
s.end_date
FROM public.recurring_sales_schedules s
JOIN public.recurrence_schedule_details d
ON d.schedule_id = s.id
AND d.is_deleted = false
JOIN public.companies c
ON c.id = s.company_id
WHERE s.is_deleted = false
AND s.schedule_status = 'active'
AND s.starts_from <= (p_schedule_date::date)
AND (s.end_date IS NULL OR s.end_date >= (p_schedule_date::date))
),
aligned AS (
SELECT
b.schedule_id,
b.invoice_header_id,
b.draft_invoice_header_id,
b.company_id,
b.organization_id
FROM base b
WHERE CASE lower(b.frequency_cycle)
WHEN 'daily' THEN TRUE
WHEN 'weekly' THEN b.day_of_week IS NOT NULL
AND b.day_of_week = EXTRACT(ISODOW FROM p_schedule_date)::int
WHEN 'monthly' THEN b.day_of_month IS NOT NULL
AND b.day_of_month = EXTRACT(DAY FROM p_schedule_date)::int
WHEN 'yearly' THEN b.month_of_year IS NOT NULL AND b.day_of_month IS NOT NULL
AND b.month_of_year = EXTRACT(MONTH FROM p_schedule_date)::int
AND b.day_of_month = EXTRACT(DAY FROM p_schedule_date)::int
ELSE FALSE
END
)
SELECT DISTINCT
a.invoice_header_id,
a.draft_invoice_header_id,
a.company_id,
a.organization_id
FROM aligned a
WHERE NOT EXISTS (
SELECT 1
FROM public.schedule_execution_logs l
WHERE l.schedule_id = a.schedule_id
AND l.execution_date = (p_schedule_date::date)
AND l.is_deleted = false
);
$function$
-- Function: get_invoice_by_id
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_invoice_by_id(p_invoice_header_id uuid)
RETURNS TABLE(id uuid, invoice_number text, credit_account_id uuid, invoice_voucher text, invoice_date timestamp with time zone, payment_term integer, customer_id uuid, customer_name character varying, customer_email text, customer_mobile_number text, customer_phone_number text, customer_gstin text, customer_short_name text, customer_pan text, customer_tan text, due_date timestamp with time zone, total_amount numeric, taxable_amount numeric, fees numeric, cgst_amount numeric, sgst_amount numeric, igst_amount numeric, settled_amount numeric, currency_id integer, invoice_status_id integer, invoice_status text, discount numeric, note text, round_off numeric, source_warehouse_id uuid, destination_warehouse_id uuid, destination_customer_id uuid, destination_warehouse_name text, destination_address_id uuid, destination_country_name text, destination_country_id uuid, destination_state_name text, destination_state_id uuid, destination_city_name text, destination_city_id uuid, destination_address_line1 text, destination_address_line2 text, destination_zip_code text, destination_gstin text, invoice_lines jsonb, type integer, current_approval_level integer, is_created_by_scheduler boolean, created_on_utc timestamp with time zone, modified_on_utc timestamp with time zone, created_by uuid, modified_by uuid)
LANGUAGE plpgsql
AS $function$
DECLARE
v_invoice_status_id integer;
BEGIN
-- Get the invoice status using the get_invoice_status function
v_invoice_status_id := public.get_invoice_status(p_invoice_header_id);
IF v_invoice_status_id >= 3 THEN
RETURN QUERY
SELECT
ih.id,
ih.invoice_number,
ih.credit_account_id,
ih.invoice_voucher,
ih.invoice_date::timestamp with time zone,
ih.payment_term,
ih.customer_id,
c.name AS customer_name,
c.email AS customer_email,
c.mobile_number AS customer_mobile_number,
c.phone_number AS customer_phone_number,
c.gstin AS customer_gstin,
c.short_name AS customer_short_name,
c.pan AS customer_pan,
c.tan AS customer_tan,
ih.due_date::timestamp with time zone,
ih.total_amount,
ih.taxable_amount,
ih.fees,
ih.cgst_amount,
ih.sgst_amount,
ih.igst_amount,
ih.settled_amount,
ih.currency_id,
ih.invoice_status_id,
invst.name :: text,
ih.discount,
ih.note,
ih.round_off,
ih.source_warehouse_id,
wd.id AS destination_warehouse_id,
wd.customer_id AS destination_customer_id,
wd.name AS destination_warehouse_name,
wd.address_id AS destination_address_id,
wd.country_name AS destination_country_name,
wd.country_id AS destination_country_id,
wd.state_name AS destination_state_name,
wd.state_id AS destination_state_id,
wd.city_name AS destination_city_name,
wd.city_id AS destination_city_id,
wd.address_line1 AS destination_address_line1,
wd.address_line2 AS destination_address_line2,
wd.zip_code AS destination_zip_code,
wd.gstin AS destination_gstin,
jsonb_agg(jsonb_build_object(
'invoice_line_id', il.id,
'product_id', il.product_id,
'price', il.price,
'quantity', il.quantity,
'fees', il.fees,
'discount', il.discount,
'taxable_amount', il.taxable_amount,
'sgst_amount', il.sgst_amount,
'cgst_amount', il.cgst_amount,
'igst_amount', il.igst_amount,
'total_amount', il.total_amount,
'serial_number', il.serial_number
) ORDER BY il.serial_number) AS invoice_lines,
ih.type,
ih.current_approval_level,
ih.is_created_by_scheduler,
ih.created_on_utc::timestamp with time zone,
ih.modified_on_utc::timestamp with time zone,
ih.created_by,
ih.modified_by
FROM invoice_headers ih
LEFT JOIN public.get_customer_details(ih.customer_id) c ON TRUE
LEFT JOIN public.get_warehouse_details(ih.destination_warehouse_id) wd ON TRUE
LEFT JOIN public.get_invoice_lines(ih.id, ih.invoice_status_id) il ON TRUE
JOIN public.invoice_statuses invst ON ih.invoice_status_id = invst.id
WHERE ih.id = p_invoice_header_id
GROUP BY
ih.id, ih.invoice_number, ih.credit_account_id, ih.invoice_voucher, ih.invoice_date,
ih.payment_term, ih.customer_id, c.name, c.email, c.mobile_number,
c.phone_number, c.gstin, c.short_name, c.pan, c.tan, ih.due_date,
ih.total_amount, ih.taxable_amount, ih.fees, ih.cgst_amount, ih.sgst_amount,
ih.igst_amount, ih.settled_amount, ih.currency_id, ih.invoice_status_id, invst.name, ih.discount, ih.note,
ih.round_off, ih.source_warehouse_id, wd.id, wd.customer_id, wd.name,
wd.address_id, wd.country_name, wd.country_id, wd.state_name, wd.state_id,
wd.city_name, wd.city_id, wd.address_line1, wd.address_line2, wd.zip_code,
wd.gstin,ih.type, ih.current_approval_level, ih.is_created_by_scheduler, ih.created_on_utc, ih.modified_on_utc, ih.created_by, ih.modified_by;
--order by il.serial_number;
ELSE
RETURN QUERY
SELECT
dih.id,
dih.invoice_number,
dih.credit_account_id,
dih.invoice_voucher,
dih.invoice_date::timestamp with time zone,
dih.payment_term,
dih.customer_id,
c.name AS customer_name,
c.email AS customer_email,
c.mobile_number AS customer_mobile_number,
c.phone_number AS customer_phone_number,
c.gstin AS customer_gstin,
c.short_name AS customer_short_name,
c.pan AS customer_pan,
c.tan AS customer_tan,
dih.due_date::timestamp with time zone,
dih.total_amount,
dih.taxable_amount,
dih.fees,
dih.cgst_amount,
dih.sgst_amount,
dih.igst_amount,
dih.settled_amount,
dih.currency_id,
dih.invoice_status_id,
dinvst.name :: text,
dih.discount,
dih.note,
dih.round_off,
dih.source_warehouse_id,
wd.id AS destination_warehouse_id,
wd.customer_id AS destination_customer_id,
wd.name AS destination_warehouse_name,
wd.address_id AS destination_address_id,
wd.country_name AS destination_country_name,
wd.country_id AS destination_country_id,
wd.state_name AS destination_state_name,
wd.state_id AS destination_state_id,
wd.city_name AS destination_city_name,
wd.city_id AS destination_city_id,
wd.address_line1 AS destination_address_line1,
wd.address_line2 AS destination_address_line2,
wd.zip_code AS destination_zip_code,
wd.gstin AS destination_gstin,
jsonb_agg(jsonb_build_object(
'invoice_line_id', il.id,
'product_id', il.product_id,
'price', il.price,
'quantity', il.quantity,
'fees', il.fees,
'discount', il.discount,
'taxable_amount', il.taxable_amount,
'sgst_amount', il.sgst_amount,
'cgst_amount', il.cgst_amount,
'igst_amount', il.igst_amount,
'total_amount', il.total_amount,
'serial_number', il.serial_number
) ORDER BY il.serial_number) AS invoice_lines,
dih.type,
dih.current_approval_level,
dih.is_created_by_scheduler,
dih.created_on_utc::timestamp with time zone,
dih.modified_on_utc::timestamp with time zone,
dih.created_by,
dih.modified_by
FROM draft_invoice_headers dih
LEFT JOIN public.get_customer_details(dih.customer_id) c ON TRUE
LEFT JOIN public.get_warehouse_details(dih.destination_warehouse_id) wd ON TRUE
LEFT JOIN public.get_invoice_lines(dih.id, dih.invoice_status_id) il ON TRUE
JOIN public.invoice_statuses dinvst ON dih.invoice_status_id = dinvst.id
WHERE dih.id = p_invoice_header_id
GROUP BY
dih.id, dih.invoice_number, dih.credit_account_id, dih.invoice_voucher, dih.invoice_date,
dih.payment_term, dih.customer_id, c.name, c.email, c.mobile_number,
c.phone_number, c.gstin, c.short_name, c.pan, c.tan, dih.due_date,
dih.total_amount, dih.taxable_amount, dih.fees, dih.cgst_amount, dih.sgst_amount,
dih.igst_amount, dih.settled_amount, dih.currency_id, dih.invoice_status_id, dinvst.name, dih.discount, dih.note,
dih.round_off, dih.source_warehouse_id, wd.id, wd.customer_id, wd.name,
wd.address_id, wd.country_name, wd.country_id, wd.state_name, wd.state_id,
wd.city_name, wd.city_id, wd.address_line1, wd.address_line2, wd.zip_code,
wd.gstin,dih.type, dih.current_approval_level, dih.is_created_by_scheduler, dih.created_on_utc, dih.modified_on_utc, dih.created_by, dih.modified_by;
--order by il.serial_number;
END IF;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_invoice_by_id(p_invoice_header_id uuid)
RETURNS TABLE(id uuid, invoice_number text, credit_account_id uuid, invoice_voucher text, invoice_date timestamp with time zone, payment_term integer, customer_id uuid, customer_name character varying, customer_email text, customer_mobile_number text, customer_phone_number text, customer_gstin text, customer_short_name text, customer_pan text, customer_tan text, due_date timestamp with time zone, total_amount numeric, taxable_amount numeric, fees numeric, cgst_amount numeric, sgst_amount numeric, igst_amount numeric, setteled_amount numeric, currency_id integer, invoice_status_id integer, invoice_status text, discount numeric, note text, round_off numeric, source_warehouse_id uuid, destination_warehouse_id uuid, destination_customer_id uuid, destination_warehouse_name text, destination_address_id uuid, destination_country_name text, destination_country_id uuid, destination_state_name text, destination_state_id uuid, destination_city_name text, destination_city_id uuid, destination_address_line1 text, destination_address_line2 text, destination_zip_code text, destination_gstin text, invoice_lines jsonb, type integer, current_approval_level integer, is_created_by_scheduler boolean, created_on_utc timestamp with time zone, modified_on_utc timestamp with time zone, created_by uuid, modified_by uuid)
LANGUAGE plpgsql
AS $function$
DECLARE
v_invoice_status_id integer;
BEGIN
-- Get the invoice status using the get_invoice_status function
v_invoice_status_id := public.get_invoice_status(p_invoice_header_id);
IF v_invoice_status_id >= 3 THEN
RETURN QUERY
SELECT
ih.id,
ih.invoice_number,
ih.credit_account_id,
ih.invoice_voucher,
ih.invoice_date::timestamp with time zone,
ih.payment_term,
ih.customer_id,
c.name AS customer_name,
c.email AS customer_email,
c.mobile_number AS customer_mobile_number,
c.phone_number AS customer_phone_number,
c.gstin AS customer_gstin,
c.short_name AS customer_short_name,
c.pan AS customer_pan,
c.tan AS customer_tan,
ih.due_date::timestamp with time zone,
ih.total_amount,
ih.taxable_amount,
ih.fees,
ih.cgst_amount,
ih.sgst_amount,
ih.igst_amount,
ih.setteled_amount,
ih.currency_id,
ih.invoice_status_id,
invst.name :: text,
ih.discount,
ih.note,
ih.round_off,
ih.source_warehouse_id,
wd.id AS destination_warehouse_id,
wd.customer_id AS destination_customer_id,
wd.name AS destination_warehouse_name,
wd.address_id AS destination_address_id,
wd.country_name AS destination_country_name,
wd.country_id AS destination_country_id,
wd.state_name AS destination_state_name,
wd.state_id AS destination_state_id,
wd.city_name AS destination_city_name,
wd.city_id AS destination_city_id,
wd.address_line1 AS destination_address_line1,
wd.address_line2 AS destination_address_line2,
wd.zip_code AS destination_zip_code,
wd.gstin AS destination_gstin,
jsonb_agg(jsonb_build_object(
'invoice_line_id', il.id,
'product_id', il.product_id,
'price', il.price,
'quantity', il.quantity,
'fees', il.fees,
'discount', il.discount,
'taxable_amount', il.taxable_amount,
'sgst_amount', il.sgst_amount,
'cgst_amount', il.cgst_amount,
'igst_amount', il.igst_amount,
'total_amount', il.total_amount,
'serial_number', il.serial_number
) ORDER BY il.serial_number) AS invoice_lines,
ih.type,
ih.current_approval_level,
ih.is_created_by_scheduler,
ih.created_on_utc::timestamp with time zone,
ih.modified_on_utc::timestamp with time zone,
ih.created_by,
ih.modified_by
FROM invoice_headers ih
LEFT JOIN public.get_customer_details(ih.customer_id) c ON TRUE
LEFT JOIN public.get_warehouse_details(ih.destination_warehouse_id) wd ON TRUE
LEFT JOIN public.get_invoice_lines(ih.id, ih.invoice_status_id) il ON TRUE
JOIN public.invoice_statuses invst ON ih.invoice_status_id = invst.id
WHERE ih.id = p_invoice_header_id
GROUP BY
ih.id, ih.invoice_number, ih.credit_account_id, ih.invoice_voucher, ih.invoice_date,
ih.payment_term, ih.customer_id, c.name, c.email, c.mobile_number,
c.phone_number, c.gstin, c.short_name, c.pan, c.tan, ih.due_date,
ih.total_amount, ih.taxable_amount, ih.fees, ih.cgst_amount, ih.sgst_amount,
ih.igst_amount, ih.setteled_amount, ih.currency_id, ih.invoice_status_id, invst.name, ih.discount, ih.note,
ih.round_off, ih.source_warehouse_id, wd.id, wd.customer_id, wd.name,
wd.address_id, wd.country_name, wd.country_id, wd.state_name, wd.state_id,
wd.city_name, wd.city_id, wd.address_line1, wd.address_line2, wd.zip_code,
wd.gstin,ih.type, ih.current_approval_level, ih.is_created_by_scheduler, ih.created_on_utc, ih.modified_on_utc, ih.created_by, ih.modified_by;
--order by il.serial_number;
ELSE
RETURN QUERY
SELECT
dih.id,
dih.invoice_number,
dih.credit_account_id,
dih.invoice_voucher,
dih.invoice_date::timestamp with time zone,
dih.payment_term,
dih.customer_id,
c.name AS customer_name,
c.email AS customer_email,
c.mobile_number AS customer_mobile_number,
c.phone_number AS customer_phone_number,
c.gstin AS customer_gstin,
c.short_name AS customer_short_name,
c.pan AS customer_pan,
c.tan AS customer_tan,
dih.due_date::timestamp with time zone,
dih.total_amount,
dih.taxable_amount,
dih.fees,
dih.cgst_amount,
dih.sgst_amount,
dih.igst_amount,
dih.setteled_amount,
dih.currency_id,
dih.invoice_status_id,
dinvst.name :: text,
dih.discount,
dih.note,
dih.round_off,
dih.source_warehouse_id,
wd.id AS destination_warehouse_id,
wd.customer_id AS destination_customer_id,
wd.name AS destination_warehouse_name,
wd.address_id AS destination_address_id,
wd.country_name AS destination_country_name,
wd.country_id AS destination_country_id,
wd.state_name AS destination_state_name,
wd.state_id AS destination_state_id,
wd.city_name AS destination_city_name,
wd.city_id AS destination_city_id,
wd.address_line1 AS destination_address_line1,
wd.address_line2 AS destination_address_line2,
wd.zip_code AS destination_zip_code,
wd.gstin AS destination_gstin,
jsonb_agg(jsonb_build_object(
'invoice_line_id', il.id,
'product_id', il.product_id,
'price', il.price,
'quantity', il.quantity,
'fees', il.fees,
'discount', il.discount,
'taxable_amount', il.taxable_amount,
'sgst_amount', il.sgst_amount,
'cgst_amount', il.cgst_amount,
'igst_amount', il.igst_amount,
'total_amount', il.total_amount,
'serial_number', il.serial_number
) ORDER BY il.serial_number) AS invoice_lines,
dih.type,
dih.current_approval_level,
dih.is_created_by_scheduler,
dih.created_on_utc::timestamp with time zone,
dih.modified_on_utc::timestamp with time zone,
dih.created_by,
dih.modified_by
FROM draft_invoice_headers dih
LEFT JOIN public.get_customer_details(dih.customer_id) c ON TRUE
LEFT JOIN public.get_warehouse_details(dih.destination_warehouse_id) wd ON TRUE
LEFT JOIN public.get_invoice_lines(dih.id, dih.invoice_status_id) il ON TRUE
JOIN public.invoice_statuses dinvst ON dih.invoice_status_id = dinvst.id
WHERE dih.id = p_invoice_header_id
GROUP BY
dih.id, dih.invoice_number, dih.credit_account_id, dih.invoice_voucher, dih.invoice_date,
dih.payment_term, dih.customer_id, c.name, c.email, c.mobile_number,
c.phone_number, c.gstin, c.short_name, c.pan, c.tan, dih.due_date,
dih.total_amount, dih.taxable_amount, dih.fees, dih.cgst_amount, dih.sgst_amount,
dih.igst_amount, dih.setteled_amount, dih.currency_id, dih.invoice_status_id, dinvst.name, dih.discount, dih.note,
dih.round_off, dih.source_warehouse_id, wd.id, wd.customer_id, wd.name,
wd.address_id, wd.country_name, wd.country_id, wd.state_name, wd.state_id,
wd.city_name, wd.city_id, wd.address_line1, wd.address_line2, wd.zip_code,
wd.gstin,dih.type, dih.current_approval_level, dih.is_created_by_scheduler, dih.created_on_utc, dih.modified_on_utc, dih.created_by, dih.modified_by;
--order by il.serial_number;
END IF;
END;
$function$
-- Function: fetch_all_invoices
-- SOURCE
CREATE OR REPLACE FUNCTION public.fetch_all_invoices(company_id uuid, fin_year_id integer)
RETURNS TABLE(invoice_id uuid, invoice_number text, credit_account_id uuid, invoice_voucher text, customer_id uuid, customer_name text, due_date date, invoice_date date, payment_term text, total_amount numeric, setteled_amount numeric, currency_id uuid, invoice_status text, invoice_status_id uuid, discount numeric, note text, created_on timestamp without time zone, modified_on timestamp without time zone, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, invoice_lines jsonb, invoice_type text, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
DECLARE
financial_year_start DATE := MAKE_DATE(fin_year_id, 4, 1);
financial_year_end DATE := MAKE_DATE(fin_year_id + 1, 3, 31);
BEGIN
RETURN QUERY
SELECT
ih.id AS invoice_id,
ih.invoice_number,
ih.credit_account_id,
ih.invoice_voucher,
ih.customer_id,
(SELECT c.name FROM customers c WHERE c.id = ih.customer_id) AS customer_name,
ih.due_date,
ih.invoice_date,
ih.payment_term,
ih.total_amount,
ih.settled_amount,
ih.currency_id,
(SELECT ist.name FROM invoice_statuses ist WHERE ist.id = ih.invoice_status_id) AS invoice_status,
ih.invoice_status_id,
ih.discount,
ih.note,
ih.created_on_utc AS created_on,
ih.modified_on_utc AS modified_on,
ih.created_by,
ih.modified_by,
ih.source_warehouse_id,
ih.destination_warehouse_id,
(SELECT w.name FROM warehouses w WHERE w.id = ih.destination_warehouse_id) AS destination_warehouse_name,
(SELECT JSONB_AGG(
JSONB_BUILD_OBJECT(
'id', id,
'product_id', product_id,
'price', price,
'quantity', quantity,
'fees', fees,
'discount', discount,
'taxable_amount', taxable_amount,
'sgst_amount', sgst_amount,
'cgst_amount', cgst_amount,
'igst_amount', igst_amount,
'total_amount', total_amount,
'serial_number', serial_number
)
)
FROM invoice_details
WHERE invoice_header_id = ih.id) AS invoice_lines,
ih.type AS invoice_type,
ih.is_created_by_scheduler
FROM invoice_headers ih
WHERE ih.company_id = company_id
AND ih.invoice_date BETWEEN financial_year_start AND financial_year_end
UNION ALL
SELECT
dih.id AS invoice_id,
dih.invoice_number,
dih.credit_account_id,
dih.invoice_voucher,
dih.customer_id,
(SELECT c.name FROM customers c WHERE c.id = dih.customer_id) AS customer_name,
dih.due_date,
dih.invoice_date,
dih.payment_term,
dih.total_amount,
dih.settled_amount,
dih.currency_id,
(SELECT ist.name FROM invoice_statuses ist WHERE ist.id = dih.invoice_status_id) AS invoice_status,
dih.invoice_status_id,
dih.discount,
dih.note,
dih.created_on_utc AS created_on,
dih.modified_on_utc AS modified_on,
dih.created_by,
dih.modified_by,
dih.source_warehouse_id,
dih.destination_warehouse_id,
(SELECT w.name FROM warehouses w WHERE w.id = dih.destination_warehouse_id) AS destination_warehouse_name,
(SELECT JSONB_AGG(
JSONB_BUILD_OBJECT(
'id', id,
'product_id', product_id,
'price', price,
'quantity', quantity,
'fees', fees,
'discount', discount,
'taxable_amount', taxable_amount,
'sgst_amount', sgst_amount,
'cgst_amount', cgst_amount,
'igst_amount', igst_amount,
'total_amount', total_amount,
'serial_number', serial_number
)
)
FROM draft_invoice_details
WHERE invoice_header_id = dih.id) AS invoice_lines,
dih.type AS invoice_type,
dih.is_created_by_scheduler
FROM draft_invoice_headers dih
WHERE dih.company_id = company_id
AND dih.invoice_date BETWEEN financial_year_start AND financial_year_end;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.fetch_all_invoices(company_id uuid, fin_year_id integer)
RETURNS TABLE(invoice_id uuid, invoice_number text, credit_account_id uuid, invoice_voucher text, customer_id uuid, customer_name text, due_date date, invoice_date date, payment_term text, total_amount numeric, setteled_amount numeric, currency_id uuid, invoice_status text, invoice_status_id uuid, discount numeric, note text, created_on timestamp without time zone, modified_on timestamp without time zone, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, invoice_lines jsonb, invoice_type text, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
DECLARE
financial_year_start DATE := MAKE_DATE(fin_year_id, 4, 1);
financial_year_end DATE := MAKE_DATE(fin_year_id + 1, 3, 31);
BEGIN
RETURN QUERY
SELECT
ih.id AS invoice_id,
ih.invoice_number,
ih.credit_account_id,
ih.invoice_voucher,
ih.customer_id,
(SELECT c.name FROM customers c WHERE c.id = ih.customer_id) AS customer_name,
ih.due_date,
ih.invoice_date,
ih.payment_term,
ih.total_amount,
ih.setteled_amount,
ih.currency_id,
(SELECT ist.name FROM invoice_statuses ist WHERE ist.id = ih.invoice_status_id) AS invoice_status,
ih.invoice_status_id,
ih.discount,
ih.note,
ih.created_on_utc AS created_on,
ih.modified_on_utc AS modified_on,
ih.created_by,
ih.modified_by,
ih.source_warehouse_id,
ih.destination_warehouse_id,
(SELECT w.name FROM warehouses w WHERE w.id = ih.destination_warehouse_id) AS destination_warehouse_name,
(SELECT JSONB_AGG(
JSONB_BUILD_OBJECT(
'id', id,
'product_id', product_id,
'price', price,
'quantity', quantity,
'fees', fees,
'discount', discount,
'taxable_amount', taxable_amount,
'sgst_amount', sgst_amount,
'cgst_amount', cgst_amount,
'igst_amount', igst_amount,
'total_amount', total_amount,
'serial_number', serial_number
)
)
FROM invoice_details
WHERE invoice_header_id = ih.id) AS invoice_lines,
ih.type AS invoice_type,
ih.is_created_by_scheduler
FROM invoice_headers ih
WHERE ih.company_id = company_id
AND ih.invoice_date BETWEEN financial_year_start AND financial_year_end
UNION ALL
SELECT
dih.id AS invoice_id,
dih.invoice_number,
dih.credit_account_id,
dih.invoice_voucher,
dih.customer_id,
(SELECT c.name FROM customers c WHERE c.id = dih.customer_id) AS customer_name,
dih.due_date,
dih.invoice_date,
dih.payment_term,
dih.total_amount,
dih.setteled_amount,
dih.currency_id,
(SELECT ist.name FROM invoice_statuses ist WHERE ist.id = dih.invoice_status_id) AS invoice_status,
dih.invoice_status_id,
dih.discount,
dih.note,
dih.created_on_utc AS created_on,
dih.modified_on_utc AS modified_on,
dih.created_by,
dih.modified_by,
dih.source_warehouse_id,
dih.destination_warehouse_id,
(SELECT w.name FROM warehouses w WHERE w.id = dih.destination_warehouse_id) AS destination_warehouse_name,
(SELECT JSONB_AGG(
JSONB_BUILD_OBJECT(
'id', id,
'product_id', product_id,
'price', price,
'quantity', quantity,
'fees', fees,
'discount', discount,
'taxable_amount', taxable_amount,
'sgst_amount', sgst_amount,
'cgst_amount', cgst_amount,
'igst_amount', igst_amount,
'total_amount', total_amount,
'serial_number', serial_number
)
)
FROM draft_invoice_details
WHERE invoice_header_id = dih.id) AS invoice_lines,
dih.type AS invoice_type,
dih.is_created_by_scheduler
FROM draft_invoice_headers dih
WHERE dih.company_id = company_id
AND dih.invoice_date BETWEEN financial_year_start AND financial_year_end;
END;
$function$
-- Function: apply_penalty_for_due_invoices
-- SOURCE
CREATE OR REPLACE FUNCTION public.apply_penalty_for_due_invoices(p_product_id uuid, p_company_id uuid, p_penalty_receivable_account_id uuid, p_revenue_account_id uuid, p_created_by uuid, p_batch_size integer DEFAULT 10, p_penalty_date date DEFAULT CURRENT_DATE)
RETURNS TABLE(invoice_header_id uuid, credit_account uuid, debit_account uuid, company_id uuid, customer_id uuid, invoice_number text, penalty_amount numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
cursor_invoice CURSOR FOR
SELECT
ih.id AS invoice_header_id,
ih.total_amount,
ih.settled_amount,
ih.customer_id,
ih.invoice_number,
ih.due_date,
pc.percentage as percentage,
pf.name AS frequency
FROM
invoice_headers ih
JOIN
penalty_configs pc ON ih.penalty_config_id = pc.id
JOIN
penalty_frequencies pf ON pc.penalty_frequency_id = pf.id
LEFT JOIN
penalty_processing_logs pl ON ih.id = pl.invoice_id AND pl.processing_date = p_penalty_date
WHERE
ih.company_id = p_company_id
AND public.check_penalty_date(ih.due_date::date, p_penalty_date) -- Apply penalty one day after the due date
AND ih.invoice_status_id IN (3, 4)-- Approved or Partially Paid
AND (pl.invoice_id IS NULL OR pl.status = 'failed'); -- Only process unprocessed or failed ones
invoice RECORD;
penalty_amount NUMERIC;
frequency_factor NUMERIC;
base_amount NUMERIC;
batch_count INT := 0;
invoice_header_id uuid;
invoice_details_id uuid;
BEGIN
-- Create a temporary table to store results
CREATE TEMP TABLE temp_penalty_results (
invoice_header_id UUID,
credit_account UUID,
debit_account UUID,
company_id UUID,
customer_id UUID,
invoice_number TEXT,
penalty_amount NUMERIC
) ON COMMIT DROP;
OPEN cursor_invoice;
LOOP
-- Begin a new transaction batch
BEGIN
-- Fetch the first record in the current batch
FETCH cursor_invoice INTO invoice;
EXIT WHEN NOT FOUND; -- Exit if no more records to process
-- Process up to p_batch_size records within this transaction
FOR i IN 1 .. p_batch_size LOOP
-- Exit the inner loop if there are no more records
EXIT WHEN NOT FOUND;
BEGIN
-- Track the penalty application start
INSERT INTO penalty_processing_logs (invoice_id, processing_date, status)
VALUES (invoice.invoice_header_id, p_penalty_date, 'pending')
ON CONFLICT (invoice_id, processing_date) DO UPDATE SET status = 'pending';
-- Calculate penalty amount
base_amount := CASE
WHEN invoice.settled_amount IS NOT NULL THEN (invoice.total_amount - invoice.settled_amount)
ELSE invoice.total_amount
END;
RAISE NOTICE 'base_amount : %', base_amount;
frequency_factor := CASE invoice.frequency
WHEN 'Yearly' THEN (invoice.percentage / 100) / 12
WHEN 'Monthly' THEN (invoice.percentage / 100)
ELSE 0
END;
RAISE NOTICE 'frequency_factor : %', frequency_factor;
penalty_amount := ROUND(base_amount * frequency_factor);
RAISE NOTICE 'penalty_amount : %', penalty_amount;
SELECT gen_random_uuid() INTO invoice_details_id;
SELECT gen_random_uuid() INTO invoice_header_id;
-- Insert penalty into invoice_details
--insert into invoice_headers
CALL public.create_invoice_header(
invoice_header_id,
p_company_id,
invoice.customer_id,
p_penalty_receivable_account_id,
p_revenue_account_id,
p_penalty_date,
invoice.due_date::date,
0,
penalty_amount,
0,
0,
0,
penalty_amount,
0,
0,
0,
1,
'penalty charges',
3,
p_created_by,
'inv',
'00000000-0000-0000-0000-000000000000',
'00000000-0000-0000-0000-000000000000',
null,
null,
5
);
--insert into invoice_details
CALL public.create_invoice_detail(
invoice_header_id,
penalty_amount,
1,
0,
0,
0,
0,
0,
penalty_amount,
penalty_amount,
1,
p_product_id
);
RAISE NOTICE 'successful inserted in invoiceDetails';
-- Mark the penalty application as completed in the log
UPDATE penalty_processing_logs
SET status = 'completed'
WHERE invoice_id = invoice.invoice_header_id;
RAISE NOTICE 'successful updated in penalty_processing_logs';
-- Insert data into the temporary table for returning at the end
INSERT INTO temp_penalty_results (
invoice_header_id, credit_account, debit_account, company_id, customer_id, invoice_number, penalty_amount
) VALUES (
invoice.invoice_header_id, p_revenue_account_id, p_penalty_receivable_account_id, p_company_id,
invoice.customer_id, invoice.invoice_number, penalty_amount
);
-- Fetch the next invoice for processing within the same batch
FETCH cursor_invoice INTO invoice;
EXCEPTION
WHEN OTHERS THEN
-- Mark the failed invoices as 'failed' in the log
UPDATE penalty_processing_logs
SET status = 'failed'
WHERE invoice_id = invoice.invoice_header_id
AND processing_date = p_penalty_date;
-- Log the error
RAISE NOTICE 'Error in processing invoice %: %', invoice.invoice_header_id, SQLERRM;
END;
END LOOP;
-- Increment batch count for reference
batch_count := batch_count + 1;
END;
END LOOP;
CLOSE cursor_invoice;
-- Return all data from the temporary table at the end
RETURN QUERY SELECT * FROM temp_penalty_results;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.apply_penalty_for_due_invoices(p_product_id uuid, p_company_id uuid, p_penalty_receivable_account_id uuid, p_revenue_account_id uuid, p_created_by uuid, p_batch_size integer DEFAULT 10, p_penalty_date date DEFAULT CURRENT_DATE)
RETURNS TABLE(invoice_header_id uuid, credit_account uuid, debit_account uuid, company_id uuid, customer_id uuid, invoice_number text, penalty_amount numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
cursor_invoice CURSOR FOR
SELECT
ih.id AS invoice_header_id,
ih.total_amount,
ih.setteled_amount,
ih.customer_id,
ih.invoice_number,
ih.due_date,
pc.percentage as percentage,
pf.name AS frequency
FROM
invoice_headers ih
JOIN
penalty_configs pc ON ih.penalty_config_id = pc.id
JOIN
penalty_frequencies pf ON pc.penalty_frequency_id = pf.id
LEFT JOIN
penalty_processing_logs pl ON ih.id = pl.invoice_id AND pl.processing_date = p_penalty_date
WHERE
ih.company_id = p_company_id
AND public.check_penalty_date(ih.due_date::date, p_penalty_date) -- Apply penalty one day after the due date
AND ih.invoice_status_id IN (3, 4)-- Approved or Partially Paid
AND (pl.invoice_id IS NULL OR pl.status = 'failed'); -- Only process unprocessed or failed ones
invoice RECORD;
penalty_amount NUMERIC;
frequency_factor NUMERIC;
base_amount NUMERIC;
batch_count INT := 0;
invoice_header_id uuid;
invoice_details_id uuid;
BEGIN
-- Create a temporary table to store results
CREATE TEMP TABLE temp_penalty_results (
invoice_header_id UUID,
credit_account UUID,
debit_account UUID,
company_id UUID,
customer_id UUID,
invoice_number TEXT,
penalty_amount NUMERIC
) ON COMMIT DROP;
OPEN cursor_invoice;
LOOP
-- Begin a new transaction batch
BEGIN
-- Fetch the first record in the current batch
FETCH cursor_invoice INTO invoice;
EXIT WHEN NOT FOUND; -- Exit if no more records to process
-- Process up to p_batch_size records within this transaction
FOR i IN 1 .. p_batch_size LOOP
-- Exit the inner loop if there are no more records
EXIT WHEN NOT FOUND;
BEGIN
-- Track the penalty application start
INSERT INTO penalty_processing_logs (invoice_id, processing_date, status)
VALUES (invoice.invoice_header_id, p_penalty_date, 'pending')
ON CONFLICT (invoice_id, processing_date) DO UPDATE SET status = 'pending';
-- Calculate penalty amount
base_amount := CASE
WHEN invoice.setteled_amount IS NOT NULL THEN (invoice.total_amount - invoice.setteled_amount)
ELSE invoice.total_amount
END;
RAISE NOTICE 'base_amount : %', base_amount;
frequency_factor := CASE invoice.frequency
WHEN 'Yearly' THEN (invoice.percentage / 100) / 12
WHEN 'Monthly' THEN (invoice.percentage / 100)
ELSE 0
END;
RAISE NOTICE 'frequency_factor : %', frequency_factor;
penalty_amount := ROUND(base_amount * frequency_factor);
RAISE NOTICE 'penalty_amount : %', penalty_amount;
SELECT gen_random_uuid() INTO invoice_details_id;
SELECT gen_random_uuid() INTO invoice_header_id;
-- Insert penalty into invoice_details
--insert into invoice_headers
CALL public.create_invoice_header(
invoice_header_id,
p_company_id,
invoice.customer_id,
p_penalty_receivable_account_id,
p_revenue_account_id,
p_penalty_date,
invoice.due_date::date,
0,
penalty_amount,
0,
0,
0,
penalty_amount,
0,
0,
0,
1,
'penalty charges',
3,
p_created_by,
'inv',
'00000000-0000-0000-0000-000000000000',
'00000000-0000-0000-0000-000000000000',
null,
null,
5
);
--insert into invoice_details
CALL public.create_invoice_detail(
invoice_header_id,
penalty_amount,
1,
0,
0,
0,
0,
0,
penalty_amount,
penalty_amount,
1,
p_product_id
);
RAISE NOTICE 'successful inserted in invoiceDetails';
-- Mark the penalty application as completed in the log
UPDATE penalty_processing_logs
SET status = 'completed'
WHERE invoice_id = invoice.invoice_header_id;
RAISE NOTICE 'successful updated in penalty_processing_logs';
-- Insert data into the temporary table for returning at the end
INSERT INTO temp_penalty_results (
invoice_header_id, credit_account, debit_account, company_id, customer_id, invoice_number, penalty_amount
) VALUES (
invoice.invoice_header_id, p_revenue_account_id, p_penalty_receivable_account_id, p_company_id,
invoice.customer_id, invoice.invoice_number, penalty_amount
);
-- Fetch the next invoice for processing within the same batch
FETCH cursor_invoice INTO invoice;
EXCEPTION
WHEN OTHERS THEN
-- Mark the failed invoices as 'failed' in the log
UPDATE penalty_processing_logs
SET status = 'failed'
WHERE invoice_id = invoice.invoice_header_id
AND processing_date = p_penalty_date;
-- Log the error
RAISE NOTICE 'Error in processing invoice %: %', invoice.invoice_header_id, SQLERRM;
END;
END LOOP;
-- Increment batch count for reference
batch_count := batch_count + 1;
END;
END LOOP;
CLOSE cursor_invoice;
-- Return all data from the temporary table at the end
RETURN QUERY SELECT * FROM temp_penalty_results;
END;
$function$
-- Function: get_all_invoices
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_all_invoices(company_id uuid, fin_year_id integer)
RETURNS TABLE(invoice_id uuid, invoice_number character varying, credit_account_id uuid, invoice_voucher character varying, customer_id uuid, customer_name character varying, due_date date, invoice_date date, payment_term character varying, total_amount numeric, settled_amount numeric, currency_id integer, invoice_status character varying, invoice_status_id integer, discount numeric, note character varying, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name character varying, invoice_lines jsonb, type character varying, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ih.id AS invoice_id,
ih.invoice_number::character varying, -- Cast to character varying
ih.credit_account_id,
ih.invoice_voucher::character varying, -- Cast to character varying
ih.customer_id,
(SELECT c.name::character varying FROM customers c WHERE c.id = ih.customer_id) AS customer_name,
ih.due_date::date, -- Cast to date
ih.invoice_date::date, -- Cast to date
ih.payment_term::character varying, -- Cast to character varying
ih.total_amount,
ih.settled_amount,
ih.currency_id,
(SELECT s.name::character varying FROM invoice_statuses s WHERE s.id = ih.invoice_status_id) AS invoice_status,
ih.invoice_status_id, -- Already integer
ih.discount,
ih.note::character varying, -- Cast to character varying
ih.created_on_utc,
ih.modified_on_utc,
ih.created_by,
ih.modified_by,
ih.source_warehouse_id,
ih.destination_warehouse_id,
(SELECT w.name::character varying FROM warehouses w WHERE w.id = ih.destination_warehouse_id) AS destination_warehouse_name,
(
SELECT jsonb_agg(
jsonb_build_object(
'id', d.id,
'product_id', d.product_id,
'price', d.price,
'quantity', d.quantity,
'fees', d.fees,
'discount', d.discount,
'taxable_amount', d.taxable_amount,
'sgst_amount', d.sgst_amount,
'cgst_amount', d.cgst_amount,
'igst_amount', d.igst_amount,
'total_amount', d.total_amount,
'serial_number', d.serial_number
)
)
FROM invoice_details d WHERE d.invoice_header_id = ih.id
) AS invoice_lines,
ih.type::character varying, -- Cast to character varying
ih.is_created_by_scheduler
FROM invoice_headers ih
WHERE ih.company_id = get_all_invoices.company_id
AND ih.invoice_date BETWEEN MAKE_DATE(get_all_invoices.fin_year_id, 4, 1) AND MAKE_DATE(get_all_invoices.fin_year_id + 1, 3, 31)
UNION ALL
SELECT
dih.id AS invoice_id,
dih.invoice_number::character varying, -- Cast to character varying
dih.credit_account_id,
dih.invoice_voucher::character varying, -- Cast to character varying
dih.customer_id,
(SELECT c.name::character varying FROM customers c WHERE c.id = dih.customer_id) AS customer_name,
dih.due_date::date, -- Cast to date
dih.invoice_date::date, -- Cast to date
dih.payment_term::character varying, -- Cast to character varying
dih.total_amount,
dih.settled_amount,
dih.currency_id,
(SELECT s.name::character varying FROM invoice_statuses s WHERE s.id = dih.invoice_status_id) AS invoice_status,
dih.invoice_status_id, -- Already integer
dih.discount,
dih.note::character varying, -- Cast to character varying
dih.created_on_utc,
dih.modified_on_utc,
dih.created_by,
dih.modified_by,
dih.source_warehouse_id,
dih.destination_warehouse_id,
(SELECT w.name::character varying FROM warehouses w WHERE w.id = dih.destination_warehouse_id) AS destination_warehouse_name,
(
SELECT jsonb_agg(
jsonb_build_object(
'id', d.id,
'product_id', d.product_id,
'price', d.price,
'quantity', d.quantity,
'fees', d.fees,
'discount', d.discount,
'taxable_amount', d.taxable_amount,
'sgst_amount', d.sgst_amount,
'cgst_amount', d.cgst_amount,
'igst_amount', d.igst_amount,
'total_amount', d.total_amount,
'serial_number', d.serial_number
)
)
FROM draft_invoice_details d WHERE d.invoice_header_id = dih.id
) AS invoice_lines,
dih.type::character varying, -- Cast to character varying
dih.is_created_by_scheduler
FROM draft_invoice_headers dih
WHERE dih.company_id = get_all_invoices.company_id
AND dih.invoice_date BETWEEN MAKE_DATE(get_all_invoices.fin_year_id, 4, 1) AND MAKE_DATE(get_all_invoices.fin_year_id + 1, 3, 31);
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_all_invoices(company_id uuid, fin_year_id integer)
RETURNS TABLE(invoice_id uuid, invoice_number character varying, credit_account_id uuid, invoice_voucher character varying, customer_id uuid, customer_name character varying, due_date date, invoice_date date, payment_term character varying, total_amount numeric, settled_amount numeric, currency_id integer, invoice_status character varying, invoice_status_id integer, discount numeric, note character varying, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name character varying, invoice_lines jsonb, type character varying, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ih.id AS invoice_id,
ih.invoice_number::character varying, -- Cast to character varying
ih.credit_account_id,
ih.invoice_voucher::character varying, -- Cast to character varying
ih.customer_id,
(SELECT c.name::character varying FROM customers c WHERE c.id = ih.customer_id) AS customer_name,
ih.due_date::date, -- Cast to date
ih.invoice_date::date, -- Cast to date
ih.payment_term::character varying, -- Cast to character varying
ih.total_amount,
ih.setteled_amount,
ih.currency_id,
(SELECT s.name::character varying FROM invoice_statuses s WHERE s.id = ih.invoice_status_id) AS invoice_status,
ih.invoice_status_id, -- Already integer
ih.discount,
ih.note::character varying, -- Cast to character varying
ih.created_on_utc,
ih.modified_on_utc,
ih.created_by,
ih.modified_by,
ih.source_warehouse_id,
ih.destination_warehouse_id,
(SELECT w.name::character varying FROM warehouses w WHERE w.id = ih.destination_warehouse_id) AS destination_warehouse_name,
(
SELECT jsonb_agg(
jsonb_build_object(
'id', d.id,
'product_id', d.product_id,
'price', d.price,
'quantity', d.quantity,
'fees', d.fees,
'discount', d.discount,
'taxable_amount', d.taxable_amount,
'sgst_amount', d.sgst_amount,
'cgst_amount', d.cgst_amount,
'igst_amount', d.igst_amount,
'total_amount', d.total_amount,
'serial_number', d.serial_number
)
)
FROM invoice_details d WHERE d.invoice_header_id = ih.id
) AS invoice_lines,
ih.type::character varying, -- Cast to character varying
ih.is_created_by_scheduler
FROM invoice_headers ih
WHERE ih.company_id = get_all_invoices.company_id
AND ih.invoice_date BETWEEN MAKE_DATE(get_all_invoices.fin_year_id, 4, 1) AND MAKE_DATE(get_all_invoices.fin_year_id + 1, 3, 31)
UNION ALL
SELECT
dih.id AS invoice_id,
dih.invoice_number::character varying, -- Cast to character varying
dih.credit_account_id,
dih.invoice_voucher::character varying, -- Cast to character varying
dih.customer_id,
(SELECT c.name::character varying FROM customers c WHERE c.id = dih.customer_id) AS customer_name,
dih.due_date::date, -- Cast to date
dih.invoice_date::date, -- Cast to date
dih.payment_term::character varying, -- Cast to character varying
dih.total_amount,
dih.setteled_amount,
dih.currency_id,
(SELECT s.name::character varying FROM invoice_statuses s WHERE s.id = dih.invoice_status_id) AS invoice_status,
dih.invoice_status_id, -- Already integer
dih.discount,
dih.note::character varying, -- Cast to character varying
dih.created_on_utc,
dih.modified_on_utc,
dih.created_by,
dih.modified_by,
dih.source_warehouse_id,
dih.destination_warehouse_id,
(SELECT w.name::character varying FROM warehouses w WHERE w.id = dih.destination_warehouse_id) AS destination_warehouse_name,
(
SELECT jsonb_agg(
jsonb_build_object(
'id', d.id,
'product_id', d.product_id,
'price', d.price,
'quantity', d.quantity,
'fees', d.fees,
'discount', d.discount,
'taxable_amount', d.taxable_amount,
'sgst_amount', d.sgst_amount,
'cgst_amount', d.cgst_amount,
'igst_amount', d.igst_amount,
'total_amount', d.total_amount,
'serial_number', d.serial_number
)
)
FROM draft_invoice_details d WHERE d.invoice_header_id = dih.id
) AS invoice_lines,
dih.type::character varying, -- Cast to character varying
dih.is_created_by_scheduler
FROM draft_invoice_headers dih
WHERE dih.company_id = get_all_invoices.company_id
AND dih.invoice_date BETWEEN MAKE_DATE(get_all_invoices.fin_year_id, 4, 1) AND MAKE_DATE(get_all_invoices.fin_year_id + 1, 3, 31);
END;
$function$
-- Function: get_grouped_invoice_header
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_header(p_company_id uuid, p_fin_year_id integer)
RETURNS TABLE(id uuid, group_invoice_number text, grouped_invoice_template_id uuid, execution_date timestamp without time zone, error_message text, success_count integer, total_count integer, invoice_description text, group_invoice_batch_id text, paid_count integer, total_paid_amount numeric, original_total_amount numeric, remaining_amount numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
BEGIN
RETURN QUERY
SELECT
gih.id,
gih.group_invoice_number,
gih.group_invoice_template_id,
gih.execution_date,
gih.error_message,
gih.success_count,
gih.total_count,
templates.invoice_description,
gih.group_invoice_batch_id,
/* Paid invoice count (fully settled invoices only) */
CAST(
COALESCE(
SUM(
CASE
WHEN ih.settled_amount >= ih.total_amount THEN 1
ELSE 0
END
),
0
) AS integer
) AS paid_count,
/* ✅ FIX: Cap payment per invoice */
COALESCE(
SUM(
LEAST(ih.settled_amount, ih.total_amount)
),
0
) AS total_paid_amount,
/* Original invoice total */
COALESCE(
SUM(ih.total_amount),
0
) AS original_total_amount,
/* ✅ FIX: Never negative remaining */
COALESCE(
SUM(
GREATEST(ih.total_amount - ih.settled_amount, 0)
),
0
) AS remaining_amount
FROM public.group_invoice_headers gih
INNER JOIN public.group_invoice_templates templates
ON templates.id = gih.group_invoice_template_id
LEFT JOIN public.group_invoice_details gid
ON gid.group_invoice_header_id = gih.id
AND gid.is_deleted = false
AND gid.company_id = p_company_id
LEFT JOIN public.invoice_headers ih
ON ih.id = gid.invoice_header_id
AND ih.is_deleted = false
AND ih.company_id = p_company_id
WHERE gih.company_id = p_company_id
AND gih.execution_date::date BETWEEN v_start_date AND v_end_date
GROUP BY
gih.id,
gih.group_invoice_number,
gih.group_invoice_template_id,
gih.execution_date,
gih.error_message,
gih.success_count,
gih.total_count,
templates.invoice_description,
gih.group_invoice_batch_id
ORDER BY gih.execution_date DESC;
END;
$function$
-- Function: get_grouped_invoice_details
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_details(p_group_invoice_header_id uuid)
RETURNS TABLE(invoice_header_id uuid, invoice_number text, invoice_date timestamp without time zone, payment_term numeric, due_date timestamp without time zone, total_amount numeric, settled_amount numeric, taxable_amount numeric, discount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, round_off numeric, note text, customer_id uuid, customer_name text, invoice_status_id integer, invoice_status text, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, credit_account_id uuid, invoice_voucher text, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, type integer, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ih.id AS invoice_header_id,
ih.invoice_number::text AS invoice_number, -- Explicit cast
ih.invoice_date,
ih.payment_term::numeric AS payment_term, -- Cast to numeric
ih.due_date,
ih.total_amount,
ih.settled_amount,
ih.taxable_amount,
ih.discount,
ih.sgst_amount,
ih.cgst_amount,
ih.igst_amount,
ih.round_off,
ih.note::text AS note, -- Explicit cast
ih.customer_id,
COALESCE(c.name, 'Unknown')::text AS customer_name, -- Explicit cast
ih.invoice_status_id,
COALESCE(s.name, 'Unknown')::text AS invoice_status, -- Explicit cast
ih.currency_id,
ih.created_on_utc,
ih.modified_on_utc,
ih.credit_account_id,
ih.invoice_voucher::text AS invoice_voucher, -- Explicit cast
ih.created_by,
ih.modified_by,
ih.source_warehouse_id,
ih.destination_warehouse_id,
COALESCE(w.name, 'Unknown')::text AS destination_warehouse_name, -- Explicit cast
ih.type,
ih.is_created_by_scheduler
FROM
public.group_invoice_details aid
INNER JOIN
invoice_headers ih ON aid.invoice_header_id = ih.id
LEFT JOIN
customers c ON ih.customer_id = c.id
LEFT JOIN
invoice_statuses s ON ih.invoice_status_id = s.id
LEFT JOIN
warehouses w ON ih.destination_warehouse_id = w.id
WHERE
aid.group_invoice_header_id = p_group_invoice_header_id
AND NOT aid.is_deleted
AND NOT ih.is_deleted;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_details(p_group_invoice_header_id uuid)
RETURNS TABLE(invoice_header_id uuid, invoice_number text, invoice_date timestamp without time zone, payment_term numeric, due_date timestamp without time zone, total_amount numeric, setteled_amount numeric, taxable_amount numeric, discount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, round_off numeric, note text, customer_id uuid, customer_name text, invoice_status_id integer, invoice_status text, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, credit_account_id uuid, invoice_voucher text, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, type integer, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ih.id AS invoice_header_id,
ih.invoice_number::text AS invoice_number, -- Explicit cast
ih.invoice_date,
ih.payment_term::numeric AS payment_term, -- Cast to numeric
ih.due_date,
ih.total_amount,
ih.setteled_amount,
ih.taxable_amount,
ih.discount,
ih.sgst_amount,
ih.cgst_amount,
ih.igst_amount,
ih.round_off,
ih.note::text AS note, -- Explicit cast
ih.customer_id,
COALESCE(c.name, 'Unknown')::text AS customer_name, -- Explicit cast
ih.invoice_status_id,
COALESCE(s.name, 'Unknown')::text AS invoice_status, -- Explicit cast
ih.currency_id,
ih.created_on_utc,
ih.modified_on_utc,
ih.credit_account_id,
ih.invoice_voucher::text AS invoice_voucher, -- Explicit cast
ih.created_by,
ih.modified_by,
ih.source_warehouse_id,
ih.destination_warehouse_id,
COALESCE(w.name, 'Unknown')::text AS destination_warehouse_name, -- Explicit cast
ih.type,
ih.is_created_by_scheduler
FROM
public.group_invoice_details aid
INNER JOIN
invoice_headers ih ON aid.invoice_header_id = ih.id
LEFT JOIN
customers c ON ih.customer_id = c.id
LEFT JOIN
invoice_statuses s ON ih.invoice_status_id = s.id
LEFT JOIN
warehouses w ON ih.destination_warehouse_id = w.id
WHERE
aid.group_invoice_header_id = p_group_invoice_header_id
AND NOT aid.is_deleted
AND NOT ih.is_deleted;
END;
$function$
-- Function: get_unit_dues_by_cust_id
CREATE OR REPLACE FUNCTION public.get_unit_dues_by_cust_id(p_company_id uuid, p_customer_id uuid)
RETURNS TABLE(id uuid, name text, total_due numeric, total_paid numeric, original_amount numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
c.id::uuid,
c.name::text,
COALESCE(SUM(
CASE
WHEN i.due_date < now()
AND i.is_deleted = false
AND (i.total_amount - i.settled_amount) > 0
THEN (i.total_amount - i.settled_amount)
ELSE 0
END
), 0)::numeric AS total_due,
COALESCE(SUM(
CASE
WHEN i.due_date < now()
AND i.is_deleted = false
THEN i.settled_amount
ELSE 0
END
), 0)::numeric AS total_paid,
COALESCE(SUM(i.total_amount), 0)::numeric AS original_amount
FROM public.customers c
LEFT JOIN public.invoice_headers i
ON i.customer_id = c.id AND i.company_id = p_company_id
WHERE c.is_deleted = false
AND c.company_id = p_company_id
AND c.id = p_customer_id
GROUP BY c.id, c.name;
END;
$function$
-- Function: get_all_invoice_account_approvers
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_all_invoice_account_approvers(p_company_id uuid)
RETURNS TABLE(id integer, account_id uuid, status_id integer, user_id uuid, approval_level integer, required_approval_levels integer)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ia.id,
ia.account_id,
ia.status_id, -- Use ia.status_id here (the user's approval status)
ia.user_id,
ia.approval_level,
iaal.approval_level_required AS required_approval_levels
FROM invoice_approval_users_account ia
JOIN invoice_account_approval_levels iaal
ON ia.company_id = iaal.company_id
AND ia.account_id = iaal.account_id
WHERE ia.company_id = p_company_id
AND ia.is_deleted = FALSE
AND iaal.is_deleted = FALSE
AND iaal.status_id = 2 -- Filter for status_id = 2
ORDER BY ia.account_id, ia.status_id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_all_invoice_account_approvers(p_company_id uuid)
RETURNS TABLE(id integer, account_id uuid, status_id integer, user_id uuid, approval_level integer)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ia.id,
ia.account_id,
ia.status_id,
ia.user_id,
ia.approval_level
FROM invoice_approval_users_account ia
WHERE ia.company_id = p_company_id
AND ia.is_deleted = false
ORDER BY ia.account_id, ia.status_id;
END;
$function$
-- Function: get_all_invoice_headers
CREATE OR REPLACE FUNCTION public.get_all_invoice_headers(p_company_id uuid)
RETURNS TABLE(id uuid, invoice_number text, invoice_voucher text, customer_id uuid, customer_name text, due_date timestamp with time zone, invoice_date timestamp with time zone, payment_term integer, total_amount numeric, settled_amount numeric, discount numeric, note text, currency_id integer, invoice_status_id integer, invoice_status text, type integer, is_created_by_scheduler boolean, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
-- Query InvoiceHeaders
SELECT header.id, header.invoice_number, header.invoice_voucher, header.customer_id,
customer.name::text AS customer_name,
header.due_date::timestamptz, header.invoice_date::timestamptz, header.payment_term,
header.total_amount, header.settled_amount, header.discount, header.note,
header.currency_id, header.invoice_status_id, status.name::text AS invoice_status, header.type, header.is_created_by_scheduler,
header.created_by,
COALESCE(u_created.first_name || ' ' || u_created.last_name, 'N/A') AS created_by_name, -- full name or 'N/A'
header.modified_by,
COALESCE(u_modified.first_name || ' ' || u_modified.last_name, 'N/A') AS modified_by_name -- full name or 'N/A'
FROM invoice_headers header
JOIN customers customer ON customer.id = header.customer_id
JOIN invoice_statuses status ON status.id = header.invoice_status_id
LEFT JOIN users u_created ON u_created.id = header.created_by -- join to get the created_by user's full name
LEFT JOIN users u_modified ON u_modified.id = header.modified_by -- join to get the modified_by user's full name
WHERE header.company_id = p_company_id
AND header.is_deleted = false
UNION ALL
-- Query DraftInvoiceHeaders
SELECT draft.id, draft.invoice_number, draft.invoice_voucher, draft.customer_id,
customer.name::text AS customer_name,
draft.due_date::timestamptz, draft.invoice_date::timestamptz, draft.payment_term,
draft.total_amount, draft.settled_amount, draft.discount, draft.note,
draft.currency_id, draft.invoice_status_id, status.name::text AS invoice_status, draft.type, draft.is_created_by_scheduler,
draft.created_by,
COALESCE(u_created.first_name || ' ' || u_created.last_name, 'N/A') AS created_by_name, -- full name or 'N/A'
draft.modified_by,
COALESCE(u_modified.first_name || ' ' || u_modified.last_name, 'N/A') AS modified_by_name -- full name or 'N/A'
FROM draft_invoice_headers draft
JOIN customers customer ON customer.id = draft.customer_id
JOIN invoice_statuses status ON status.id = draft.invoice_status_id
LEFT JOIN users u_created ON u_created.id = draft.created_by -- join to get the created_by user's full name
LEFT JOIN users u_modified ON u_modified.id = draft.modified_by -- join to get the modified_by user's full name
WHERE draft.company_id = p_company_id
AND draft.is_deleted = false;
END;
$function$
-- Function: get_invoice_header_by_id
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_invoice_header_by_id(p_invoice_header_id uuid)
RETURNS TABLE(id uuid, invoice_number text, invoice_voucher text, customer_id uuid, customer_name text, due_date timestamp with time zone, invoice_date timestamp with time zone, payment_term integer, total_amount numeric, settled_amount numeric, discount numeric, note text, currency_id integer, invoice_status_id integer, invoice_status text, type integer, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
-- Query for the specific InvoiceHeader
SELECT header.id,
header.invoice_number,
header.invoice_voucher,
customer.id AS customer_id,
customer.name::text AS customer_name, -- Cast to text
header.due_date::timestamptz,
header.invoice_date::timestamptz,
header.payment_term,
header.total_amount,
header.settled_amount,
header.discount,
header.note,
header.currency_id,
status.id AS invoice_status_id,
status.name::text AS invoice_status, -- Cast to text
header.type,
header.is_created_by_scheduler
FROM invoice_headers header
JOIN customers customer ON customer.id = header.customer_id
JOIN invoice_statuses status ON status.id = header.invoice_status_id
WHERE header.id = p_invoice_header_id
AND header.is_deleted = false;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_invoice_header_by_id(p_invoice_header_id uuid)
RETURNS TABLE(id uuid, invoice_number text, invoice_voucher text, customer_id uuid, customer_name text, due_date timestamp with time zone, invoice_date timestamp with time zone, payment_term integer, total_amount numeric, setteled_amount numeric, discount numeric, note text, currency_id integer, invoice_status_id integer, invoice_status text, type integer, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
-- Query for the specific InvoiceHeader
SELECT header.id,
header.invoice_number,
header.invoice_voucher,
customer.id AS customer_id,
customer.name::text AS customer_name, -- Cast to text
header.due_date::timestamptz,
header.invoice_date::timestamptz,
header.payment_term,
header.total_amount,
header.setteled_amount,
header.discount,
header.note,
header.currency_id,
status.id AS invoice_status_id,
status.name::text AS invoice_status, -- Cast to text
header.type,
header.is_created_by_scheduler
FROM invoice_headers header
JOIN customers customer ON customer.id = header.customer_id
JOIN invoice_statuses status ON status.id = header.invoice_status_id
WHERE header.id = p_invoice_header_id
AND header.is_deleted = false;
END;
$function$
-- Function: get_invoice_payments_by_customer_id
CREATE OR REPLACE FUNCTION public.get_invoice_payments_by_customer_id(p_company_id uuid, p_customer_id uuid, p_from_date date, p_to_date date)
RETURNS TABLE(company_id uuid, customer_id uuid, customer_name character varying, id uuid, payment_number text, mode_of_payment text, reference text, received_date timestamp without time zone, received_amount numeric, grand_total_amount numeric, advance_amount numeric, tds_amount numeric, description text, debit_account_id uuid, credit_account_id uuid, transaction_id bigint, is_posted boolean, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, is_deleted boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
iph.company_id,
iph.customer_id,
c.name AS customer_name,
iph.id AS id,
iph.payment_number,
iph.mode_of_payment,
iph.reference,
iph.received_date,
iph.received_amount,
iph.grand_total_amount,
iph.advance_amount,
iph.tds_amount,
iph.description,
iph.debit_account_id,
iph.credit_account_id,
iph.transaction_id,
iph.is_posted,
iph.created_on_utc,
iph.modified_on_utc,
iph.is_deleted
FROM
invoice_payment_headers iph
INNER JOIN
customers c ON iph.customer_id = c.id
WHERE
iph.company_id = p_company_id
AND iph.customer_id = p_customer_id
AND iph.is_deleted = FALSE
AND c.is_deleted = FALSE
AND iph.received_date BETWEEN p_from_date AND p_to_date
ORDER BY
iph.received_date DESC;
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, p_from_date date, p_to_date date)
RETURNS TABLE(company_id uuid, customer_id uuid, customer_name character varying, id uuid, invoice_number text, invoice_voucher text, invoice_date timestamp without time zone, due_date timestamp without time zone, total_amount numeric, settled_amount numeric, due_amount numeric, payment_status_id integer, fin_entry_status_id integer, transaction_id bigint, is_posted boolean, current_approval_level integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, is_deleted boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
i.company_id,
i.customer_id,
c.name AS customer_name,
i.id AS id,
i.invoice_number,
i.invoice_voucher,
i.invoice_date,
i.due_date,
i.total_amount,
i.settled_amount,
(i.total_amount - COALESCE(i.settled_amount, 0))::numeric AS due_amount,
i.payment_status_id,
i.fin_entry_status_id,
i.transaction_id,
i.is_posted,
i.current_approval_level,
i.created_on_utc,
i.modified_on_utc,
i.is_deleted
FROM
invoice_headers i
INNER JOIN
customers c ON i.customer_id = c.id
WHERE
i.company_id = p_company_id
AND i.customer_id = p_customer_id
AND i.is_deleted = FALSE
AND c.is_deleted = FALSE
-- Unpaid or partially paid invoices
AND (i.settled_amount IS NULL OR i.settled_amount < i.total_amount)
-- Due date filter
AND i.due_date BETWEEN p_from_date AND p_to_date
ORDER BY
i.due_date ASC;
END;
$function$
-- Function: get_customers_with_invoice_summary
CREATE OR REPLACE FUNCTION public.get_customers_with_invoice_summary(p_company_id uuid, p_fin_year_id integer)
RETURNS TABLE(id uuid, name character varying, contact_name text, gst_in text, mobile_number text, email text, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, billing_address jsonb, contact_count integer, total_invoice_amount numeric, total_settled_amount numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
BEGIN
RETURN QUERY
SELECT
c.id,
c.name,
con.first_name || ' ' || con.last_name AS contact_name,
c.gstin AS gst_in,
con.mobile_number,
con.email,
c.created_by,
u_created.first_name || ' ' || u_created.last_name AS created_by_name,
c.modified_by,
u_modified.first_name || ' ' || u_modified.last_name AS modified_by_name,
c.created_on_utc,
c.modified_on_utc,
CASE
WHEN ba.id IS NOT NULL THEN jsonb_build_object(
'AddressLine1', ba.address_line1,
'AddressLine2', ba.address_line2,
'ZipCode', ba.zip_code,
'CountryId', ba.country_id,
'StateId', ba.state_id,
'CityId', ba.city_id
)
ELSE NULL
END AS billing_address,
(SELECT COUNT(*)::integer FROM public.customer_contacts cc_count WHERE cc_count.customer_id = c.id) AS contact_count,
COALESCE(inv_sums.total_invoice_amount, 0) AS total_invoice_amount,
COALESCE(inv_sums.total_settled_amount, 0) AS total_settled_amount
FROM public.customers c
LEFT JOIN public.customer_contacts cc ON c.id = cc.customer_id
LEFT JOIN public.contacts con ON cc.contact_id = con.id AND con.is_primary = true
LEFT JOIN public.users u_created ON c.created_by = u_created.id
LEFT JOIN public.users u_modified ON c.modified_by = u_modified.id
LEFT JOIN public.addresses ba ON c.billing_address_id = ba.id
LEFT JOIN (
SELECT
ih.customer_id,
SUM(ih.total_amount) AS total_invoice_amount,
SUM(ih.settled_amount) AS total_settled_amount
FROM public.invoice_headers ih
WHERE ih.company_id = p_company_id
AND ih.is_deleted = false
AND ih.invoice_date >= v_start_date
AND ih.invoice_date <= v_end_date
GROUP BY ih.customer_id
) inv_sums ON inv_sums.customer_id = c.id
WHERE c.company_id = p_company_id
AND c.is_deleted = false;
END;
$function$
-- Function: get_defaultors
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_defaultors(p_company_id uuid)
RETURNS TABLE(id uuid, customer_name text, invoice_number text, created_date timestamp without time zone, due_date timestamp without time zone, amount numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
-- Calculate and return the list of defaultors (overdue invoices)
RETURN QUERY
SELECT
gen_random_uuid() AS id, -- Generate a random unique ID
c.name::TEXT, -- Customer Name
i.invoice_number, -- Invoice Number
i.created_on_utc AS created_date, -- Created Date
i.due_date, -- Due Date
i.total_amount AS amount -- Total Invoice Amount
FROM
invoice_headers i
JOIN
customers c ON i.customer_id = c.id
JOIN
company_defaultor_configurations cfg ON i.company_id = cfg.company_id
WHERE
i.company_id = p_company_id
AND i.due_date < CURRENT_DATE - INTERVAL '1 day' * cfg.due_period_days -- Overdue invoices based on company configuration
AND i.settled_amount < i.total_amount -- Only unpaid invoices
AND i.is_deleted = FALSE -- Exclude deleted invoices
AND c.is_deleted = FALSE; -- Exclude deleted customers
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_defaultors(p_company_id uuid)
RETURNS TABLE(id uuid, customer_name text, invoice_number text, created_date timestamp without time zone, due_date timestamp without time zone, amount numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
-- Calculate and return the list of defaultors (overdue invoices)
RETURN QUERY
SELECT
gen_random_uuid() AS id, -- Generate a random unique ID
c.name::TEXT, -- Customer Name
i.invoice_number, -- Invoice Number
i.created_on_utc AS created_date, -- Created Date
i.due_date, -- Due Date
i.total_amount AS amount -- Total Invoice Amount
FROM
invoice_headers i
JOIN
customers c ON i.customer_id = c.id
JOIN
company_defaultor_configurations cfg ON i.company_id = cfg.company_id
WHERE
i.company_id = p_company_id
AND i.due_date < CURRENT_DATE - INTERVAL '1 day' * cfg.due_period_days -- Overdue invoices based on company configuration
AND i.setteled_amount < i.total_amount -- Only unpaid invoices
AND i.is_deleted = FALSE -- Exclude deleted invoices
AND c.is_deleted = FALSE; -- Exclude deleted customers
END;
$function$
-- Function: get_execution_invoices
CREATE OR REPLACE FUNCTION public.get_execution_invoices(p_execution_id uuid)
RETURNS TABLE(invoice_header_id uuid, invoice_number text, invoice_date timestamp without time zone, payment_term numeric, due_date timestamp without time zone, total_amount numeric, settled_amount numeric, taxable_amount numeric, discount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, round_off numeric, note text, customer_id uuid, customer_name text, invoice_status_id integer, invoice_status text, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, credit_account_id uuid, invoice_voucher text, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, type integer, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ih.id AS invoice_header_id,
ih.invoice_number::text AS invoice_number, -- Explicit cast
ih.invoice_date,
ih.payment_term::numeric AS payment_term, -- Cast to numeric
ih.due_date,
ih.total_amount,
ih.settled_amount,
ih.taxable_amount,
ih.discount,
ih.sgst_amount,
ih.cgst_amount,
ih.igst_amount,
ih.round_off,
ih.note::text AS note, -- Explicit cast
ih.customer_id,
COALESCE(c.name, 'Unknown')::text AS customer_name, -- Explicit cast
ih.invoice_status_id,
COALESCE(s.name, 'Unknown')::text AS invoice_status, -- Explicit cast
ih.currency_id,
ih.created_on_utc,
ih.modified_on_utc,
ih.credit_account_id,
ih.invoice_voucher::text AS invoice_voucher, -- Explicit cast
ih.created_by,
ih.modified_by,
ih.source_warehouse_id,
ih.destination_warehouse_id,
COALESCE(w.name, 'Unknown')::text AS destination_warehouse_name, -- Explicit cast
ih.type,
ih.is_created_by_scheduler
FROM
apartment_invoice_postings aip
INNER JOIN
invoice_headers ih ON aip.invoice_header_id = ih.id
LEFT JOIN
customers c ON ih.customer_id = c.id
LEFT JOIN
invoice_statuses s ON ih.invoice_status_id = s.id
LEFT JOIN
warehouses w ON ih.destination_warehouse_id = w.id
WHERE
aip.execution_id = p_execution_id
AND NOT aip.is_deleted
AND NOT ih.is_deleted;
END;
$function$
-- Function: get_total_defaultors_and_amount
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_total_defaultors_and_amount(p_company_id uuid)
RETURNS TABLE(result_id uuid, total_defaultors integer, total_amount numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
-- Generate a unique id for this result
result_id := gen_random_uuid();
-- Debugging: Output the company_id being passed
RAISE NOTICE 'Calculating for company_id: %', p_company_id;
-- Calculate total defaultors and total amount for overdue invoices
RETURN QUERY
SELECT
result_id As id, -- Return the generated unique id for each result
COUNT(DISTINCT i.customer_id)::INT AS total_defaultors, -- Cast COUNT to INT
SUM(i.total_amount) AS total_amount -- Sum of total amounts of overdue invoices
FROM
invoice_headers i
JOIN
customers c ON i.customer_id = c.id -- Join with customers to filter by company_id
WHERE
i.company_id = p_company_id -- Use the passed-in parameter (p_company_id)
AND i.due_date < CURRENT_DATE - (SELECT due_period_days FROM company_defaultor_configurations WHERE company_id = p_company_id LIMIT 1) -- Check for overdue invoices based on due_period_days
AND i.settled_amount < i.total_amount -- Only unpaid invoices (settled_amount < total_amount)
AND i.is_deleted = FALSE -- Exclude deleted invoices
AND c.is_deleted = FALSE; -- Exclude deleted customers
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_total_defaultors_and_amount(p_company_id uuid)
RETURNS TABLE(result_id uuid, total_defaultors integer, total_amount numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
-- Generate a unique id for this result
result_id := gen_random_uuid();
-- Debugging: Output the company_id being passed
RAISE NOTICE 'Calculating for company_id: %', p_company_id;
-- Calculate total defaultors and total amount for overdue invoices
RETURN QUERY
SELECT
result_id As id, -- Return the generated unique id for each result
COUNT(DISTINCT i.customer_id)::INT AS total_defaultors, -- Cast COUNT to INT
SUM(i.total_amount) AS total_amount -- Sum of total amounts of overdue invoices
FROM
invoice_headers i
JOIN
customers c ON i.customer_id = c.id -- Join with customers to filter by company_id
WHERE
i.company_id = p_company_id -- Use the passed-in parameter (p_company_id)
AND i.due_date < CURRENT_DATE - (SELECT due_period_days FROM company_defaultor_configurations WHERE company_id = p_company_id LIMIT 1) -- Check for overdue invoices based on due_period_days
AND i.setteled_amount < i.total_amount -- Only unpaid invoices (settled_amount < total_amount)
AND i.is_deleted = FALSE -- Exclude deleted invoices
AND c.is_deleted = FALSE; -- Exclude deleted customers
END;
$function$
-- Function: get_grouped_invoice_details
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_details(p_group_invoice_header_id uuid, p_fin_year integer)
RETURNS TABLE(invoice_header_id uuid, invoice_number text, invoice_date timestamp without time zone, payment_term numeric, due_date timestamp without time zone, total_amount numeric, settled_amount numeric, taxable_amount numeric, discount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, round_off numeric, note text, customer_id uuid, customer_name text, invoice_status_id integer, invoice_status text, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, credit_account_id uuid, invoice_voucher text, created_by uuid, modified_by uuid, source_warehouse_id uuid, destination_warehouse_id uuid, destination_warehouse_name text, type integer, is_created_by_scheduler boolean)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := MAKE_DATE(p_fin_year, 4, 1);
v_end_date date := MAKE_DATE(p_fin_year + 1, 3, 31);
BEGIN
RETURN QUERY
SELECT
ih.id AS invoice_header_id,
ih.invoice_number::text AS invoice_number,
ih.invoice_date,
ih.payment_term::numeric AS payment_term,
ih.due_date,
ih.total_amount,
ih.settled_amount,
ih.taxable_amount,
ih.discount,
ih.sgst_amount,
ih.cgst_amount,
ih.igst_amount,
ih.round_off,
ih.note::text AS note,
ih.customer_id,
COALESCE(c.name, 'Unknown')::text AS customer_name,
ih.invoice_status_id,
COALESCE(s.name, 'Unknown')::text AS invoice_status,
ih.currency_id,
ih.created_on_utc,
ih.modified_on_utc,
ih.credit_account_id,
ih.invoice_voucher::text AS invoice_voucher,
ih.created_by,
ih.modified_by,
ih.source_warehouse_id,
ih.destination_warehouse_id,
COALESCE(w.name, 'Unknown')::text AS destination_warehouse_name,
ih.type,
ih.is_created_by_scheduler
FROM
public.group_invoice_details aid
INNER JOIN
invoice_headers ih ON aid.invoice_header_id = ih.id
LEFT JOIN
customers c ON ih.customer_id = c.id
LEFT JOIN
invoice_statuses s ON ih.invoice_status_id = s.id
LEFT JOIN
warehouses w ON ih.destination_warehouse_id = w.id
WHERE
aid.group_invoice_header_id = p_group_invoice_header_id
AND NOT aid.is_deleted
AND NOT ih.is_deleted
AND (p_fin_year IS NULL OR (ih.invoice_date >= v_start_date AND ih.invoice_date <= v_end_date));
END;
$function$
-- Function: get_group_invoice_totals
CREATE OR REPLACE FUNCTION public.get_group_invoice_totals(p_company_id uuid, p_grouped_invoice_id uuid)
RETURNS TABLE(id uuid, grouped_invoice_name text, total_amount numeric, paid_amount numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
gih.id AS id,
gih.group_invoice_number AS grouped_invoice_name,
SUM(ih.total_amount) AS total_amount,
SUM(
CASE
WHEN ih.invoice_status_id = 3 THEN ih.settled_amount
ELSE 0
END
) AS paid_amount
FROM
public.group_invoice_headers gih
JOIN
public.group_invoice_details gid ON gih.id = gid.group_invoice_header_id
JOIN
public.invoice_headers ih ON gid.invoice_header_id = ih.id
WHERE
gih.id = p_grouped_invoice_id
AND gih.company_id = p_company_id
AND gih.is_deleted = false
GROUP BY
gih.id,
gih.group_invoice_number;
END;
$function$
-- Function: get_invoice_analytics
CREATE OR REPLACE FUNCTION public.get_invoice_analytics(p_company_id uuid, p_finance_year_id integer, p_group_invoice_header_id uuid DEFAULT NULL::uuid)
RETURNS TABLE(id integer, status text, count integer, total_amount numeric, month_start_date date)
LANGUAGE plpgsql
AS $function$
DECLARE
v_financial_year_start DATE;
v_financial_year_end DATE;
BEGIN
v_financial_year_start := TO_DATE(p_finance_year_id || '-04-01', 'YYYY-MM-DD');
v_financial_year_end := TO_DATE((p_finance_year_id + 1) || '-03-31', 'YYYY-MM-DD');
-- Case 1: Group Invoice Specific
IF p_group_invoice_header_id IS NOT NULL THEN
IF NOT EXISTS (
SELECT 1
FROM group_invoice_headers gih
WHERE gih.id = p_group_invoice_header_id
AND gih.is_deleted = false
) THEN
RETURN;
END IF;
RETURN QUERY
SELECT row_number() OVER ()::int AS id,
'Paid' AS status,
COUNT(*)::int,
COALESCE(SUM(ih.settled_amount),0),
DATE_TRUNC('month', ih.invoice_date)::date
FROM group_invoice_details gid
JOIN invoice_headers ih ON ih.id = gid.invoice_header_id
WHERE gid.group_invoice_header_id = p_group_invoice_header_id
AND gid.is_deleted = false
AND ih.invoice_status_id IN (4,5)
AND ih.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
GROUP BY DATE_TRUNC('month', ih.invoice_date)
UNION ALL
SELECT row_number() OVER ()::int,
'Pending',
COUNT(*)::int,
COALESCE(SUM(ih.total_amount - ih.settled_amount),0),
DATE_TRUNC('month', ih.invoice_date)::date
FROM group_invoice_details gid
JOIN invoice_headers ih ON ih.id = gid.invoice_header_id
WHERE gid.group_invoice_header_id = p_group_invoice_header_id
AND gid.is_deleted = false
AND ih.invoice_status_id IN (3,4)
AND ih.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
GROUP BY DATE_TRUNC('month', ih.invoice_date)
UNION ALL
SELECT row_number() OVER ()::int,
'Overdue',
COUNT(*)::int,
COALESCE(SUM(ih.total_amount - ih.settled_amount),0),
DATE_TRUNC('month', ih.invoice_date)::date
FROM group_invoice_details gid
JOIN invoice_headers ih ON ih.id = gid.invoice_header_id
WHERE gid.group_invoice_header_id = p_group_invoice_header_id
AND gid.is_deleted = false
AND ih.invoice_status_id IN (3,4)
AND ih.due_date < now()
AND ih.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
GROUP BY DATE_TRUNC('month', ih.invoice_date);
-- Case 2: Company-wide Analytics
ELSE
RETURN QUERY
SELECT row_number() OVER ()::int,
'Paid',
COUNT(*)::int,
COALESCE(SUM(i.settled_amount), 0),
DATE_TRUNC('month', i.invoice_date)::date
FROM invoice_headers i
WHERE i.company_id = p_company_id
AND i.invoice_status_id IN (4, 5)
AND i.is_deleted = false
AND i.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
GROUP BY DATE_TRUNC('month', i.invoice_date)
UNION ALL
SELECT row_number() OVER ()::int,
'Pending',
COUNT(*)::int,
COALESCE(SUM(i.total_amount - i.settled_amount), 0),
DATE_TRUNC('month', i.invoice_date)::date
FROM invoice_headers i
WHERE i.company_id = p_company_id
AND i.invoice_status_id IN (3, 4)
AND i.is_deleted = false
AND i.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
GROUP BY DATE_TRUNC('month', i.invoice_date)
UNION ALL
SELECT row_number() OVER ()::int,
'Overdue',
COUNT(*)::int,
COALESCE(SUM(i.total_amount - i.settled_amount), 0),
DATE_TRUNC('month', i.invoice_date)::date
FROM invoice_headers i
WHERE i.company_id = p_company_id
AND i.invoice_status_id IN (3, 4)
AND i.due_date < CURRENT_DATE
AND i.is_deleted = false
AND i.invoice_date BETWEEN v_financial_year_start AND v_financial_year_end
GROUP BY DATE_TRUNC('month', i.invoice_date);
END IF;
END;
$function$
-- Function: get_grouped_invoice_header_by_limit
CREATE OR REPLACE FUNCTION public.get_grouped_invoice_header_by_limit(p_company_id uuid, p_fin_year_id integer, p_limit integer DEFAULT NULL::integer)
RETURNS TABLE(id uuid, group_invoice_number text, grouped_invoice_template_id uuid, execution_date timestamp without time zone, error_message text, success_count integer, total_count integer, invoice_description text, group_invoice_batch_id text, paid_count integer, total_paid_amount numeric, original_total_amount numeric, remaining_amount numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
BEGIN
IF p_limit IS NOT NULL THEN
-- If p_limit is provided, apply the LIMIT
RETURN QUERY
SELECT
gih.id,
gih.group_invoice_number,
gih.group_invoice_template_id,
gih.execution_date,
gih.error_message,
gih.success_count,
gih.total_count,
templates.invoice_description,
gih.group_invoice_batch_id,
CAST(COALESCE(SUM(CASE WHEN ih.settled_amount >= ih.total_amount THEN 1 ELSE 0 END), 0) AS integer) AS paid_count,
COALESCE(SUM(ih.settled_amount), 0) AS total_paid_amount,
COALESCE(SUM(ih.total_amount), 0) AS original_total_amount,
COALESCE(SUM(ih.total_amount - ih.settled_amount), 0) AS remaining_amount
FROM
public.group_invoice_headers gih
INNER JOIN
public.group_invoice_templates templates
ON gih.group_invoice_template_id = templates.id
LEFT JOIN
public.group_invoice_details gid
ON gid.group_invoice_header_id = gih.id
AND gid.is_deleted = false
AND gid.company_id = p_company_id
LEFT JOIN
public.invoice_headers ih
ON ih.id = gid.invoice_header_id
AND ih.is_deleted = false
AND ih.company_id = p_company_id
WHERE
gih.company_id = p_company_id
AND gih.execution_date::date BETWEEN v_start_date AND v_end_date
GROUP BY
gih.id,
gih.group_invoice_number,
gih.group_invoice_template_id,
gih.execution_date,
gih.error_message,
gih.success_count,
gih.total_count,
templates.invoice_description,
gih.group_invoice_batch_id
ORDER BY
gih.execution_date DESC
LIMIT p_limit; -- Apply the LIMIT when p_limit is provided
ELSE
-- If p_limit is NULL, return all results for the specified company and financial year
RETURN QUERY
SELECT
gih.id,
gih.group_invoice_number,
gih.group_invoice_template_id,
gih.execution_date,
gih.error_message,
gih.success_count,
gih.total_count,
templates.invoice_description,
gih.group_invoice_batch_id,
CAST(COALESCE(SUM(CASE WHEN ih.settled_amount >= ih.total_amount THEN 1 ELSE 0 END), 0) AS integer) AS paid_count,
COALESCE(SUM(ih.settled_amount), 0) AS total_paid_amount,
COALESCE(SUM(ih.total_amount), 0) AS original_total_amount,
COALESCE(SUM(ih.total_amount - ih.settled_amount), 0) AS remaining_amount
FROM
public.group_invoice_headers gih
INNER JOIN
public.group_invoice_templates templates
ON gih.group_invoice_template_id = templates.id
LEFT JOIN
public.group_invoice_details gid
ON gid.group_invoice_header_id = gih.id
AND gid.is_deleted = false
AND gid.company_id = p_company_id
LEFT JOIN
public.invoice_headers ih
ON ih.id = gid.invoice_header_id
AND ih.is_deleted = false
AND ih.company_id = p_company_id
WHERE
gih.company_id = p_company_id
AND gih.execution_date::date BETWEEN v_start_date AND v_end_date
GROUP BY
gih.id,
gih.group_invoice_number,
gih.group_invoice_template_id,
gih.execution_date,
gih.error_message,
gih.success_count,
gih.total_count,
templates.invoice_description,
gih.group_invoice_batch_id
ORDER BY
gih.execution_date DESC;
END IF;
END;
$function$
-- Function: get_all_invoices_from_span
-- SOURCE
CREATE OR REPLACE FUNCTION public.get_all_invoices_from_span(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
RETURNS TABLE(id uuid, invoice_number character varying, type integer, invoice_date date, customer_id uuid, customer_name character varying, due_date date, total_amount numeric, settled_amount numeric, invoice_status_id integer, invoice_status character varying, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, created_by_name character varying, modified_by_name character varying, is_created_by_scheduler boolean, has_next_status_approval boolean, next_status_id integer, has_pending_payment_verification boolean)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
DRAFT_STATUS CONSTANT integer := 1;
v_organization_id uuid;
BEGIN
RETURN QUERY
WITH invoice_data AS (
SELECT
ih.id,
CAST(ih.invoice_number AS varchar) AS invoice_number,
ih.type,
ih.invoice_date::date,
ih.customer_id,
c.name AS customer_name,
ih.due_date::date,
ih.total_amount,
ih.settled_amount,
ih.invoice_status_id,
s.name AS invoice_status,
ih.currency_id,
ih.created_on_utc,
ih.modified_on_utc,
ih.created_by,
ih.modified_by,
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
ih.is_created_by_scheduler,
ih.credit_account_id
FROM invoice_headers ih
LEFT JOIN customers c ON c.id = ih.customer_id
LEFT JOIN invoice_statuses s ON s.id = ih.invoice_status_id
LEFT JOIN users cu ON cu.id = ih.created_by
LEFT JOIN users mu ON mu.id = ih.modified_by
WHERE ih.company_id = p_company_id
AND ih.is_deleted = false
AND ih.invoice_date BETWEEN v_start_date AND v_end_date
AND (p_type_id IS NULL OR p_type_id = 0 OR ih.type = p_type_id)
UNION ALL
SELECT
dih.id,
CAST(dih.invoice_number AS varchar) AS invoice_number,
dih.type,
dih.invoice_date::date,
dih.customer_id,
c.name AS customer_name,
dih.due_date::date,
dih.total_amount,
dih.settled_amount,
dih.invoice_status_id,
s.name AS invoice_status,
dih.currency_id,
dih.created_on_utc,
dih.modified_on_utc,
dih.created_by,
dih.modified_by,
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
dih.is_created_by_scheduler,
dih.credit_account_id
FROM draft_invoice_headers dih
LEFT JOIN customers c ON c.id = dih.customer_id
LEFT JOIN invoice_statuses s ON s.id = dih.invoice_status_id
LEFT JOIN users cu ON cu.id = dih.created_by
LEFT JOIN users mu ON mu.id = dih.modified_by
WHERE dih.company_id = p_company_id
AND dih.is_deleted = false
AND dih.invoice_date BETWEEN v_start_date AND v_end_date
AND (p_type_id IS NULL OR p_type_id = 0 OR dih.type = p_type_id)
)
SELECT
id.id,
id.invoice_number,
id.type,
id.invoice_date,
id.customer_id,
id.customer_name,
id.due_date,
id.total_amount,
id.settled_amount,
id.invoice_status_id,
id.invoice_status,
id.currency_id,
id.created_on_utc,
id.modified_on_utc,
id.created_by,
id.modified_by,
id.created_by_name,
id.modified_by_name,
id.is_created_by_scheduler,
-- Determine if current user can approve
CASE
WHEN id.invoice_status_id = DRAFT_STATUS THEN true
WHEN EXISTS (
SELECT 1
FROM public.invoice_approval_users_account a
WHERE a.account_id = id.credit_account_id
AND a.status_id = id.invoice_status_id
AND a.user_id = p_user_id
AND a.is_deleted = false
) THEN true
WHEN EXISTS (
SELECT 1
FROM public.invoice_approval_user_company c
WHERE c.company_id = p_company_id
AND c.status_id = id.invoice_status_id
AND c.user_id = p_user_id
AND c.is_deleted = false
) THEN true
ELSE false
END AS has_next_status_approval,
-- Determine next status
COALESCE(
(SELECT iw.next_status
FROM invoice_workflow iw
WHERE iw.company_id = p_company_id
AND iw.status = id.invoice_status_id
AND iw.is_deleted = false
LIMIT 1),
0
) AS next_status_id,
-- Check if there's a pending payment verification
EXISTS (
SELECT 1
FROM public.invoice_payment_headers iph
JOIN public.invoice_payment_details ipd
ON ipd.invoice_payment_header_id = iph.id
WHERE ipd.invoice_header_id = id.id
AND iph.payment_status_id = 4 -- Payment Pending Verification status
AND iph.is_deleted = false
) AS has_pending_payment_verification
FROM invoice_data id;
END;
$function$
-- TARGET
CREATE OR REPLACE FUNCTION public.get_all_invoices_from_span(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
RETURNS TABLE(id uuid, invoice_number character varying, type integer, invoice_date date, customer_id uuid, customer_name character varying, due_date date, total_amount numeric, setteled_amount numeric, invoice_status_id integer, invoice_status character varying, currency_id integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, created_by_name character varying, modified_by_name character varying, is_created_by_scheduler boolean, has_next_status_approval boolean, next_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
DRAFT_STATUS CONSTANT integer := 1;
BEGIN
RETURN QUERY
WITH invoice_data AS (
SELECT
ih.id,
CAST(ih.invoice_number AS varchar) AS invoice_number,
ih.type,
ih.invoice_date::date,
ih.customer_id,
c.name AS customer_name,
ih.due_date::date,
ih.total_amount,
ih.setteled_amount,
ih.invoice_status_id,
s.name AS invoice_status,
ih.currency_id,
ih.created_on_utc,
ih.modified_on_utc,
ih.created_by,
ih.modified_by,
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
ih.is_created_by_scheduler,
ih.credit_account_id
FROM invoice_headers ih
LEFT JOIN customers c ON c.id = ih.customer_id
LEFT JOIN invoice_statuses s ON s.id = ih.invoice_status_id
LEFT JOIN users cu ON cu.id = ih.created_by
LEFT JOIN users mu ON mu.id = ih.modified_by
WHERE ih.company_id = p_company_id
AND ih.is_deleted = false
AND ih.invoice_date BETWEEN v_start_date AND v_end_date
AND (p_type_id IS NULL OR p_type_id = 0 OR ih.type = p_type_id)
UNION ALL
SELECT
dih.id,
CAST(dih.invoice_number AS varchar) AS invoice_number,
dih.type,
dih.invoice_date::date,
dih.customer_id,
c.name AS customer_name,
dih.due_date::date,
dih.total_amount,
dih.setteled_amount,
dih.invoice_status_id,
s.name AS invoice_status,
dih.currency_id,
dih.created_on_utc,
dih.modified_on_utc,
dih.created_by,
dih.modified_by,
CAST(CONCAT(cu.first_name, ' ', cu.last_name) AS varchar) AS created_by_name,
CAST(CONCAT(mu.first_name, ' ', mu.last_name) AS varchar) AS modified_by_name,
dih.is_created_by_scheduler,
dih.credit_account_id
FROM draft_invoice_headers dih
LEFT JOIN customers c ON c.id = dih.customer_id
LEFT JOIN invoice_statuses s ON s.id = dih.invoice_status_id
LEFT JOIN users cu ON cu.id = dih.created_by
LEFT JOIN users mu ON mu.id = dih.modified_by
WHERE dih.company_id = p_company_id
AND dih.is_deleted = false
AND dih.invoice_date BETWEEN v_start_date AND v_end_date
AND (p_type_id IS NULL OR p_type_id = 0 OR dih.type = p_type_id)
)
SELECT
id.id,
id.invoice_number,
id.type,
id.invoice_date,
id.customer_id,
id.customer_name,
id.due_date,
id.total_amount,
id.setteled_amount,
id.invoice_status_id,
id.invoice_status,
id.currency_id,
id.created_on_utc,
id.modified_on_utc,
id.created_by,
id.modified_by,
id.created_by_name,
id.modified_by_name,
id.is_created_by_scheduler,
-- Determine if current user can approve
CASE
WHEN id.invoice_status_id = DRAFT_STATUS THEN true
WHEN EXISTS (
SELECT 1
FROM invoice_approval_users_account a
WHERE a.account_id = id.credit_account_id
AND a.status_id = id.invoice_status_id
AND a.user_id = p_user_id
AND a.is_deleted = false
) THEN true
WHEN EXISTS (
SELECT 1
FROM invoice_approval_user_company c
WHERE c.company_id = p_company_id
AND c.status_id = id.invoice_status_id
AND c.user_id = p_user_id
AND c.is_deleted = false
) THEN true
ELSE false
END AS has_next_status_approval,
-- Determine next status
COALESCE(
(SELECT iw.next_status
FROM invoice_workflow iw
WHERE iw.company_id = p_company_id
AND iw.status = id.invoice_status_id
AND iw.is_deleted = false
LIMIT 1),
0
) AS next_status_id
FROM invoice_data id;
END;
$function$
-- Function: get_unit_dues_by_cust_id
CREATE OR REPLACE FUNCTION public.get_unit_dues_by_cust_id(p_company_id uuid, p_customer_id uuid, p_fin_year_id integer)
RETURNS TABLE(id uuid, invoice_number text, note text, due_date timestamp without time zone, total_due numeric, total_paid numeric, original_amount numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1); -- Default: April 1st of the given financial year
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31); -- Default: March 31st of the next financial year
BEGIN
RETURN QUERY
SELECT
i.id AS id,
i.invoice_number,
i.note,
i.due_date,
(i.total_amount - i.settled_amount) AS total_due,
i.settled_amount AS total_paid,
i.total_amount AS original_amount
FROM invoice_headers i
INNER JOIN customers c
ON c.id = i.customer_id
AND c.company_id = p_company_id
WHERE
i.company_id = p_company_id
AND i.customer_id = p_customer_id
AND c.is_deleted = false
AND i.is_deleted = false
AND i.invoice_status_id IN (3, 4) -- Approved / Partially Paid
AND (i.total_amount - i.settled_amount) > 0
AND i.due_date >= v_start_date -- Filter by the financial year start date
AND i.due_date <= v_end_date -- Filter by the financial year end date
ORDER BY i.due_date;
END;
$function$
-- Function: get_defaulter_list
CREATE OR REPLACE FUNCTION public.get_defaulter_list(p_company_id uuid, p_as_of_date timestamp without time zone, p_limit integer DEFAULT NULL::integer)
RETURNS TABLE(id integer, customer_id uuid, customer_name character varying, due_date date, days_overdue integer, total_outstanding_per_customer numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT *
FROM (
SELECT
ROW_NUMBER() OVER (
ORDER BY
SUM(ih.total_amount - ih.settled_amount) DESC,
MAX(DATE_PART('day', p_as_of_date - ih.due_date)) DESC,
c."name" ASC
)::INT AS id,
c.id AS customer_id,
c."name" AS customer_name,
-- Oldest unpaid invoice due date
MIN(ih.due_date)::DATE AS due_date,
-- Max overdue days across invoices
MAX(
DATE_PART('day', p_as_of_date - ih.due_date)
)::INT AS days_overdue,
SUM(ih.total_amount - ih.settled_amount)
AS total_outstanding_per_customer
FROM public.invoice_headers ih
JOIN public.customers c
ON c.id = ih.customer_id
LEFT JOIN public.defaulter_configurations dc
ON dc.company_id = p_company_id
WHERE
ih.settled_amount < ih.total_amount
AND ih.due_date < p_as_of_date
AND ih.is_deleted = FALSE
AND c.is_deleted = FALSE
AND ih.company_id = p_company_id
AND dc.is_deleted = FALSE
AND DATE_PART('day', p_as_of_date - ih.due_date)
> dc.defaulter_period_days
GROUP BY
c.id,
c."name"
) ranked
ORDER BY
ranked.id
LIMIT
COALESCE(p_limit, 2147483647);
END;
$function$
-- Function: get_unit_dues_summary_by_cust_id
CREATE OR REPLACE FUNCTION public.get_unit_dues_summary_by_cust_id(p_company_id uuid, p_customer_id uuid, p_fin_year_id integer)
RETURNS TABLE(last_collection_target_value numeric, last_collection_collected_value numeric, last_collection_units_targeted integer, last_collection_paid_units integer, total_outstanding_due numeric, total_units_not_paid integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
v_latest_cycle_date date;
BEGIN
/*
* 1) Find latest invoice cycle date within the FY
* Using invoice_date if present, otherwise created_on_utc
*/
SELECT MAX(COALESCE(i.invoice_date::date, i.created_on_utc::date))
INTO v_latest_cycle_date
FROM invoice_headers i
INNER JOIN customers c ON c.id = i.customer_id
WHERE i.company_id = p_company_id
AND i.customer_id = p_customer_id
AND c.company_id = p_company_id
AND c.is_deleted = false
AND i.is_deleted = false
AND i.invoice_status_id IN (3, 4) -- Approved / Partially Paid
AND i.due_date::date BETWEEN v_start_date AND v_end_date;
-- No invoices in FY → return zero summary
IF v_latest_cycle_date IS NULL THEN
RETURN QUERY
SELECT 0, 0, 0, 0, 0, 0;
RETURN;
END IF;
/*
* 2) Build FY invoice set
*/
RETURN QUERY
WITH fy_invoices AS (
SELECT
i.customer_id,
i.total_amount,
i.settled_amount,
(i.total_amount - i.settled_amount) AS due_amount,
COALESCE(i.invoice_date::date, i.created_on_utc::date) AS cycle_date
FROM invoice_headers i
INNER JOIN customers c ON c.id = i.customer_id
WHERE i.company_id = p_company_id
AND i.customer_id = p_customer_id
AND c.company_id = p_company_id
AND c.is_deleted = false
AND i.is_deleted = false
AND i.invoice_status_id IN (3, 4)
AND i.due_date::date BETWEEN v_start_date AND v_end_date
),
last_cycle AS (
SELECT *
FROM fy_invoices
WHERE cycle_date = v_latest_cycle_date
),
last_cycle_rollup AS (
SELECT
COALESCE(SUM(total_amount), 0) AS target_value,
COALESCE(SUM(settled_amount), 0) AS collected_value,
COUNT(DISTINCT customer_id)::int AS units_targeted,
COUNT(
DISTINCT CASE
WHEN due_amount <= 0 THEN customer_id
END
)::int AS paid_units
FROM last_cycle
),
overall_rollup AS (
SELECT
COALESCE(SUM(due_amount), 0) AS total_due,
COUNT(
DISTINCT CASE
WHEN due_amount > 0 THEN customer_id
END
)::int AS units_not_paid
FROM fy_invoices
)
SELECT
l.target_value,
l.collected_value,
l.units_targeted,
l.paid_units,
o.total_due,
o.units_not_paid
FROM last_cycle_rollup l
CROSS JOIN overall_rollup o;
END;
$function$
-- Function: get_unit_dues_summary_by_company
CREATE OR REPLACE FUNCTION public.get_unit_dues_summary_by_company(p_company_id uuid, p_fin_year_id integer)
RETURNS TABLE(last_collection_target_value numeric, last_collection_collected_value numeric, last_collection_units_targeted integer, last_collection_paid_units integer, total_outstanding_due numeric, total_units_not_paid integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
v_latest_cycle_date date;
BEGIN
/*
* 1) Find latest invoice cycle date in FY (company-wide)
*/
SELECT MAX(COALESCE(i.invoice_date::date, i.created_on_utc::date))
INTO v_latest_cycle_date
FROM invoice_headers i
INNER JOIN customers c ON c.id = i.customer_id
WHERE i.company_id = p_company_id
AND c.company_id = p_company_id
AND c.is_deleted = false
AND i.is_deleted = false
AND i.invoice_status_id IN (3, 4) -- Approved / Partially Paid
AND i.due_date::date BETWEEN v_start_date AND v_end_date;
-- No invoices in FY → return zero summary
IF v_latest_cycle_date IS NULL THEN
RETURN QUERY
SELECT 0, 0, 0, 0, 0, 0;
RETURN;
END IF;
/*
* 2) Build FY invoice dataset
*/
RETURN QUERY
WITH fy_invoices AS (
SELECT
i.customer_id,
i.total_amount,
i.settled_amount,
(i.total_amount - i.settled_amount) AS due_amount,
COALESCE(i.invoice_date::date, i.created_on_utc::date) AS cycle_date
FROM invoice_headers i
INNER JOIN customers c ON c.id = i.customer_id
WHERE i.company_id = p_company_id
AND c.company_id = p_company_id
AND c.is_deleted = false
AND i.is_deleted = false
AND i.invoice_status_id IN (3, 4)
AND i.due_date::date BETWEEN v_start_date AND v_end_date
),
last_cycle AS (
SELECT *
FROM fy_invoices
WHERE cycle_date = v_latest_cycle_date
),
last_cycle_rollup AS (
SELECT
COALESCE(SUM(total_amount), 0) AS target_value,
COALESCE(SUM(settled_amount), 0) AS collected_value,
COUNT(DISTINCT customer_id)::int AS units_targeted,
COUNT(
DISTINCT CASE
WHEN due_amount <= 0 THEN customer_id
END
)::int AS paid_units
FROM last_cycle
),
overall_rollup AS (
SELECT
COALESCE(SUM(due_amount), 0) AS total_due,
COUNT(
DISTINCT CASE
WHEN due_amount > 0 THEN customer_id
END
)::int AS units_not_paid
FROM fy_invoices
)
SELECT
l.target_value,
l.collected_value,
l.units_targeted,
l.paid_units,
o.total_due,
o.units_not_paid
FROM last_cycle_rollup l
CROSS JOIN overall_rollup o;
END;
$function$
-- Function: get_discussion_messages
CREATE OR REPLACE FUNCTION public.get_discussion_messages(p_organization_id uuid, p_cycle_id uuid)
RETURNS jsonb
LANGUAGE plpgsql
AS $function$
DECLARE
v_result jsonb;
BEGIN
-- Verify the cycle exists and belongs to the organization
IF NOT EXISTS (
SELECT 1
FROM public.delinquency_cycles dc
WHERE dc.id = p_cycle_id
AND dc.organization_id = p_organization_id
AND dc.is_deleted = false
) THEN
RETURN jsonb_build_object('messages', jsonb_build_array());
END IF;
-- Fetch all messages for the cycle
SELECT jsonb_build_object(
'messages',
COALESCE(
jsonb_agg(row_to_json(msg)),
jsonb_build_array()
)
)
INTO v_result
FROM (
SELECT
ddm.id,
ddm.cycle_id,
ddm.sender_id,
concat(u.first_name, ' ', u.last_name) AS sender_name,
ddm.sender_type,
ddm.message_text,
ddm.sent_on_utc
FROM public.delinquency_discussion_messages ddm
LEFT JOIN public.users u
ON u.id = ddm.sender_id
WHERE ddm.cycle_id = p_cycle_id
AND ddm.organization_id = p_organization_id
AND ddm.is_deleted = false
ORDER BY ddm.sent_on_utc ASC
) msg;
RETURN v_result;
END;
$function$
-- Function: dblink_connect
CREATE OR REPLACE FUNCTION public.dblink_connect(text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_connect$function$
-- Function: dblink_connect
CREATE OR REPLACE FUNCTION public.dblink_connect(text, text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_connect$function$
-- Function: dblink_connect_u
CREATE OR REPLACE FUNCTION public.dblink_connect_u(text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT SECURITY DEFINER
AS '$libdir/dblink', $function$dblink_connect$function$
-- Function: dblink_connect_u
CREATE OR REPLACE FUNCTION public.dblink_connect_u(text, text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT SECURITY DEFINER
AS '$libdir/dblink', $function$dblink_connect$function$
-- Function: dblink_disconnect
CREATE OR REPLACE FUNCTION public.dblink_disconnect()
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_disconnect$function$
-- Function: dblink_disconnect
CREATE OR REPLACE FUNCTION public.dblink_disconnect(text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_disconnect$function$
-- Function: dblink_open
CREATE OR REPLACE FUNCTION public.dblink_open(text, text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_open$function$
-- Function: dblink_open
CREATE OR REPLACE FUNCTION public.dblink_open(text, text, boolean)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_open$function$
-- Function: dblink_open
CREATE OR REPLACE FUNCTION public.dblink_open(text, text, text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_open$function$
-- Function: dblink_open
CREATE OR REPLACE FUNCTION public.dblink_open(text, text, text, boolean)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_open$function$
-- Function: dblink_fetch
CREATE OR REPLACE FUNCTION public.dblink_fetch(text, integer)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_fetch$function$
-- Function: dblink_fetch
CREATE OR REPLACE FUNCTION public.dblink_fetch(text, integer, boolean)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_fetch$function$
-- Function: dblink_fetch
CREATE OR REPLACE FUNCTION public.dblink_fetch(text, text, integer)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_fetch$function$
-- Function: dblink_fetch
CREATE OR REPLACE FUNCTION public.dblink_fetch(text, text, integer, boolean)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_fetch$function$
-- Function: dblink_close
CREATE OR REPLACE FUNCTION public.dblink_close(text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_close$function$
-- Function: dblink_close
CREATE OR REPLACE FUNCTION public.dblink_close(text, boolean)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_close$function$
-- Function: dblink_close
CREATE OR REPLACE FUNCTION public.dblink_close(text, text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_close$function$
-- Function: dblink_close
CREATE OR REPLACE FUNCTION public.dblink_close(text, text, boolean)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_close$function$
-- Function: dblink
CREATE OR REPLACE FUNCTION public.dblink(text, text)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_record$function$
-- Function: dblink
CREATE OR REPLACE FUNCTION public.dblink(text, text, boolean)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_record$function$
-- Function: dblink
CREATE OR REPLACE FUNCTION public.dblink(text)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_record$function$
-- Function: dblink
CREATE OR REPLACE FUNCTION public.dblink(text, boolean)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_record$function$
-- Function: dblink_exec
CREATE OR REPLACE FUNCTION public.dblink_exec(text, text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_exec$function$
-- Function: dblink_exec
CREATE OR REPLACE FUNCTION public.dblink_exec(text, text, boolean)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_exec$function$
-- Function: dblink_exec
CREATE OR REPLACE FUNCTION public.dblink_exec(text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_exec$function$
-- Function: open_or_get_delinquency_cycle
CREATE OR REPLACE FUNCTION public.open_or_get_delinquency_cycle(p_organization_id uuid, p_customer_id uuid, p_start_due_date date, p_created_by uuid)
RETURNS uuid
LANGUAGE plpgsql
AS $function$
DECLARE
v_cycle_id uuid;
CYCLE_STATUS_OPEN CONSTANT int := 1;
BEGIN
-- Try to get existing open cycle
SELECT id
INTO v_cycle_id
FROM delinquency_cycles
WHERE organization_id = p_organization_id
AND customer_id = p_customer_id
AND ended_on_utc IS NULL
AND is_deleted = false
LIMIT 1;
IF v_cycle_id IS NOT NULL THEN
RETURN v_cycle_id;
END IF;
-- if not then Create new cycle
INSERT INTO delinquency_cycles (
id,
organization_id,
customer_id,
start_due_date,
started_on_utc,
status_id,
created_on_utc,
created_by,
is_deleted
)
VALUES (
gen_random_uuid(),
p_organization_id,
p_customer_id,
p_start_due_date,
CURRENT_TIMESTAMP,
CYCLE_STATUS_OPEN,
CURRENT_TIMESTAMP,
p_created_by,
false
)
RETURNING id INTO v_cycle_id;
RETURN v_cycle_id;
END;
$function$
-- Function: dblink_exec
CREATE OR REPLACE FUNCTION public.dblink_exec(text, boolean)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_exec$function$
-- Function: dblink_get_pkey
CREATE OR REPLACE FUNCTION public.dblink_get_pkey(text)
RETURNS SETOF dblink_pkey_results
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_get_pkey$function$
-- Function: dblink_build_sql_insert
CREATE OR REPLACE FUNCTION public.dblink_build_sql_insert(text, int2vector, integer, text[], text[])
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_build_sql_insert$function$
-- Function: dblink_build_sql_delete
CREATE OR REPLACE FUNCTION public.dblink_build_sql_delete(text, int2vector, integer, text[])
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_build_sql_delete$function$
-- Function: dblink_build_sql_update
CREATE OR REPLACE FUNCTION public.dblink_build_sql_update(text, int2vector, integer, text[], text[])
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_build_sql_update$function$
-- Function: dblink_current_query
CREATE OR REPLACE FUNCTION public.dblink_current_query()
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED
AS '$libdir/dblink', $function$dblink_current_query$function$
-- Function: dblink_send_query
CREATE OR REPLACE FUNCTION public.dblink_send_query(text, text)
RETURNS integer
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_send_query$function$
-- Function: dblink_is_busy
CREATE OR REPLACE FUNCTION public.dblink_is_busy(text)
RETURNS integer
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_is_busy$function$
-- Function: dblink_get_result
CREATE OR REPLACE FUNCTION public.dblink_get_result(text)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_get_result$function$
-- Function: dblink_get_result
CREATE OR REPLACE FUNCTION public.dblink_get_result(text, boolean)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_get_result$function$
-- Function: dblink_get_connections
CREATE OR REPLACE FUNCTION public.dblink_get_connections()
RETURNS text[]
LANGUAGE c
PARALLEL RESTRICTED
AS '$libdir/dblink', $function$dblink_get_connections$function$
-- Function: dblink_cancel_query
CREATE OR REPLACE FUNCTION public.dblink_cancel_query(text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_cancel_query$function$
-- Function: dblink_error_message
CREATE OR REPLACE FUNCTION public.dblink_error_message(text)
RETURNS text
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_error_message$function$
-- Function: dblink_get_notify
CREATE OR REPLACE FUNCTION public.dblink_get_notify(OUT notify_name text, OUT be_pid integer, OUT extra text)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_get_notify$function$
-- Function: dblink_get_notify
CREATE OR REPLACE FUNCTION public.dblink_get_notify(conname text, OUT notify_name text, OUT be_pid integer, OUT extra text)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/dblink', $function$dblink_get_notify$function$
-- Function: dblink_fdw_validator
CREATE OR REPLACE FUNCTION public.dblink_fdw_validator(options text[], catalog oid)
RETURNS void
LANGUAGE c
PARALLEL SAFE STRICT
AS '$libdir/dblink', $function$dblink_fdw_validator$function$
-- Function: get_latest_raised_group_invoice_summary
CREATE OR REPLACE FUNCTION public.get_latest_raised_group_invoice_summary(p_company_id uuid DEFAULT NULL::uuid)
RETURNS TABLE(id integer, group_invoice_id uuid, group_invoice_number text, total_targeted_revenue numeric, collected_revenue numeric, total_units_count integer, collected_units_count integer)
LANGUAGE sql
STABLE
AS $function$
WITH latest_raised_group_invoice AS (
SELECT
gih.id,
gih.group_invoice_number
FROM public.group_invoice_headers gih
WHERE gih.is_deleted = false
AND (
p_company_id IS NULL
OR gih.company_id = p_company_id
)
AND EXISTS (
SELECT 1
FROM public.group_invoice_details gid
WHERE gid.group_invoice_header_id = gih.id
AND gid.is_deleted = false
)
ORDER BY gih.created_on_utc DESC NULLS LAST
LIMIT 1
)
SELECT
1 AS id,
lgi.id AS group_invoice_id,
lgi.group_invoice_number,
SUM(ih.total_amount) AS total_targeted_revenue,
SUM(COALESCE(ih.settled_amount, 0)) AS collected_revenue,
COUNT(DISTINCT gid.unit_id) AS total_units_count,
COUNT(
DISTINCT CASE
WHEN COALESCE(ih.settled_amount, 0) > 0
THEN gid.unit_id
END
) AS collected_units_count
FROM latest_raised_group_invoice lgi
JOIN public.group_invoice_details gid
ON gid.group_invoice_header_id = lgi.id
AND gid.is_deleted = false
JOIN public.invoice_headers ih
ON ih.id = gid.invoice_header_id
AND ih.is_deleted = false
GROUP BY
lgi.id,
lgi.group_invoice_number;
$function$
-- Function: get_aging_bucket
CREATE OR REPLACE FUNCTION public.get_aging_bucket(p_days integer)
RETURNS text
LANGUAGE sql
IMMUTABLE
AS $function$
SELECT CASE
WHEN p_days <= 30 THEN '0-30'
WHEN p_days <= 60 THEN '31-60'
WHEN p_days <= 90 THEN '61-90'
ELSE '90+'
END;
$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_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_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: run_defaulter_fdw
CREATE OR REPLACE FUNCTION public.run_defaulter_fdw()
RETURNS TABLE(result text)
LANGUAGE plpgsql
AS $function$
BEGIN
PERFORM public.evaluate_delinquency_for_org('b2756bc9-be6a-42bb-a78a-178ea22b46eb');
RETURN QUERY SELECT 'SUCCESS';
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,
'00000000-0000-0000-0000-000000000000'::uuid
);
END;
$function$
-- Function: get_invoice_payment_timeline_by_customerid
CREATE OR REPLACE FUNCTION public.get_invoice_payment_timeline_by_customerid(p_customer_id uuid, p_company_id uuid DEFAULT NULL::uuid)
RETURNS jsonb
LANGUAGE plpgsql
AS $function$
DECLARE
result jsonb;
BEGIN
SELECT jsonb_agg(
jsonb_build_object(
'section', section,
'dates', dates
)
)
INTO result
FROM (
SELECT
section,
jsonb_agg(date_block ORDER BY date_block->>'date') AS dates
FROM (
SELECT
section,
jsonb_build_object(
'date', event_date,
'dateLabel', to_char(event_date, 'Mon DD'),
'items',
jsonb_agg(
jsonb_build_object(
'eventType', event_type,
'invoiceNumber', invoice_number,
'paymentNumber', payment_number,
'reference', reference,
'title', title,
'timeLabel', time_label,
'status', status,
'amount', amount,
'daysToDue', days_to_due,
'dueLabel', due_label,
'invoiceId', invoice_id,
'paymentId', payment_id
)
ORDER BY event_time
)
) AS date_block
FROM (
/* =====================================================
INVOICE ISSUED (to resident)
====================================================== */
SELECT
ih.id AS invoice_id,
NULL::uuid AS payment_id,
ih.invoice_number AS invoice_number,
NULL::text AS payment_number,
ih.created_on_utc AS event_time,
ih.created_on_utc::date AS event_date,
'Invoice' AS event_type,
ih.invoice_number AS reference,
'Invoice Issued' AS title,
to_char(ih.created_on_utc, 'HH12:MI AM') AS time_label,
ih.total_amount AS amount,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN 'PAID'
WHEN ih.settled_amount > 0 THEN 'PARTIAL'
WHEN ih.due_date < now() THEN 'OVERDUE'
ELSE 'DUE'
END AS status,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN NULL
ELSE (ih.due_date::date - current_date)
END AS days_to_due,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN NULL
WHEN ih.due_date::date - current_date < 0
THEN concat('Overdue by ', abs(ih.due_date::date - current_date), ' days')
ELSE concat('Due in ', ih.due_date::date - current_date, ' days')
END AS due_label,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN 'Settled'
ELSE 'Open'
END AS section
FROM invoice_headers ih
WHERE ih.customer_id = p_customer_id
AND ih.is_deleted = false
AND (p_company_id IS NULL OR ih.company_id = p_company_id)
UNION ALL
/* =====================================================
PAYMENT MADE (by resident)
====================================================== */
SELECT
ipd.invoice_header_id AS invoice_id,
iph.id AS payment_id,
ih.invoice_number AS invoice_number,
iph.payment_number AS payment_number,
iph.received_date AS event_time,
iph.received_date::date AS event_date,
'Payment' AS event_type,
iph.payment_number AS reference,
CASE
WHEN ih.settled_amount >= ih.total_amount
THEN 'Invoice Paid'
ELSE 'Partial Payment Made'
END AS title,
to_char(iph.received_date, 'HH12:MI AM') AS time_label,
ipd.received_amount AS amount,
'PAYMENT' AS status,
NULL::int AS days_to_due,
NULL::text AS due_label,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN 'Settled'
ELSE 'Open'
END AS section
FROM invoice_payment_details ipd
JOIN invoice_payment_headers iph
ON iph.id = ipd.invoice_payment_header_id
AND iph.is_deleted = false
JOIN invoice_headers ih
ON ih.id = ipd.invoice_header_id
AND ih.is_deleted = false
WHERE iph.customer_id = p_customer_id
AND ipd.is_deleted = false
AND (p_company_id IS NULL OR iph.company_id = p_company_id)
) timeline_events
GROUP BY section, event_date
) grouped_dates
GROUP BY section
) final_sections;
RETURN COALESCE(result, '[]'::jsonb);
END;
$function$
-- Function: get_invoice_payment_timeline_by_customerid
CREATE OR REPLACE FUNCTION public.get_invoice_payment_timeline_by_customerid(p_customer_id uuid, p_company_id uuid, p_financial_year_start integer)
RETURNS jsonb
LANGUAGE plpgsql
AS $function$
DECLARE
result jsonb;
v_financial_year_start date;
v_financial_year_end date;
BEGIN
/* ---------------------------------------------------------
Financial Year Window (India: Apr 1 → Mar 31)
--------------------------------------------------------- */
v_financial_year_start := TO_DATE(p_financial_year_start || '-04-01', 'YYYY-MM-DD');
v_financial_year_end := TO_DATE((p_financial_year_start + 1) || '-03-31', 'YYYY-MM-DD');
/* ---------------------------------------------------------
Timeline JSON
--------------------------------------------------------- */
SELECT jsonb_agg(
jsonb_build_object(
'section', section,
'dates', dates
)
)
INTO result
FROM (
SELECT
section,
jsonb_agg(date_block ORDER BY date_block->>'date') AS dates
FROM (
SELECT
section,
jsonb_build_object(
'date', event_date,
'dateLabel', to_char(event_date, 'Mon DD'),
'items',
jsonb_agg(
jsonb_build_object(
'eventType', event_type,
'invoiceNumber', invoice_number,
'paymentNumber', payment_number,
'reference', reference,
'title', title,
'timeLabel', time_label,
'status', status,
'amount', amount,
'daysToDue', days_to_due,
'dueLabel', due_label,
'invoiceId', invoice_id,
'paymentId', payment_id
)
ORDER BY event_time
)
) AS date_block
FROM (
/* =====================================================
INVOICE ISSUED (Apartment → Resident)
====================================================== */
SELECT
ih.id AS invoice_id,
NULL::uuid AS payment_id,
ih.invoice_number,
NULL::text AS payment_number,
ih.created_on_utc AS event_time,
ih.due_date::date AS event_date,
'Invoice' AS event_type,
ih.invoice_number AS reference,
'Invoice Issued' AS title,
to_char(ih.created_on_utc, 'HH12:MI AM') AS time_label,
ih.total_amount AS amount,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN 'PAID'
WHEN ih.settled_amount > 0 THEN 'PARTIAL'
WHEN ih.due_date < current_date THEN 'OVERDUE'
ELSE 'DUE'
END AS status,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN NULL
ELSE (ih.due_date::date - current_date)
END AS days_to_due,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN NULL
WHEN ih.due_date::date < current_date
THEN 'Overdue by ' || abs(ih.due_date::date - current_date) || ' days'
ELSE 'Due in ' || (ih.due_date::date - current_date) || ' days'
END AS due_label,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN 'Settled'
ELSE 'Open'
END AS section
FROM invoice_headers ih
WHERE ih.customer_id = p_customer_id
AND ih.company_id = p_company_id
AND ih.is_deleted = false
AND ih.due_date::date BETWEEN v_financial_year_start AND v_financial_year_end
UNION ALL
/* =====================================================
PAYMENT MADE (Resident → Apartment)
====================================================== */
SELECT
ipd.invoice_header_id AS invoice_id,
iph.id AS payment_id,
ih.invoice_number,
iph.payment_number,
iph.received_date AS event_time,
iph.received_date::date AS event_date,
'Payment' AS event_type,
iph.payment_number AS reference,
CASE
WHEN ih.settled_amount >= ih.total_amount
THEN 'Invoice Paid'
ELSE 'Partial Payment Made'
END AS title,
to_char(iph.received_date, 'HH12:MI AM') AS time_label,
ipd.received_amount AS amount,
'PAYMENT' AS status,
NULL::int AS days_to_due,
NULL::text AS due_label,
CASE
WHEN ih.settled_amount >= ih.total_amount THEN 'Settled'
ELSE 'Open'
END AS section
FROM invoice_payment_details ipd
JOIN invoice_payment_headers iph
ON iph.id = ipd.invoice_payment_header_id
AND iph.is_deleted = false
JOIN invoice_headers ih
ON ih.id = ipd.invoice_header_id
AND ih.is_deleted = false
WHERE iph.customer_id = p_customer_id
AND iph.company_id = p_company_id
AND ipd.is_deleted = false
AND iph.received_date::date BETWEEN v_financial_year_start AND v_financial_year_end
) timeline_events
GROUP BY section, event_date
) grouped_dates
GROUP BY section
) final_sections;
RETURN COALESCE(result, '[]'::jsonb);
END;
$function$
-- Function: evaluate_delinquency_for_org
CREATE OR REPLACE FUNCTION public.evaluate_delinquency_for_org(p_organization_id uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_grace_days integer := 0;
v_company_ids uuid[];
v_system_user_id uuid;
BEGIN
/* ====================================================
1) Load latest collection policy
==================================================== */
SELECT COALESCE(cp.grace_days, 0)
INTO v_grace_days
FROM public.collection_policies cp
WHERE cp.organization_id = p_organization_id
AND cp.is_deleted = false
ORDER BY cp.created_on_utc DESC
LIMIT 1;
/* ====================================================
2) Fetch companies for organization
==================================================== */
SELECT COALESCE(array_agg(c.id), ARRAY[]::uuid[])
INTO v_company_ids
FROM public.companies c
WHERE c.organization_id = p_organization_id
AND c.is_deleted = false;
IF array_length(v_company_ids, 1) IS NULL THEN
RETURN;
END IF;
/* ====================================================
3) Resolve system user
==================================================== */
v_system_user_id := public.get_system_user_id();
/* ====================================================
4) Cleanup temp tables (safe for same session)
==================================================== */
DROP TABLE IF EXISTS tmp_overdue_invoices;
DROP TABLE IF EXISTS tmp_calc;
DROP TABLE IF EXISTS tmp_open_cycles;
DROP TABLE IF EXISTS tmp_cycles_created;
DROP TABLE IF EXISTS tmp_cycle_map;
DROP TABLE IF EXISTS tmp_snapshot_inserted;
DROP TABLE IF EXISTS tmp_active_snapshots;
/* ====================================================
5) MATERIALIZE overdue invoices
==================================================== */
CREATE TEMP TABLE tmp_overdue_invoices ON COMMIT DROP AS
SELECT
ih.company_id,
ih.customer_id,
ih.id AS invoice_id,
ih.invoice_date,
ih.due_date::date AS due_date,
ih.total_amount,
COALESCE(ih.settled_amount, 0) AS settled_amount,
(ih.total_amount - COALESCE(ih.settled_amount, 0)) AS outstanding_amount
FROM public.invoice_headers ih
WHERE ih.company_id = ANY (v_company_ids)
AND ih.is_deleted = false
AND (ih.total_amount - COALESCE(ih.settled_amount, 0)) > 0
AND (ih.due_date::date + v_grace_days) < CURRENT_DATE;
/* ====================================================
6) Aggregate delinquency per customer
==================================================== */
CREATE TEMP TABLE tmp_calc ON COMMIT DROP AS
SELECT
customer_id,
MIN(due_date) AS oldest_due_date,
SUM(outstanding_amount) AS overdue_amount,
(CURRENT_DATE - MIN(due_date + v_grace_days))::int AS days_past_due,
public.get_aging_bucket(
(CURRENT_DATE - MIN(due_date + v_grace_days))::int
) AS aging_bucket
FROM tmp_overdue_invoices
GROUP BY customer_id;
/* ====================================================
7) Existing open cycles
==================================================== */
CREATE TEMP TABLE tmp_open_cycles ON COMMIT DROP AS
SELECT
dc.customer_id,
dc.id AS cycle_id
FROM public.delinquency_cycles dc
WHERE dc.organization_id = p_organization_id
AND dc.ended_on_utc IS NULL
AND dc.is_deleted = false;
/* ====================================================
8) Create missing cycles (CTE bridge)
==================================================== */
CREATE TEMP TABLE tmp_cycles_created ON COMMIT DROP AS
WITH ins AS (
INSERT INTO public.delinquency_cycles (
id, organization_id, customer_id,
start_due_date, started_on_utc,
ended_on_utc,
status_id, resolution_state_id,
created_by, created_on_utc, is_deleted
)
SELECT
gen_random_uuid(),
p_organization_id,
c.customer_id,
c.oldest_due_date,
now(),
NULL,
1, -- status id
1, -- resolution status id 1
v_system_user_id,
now(),
false
FROM tmp_calc c
LEFT JOIN tmp_open_cycles oc ON oc.customer_id = c.customer_id
WHERE oc.cycle_id IS NULL
RETURNING customer_id, id AS cycle_id
)
SELECT * FROM ins;
/* ====================================================
9) Cycle map (existing + created)
==================================================== */
CREATE TEMP TABLE tmp_cycle_map ON COMMIT DROP AS
SELECT
c.customer_id,
COALESCE(oc.cycle_id, cc.cycle_id) AS cycle_id,
c.oldest_due_date,
c.overdue_amount,
c.days_past_due,
c.aging_bucket
FROM tmp_calc c
LEFT JOIN tmp_open_cycles oc ON oc.customer_id = c.customer_id
LEFT JOIN tmp_cycles_created cc ON cc.customer_id = c.customer_id;
/* ====================================================
10) Insert snapshot if missing (CTE bridge)
==================================================== */
CREATE TEMP TABLE tmp_snapshot_inserted ON COMMIT DROP AS
WITH ins AS (
INSERT INTO public.delinquency_snapshots (
id, organization_id, customer_id, cycle_id,
overdue_amount, oldest_due_date,
days_past_due, aging_bucket,
last_evaluated_on,
created_by, created_on_utc,
is_deleted
)
SELECT
gen_random_uuid(),
p_organization_id,
cm.customer_id,
cm.cycle_id,
cm.overdue_amount,
cm.oldest_due_date,
cm.days_past_due,
cm.aging_bucket,
now(),
v_system_user_id,
now(),
false
FROM tmp_cycle_map cm
WHERE NOT EXISTS (
SELECT 1
FROM public.delinquency_snapshots ds
WHERE ds.cycle_id = cm.cycle_id
AND ds.is_deleted = false
)
RETURNING cycle_id, id AS snapshot_id
)
SELECT * FROM ins;
/* ====================================================
11) Resolve active snapshot per cycle
==================================================== */
CREATE TEMP TABLE tmp_active_snapshots ON COMMIT DROP AS
SELECT
cm.customer_id,
cm.cycle_id,
COALESCE(
tsi.snapshot_id,
(
SELECT ds.id
FROM public.delinquency_snapshots ds
WHERE ds.cycle_id = cm.cycle_id
AND ds.is_deleted = false
ORDER BY ds.created_on_utc DESC
LIMIT 1
)
) AS snapshot_id
FROM tmp_cycle_map cm
LEFT JOIN tmp_snapshot_inserted tsi ON tsi.cycle_id = cm.cycle_id;
/* ====================================================
12) Update active snapshots
==================================================== */
UPDATE public.delinquency_snapshots ds
SET overdue_amount = c.overdue_amount,
oldest_due_date = c.oldest_due_date,
days_past_due = c.days_past_due,
aging_bucket = c.aging_bucket,
last_evaluated_on = now(),
modified_on_utc = now(),
modified_by = v_system_user_id
FROM tmp_cycle_map c
JOIN tmp_active_snapshots a ON a.cycle_id = c.cycle_id
WHERE ds.id = a.snapshot_id
AND ds.is_deleted = false;
/* ====================================================
13) Refresh snapshot invoices (UPSERT – idempotent)
==================================================== */
-- Soft delete old rows
UPDATE public.delinquency_snapshot_invoices dsi
SET is_deleted = true,
deleted_on_utc = now(),
modified_on_utc = now(),
modified_by = v_system_user_id
WHERE dsi.organization_id = p_organization_id
AND dsi.is_deleted = false
AND EXISTS (
SELECT 1
FROM tmp_active_snapshots a
WHERE a.snapshot_id = dsi.snapshot_id
);
-- Insert / revive latest overdue invoices
INSERT INTO public.delinquency_snapshot_invoices (
id,
organization_id,
company_id,
snapshot_id,
invoice_id,
invoice_date,
due_date,
total_amount,
settled_amount,
outstanding_amount,
created_on_utc,
created_by,
modified_on_utc,
modified_by,
is_deleted,
deleted_on_utc
)
SELECT
gen_random_uuid(),
p_organization_id,
oi.company_id,
a.snapshot_id,
oi.invoice_id,
oi.invoice_date,
oi.due_date,
oi.total_amount,
oi.settled_amount,
oi.outstanding_amount,
now(),
v_system_user_id,
now(),
v_system_user_id,
false,
NULL
FROM tmp_overdue_invoices oi
JOIN tmp_active_snapshots a
ON a.customer_id = oi.customer_id
GROUP BY
oi.company_id,
a.snapshot_id,
oi.invoice_id,
oi.invoice_date,
oi.due_date,
oi.total_amount,
oi.settled_amount,
oi.outstanding_amount
ON CONFLICT (snapshot_id, invoice_id)
DO UPDATE
SET
invoice_date = EXCLUDED.invoice_date,
due_date = EXCLUDED.due_date,
total_amount = EXCLUDED.total_amount,
settled_amount = EXCLUDED.settled_amount,
outstanding_amount = EXCLUDED.outstanding_amount,
is_deleted = false,
deleted_on_utc = NULL,
modified_on_utc = now(),
modified_by = v_system_user_id;
/* ====================================================
14) Close cycles with no overdue invoices
==================================================== */
UPDATE public.delinquency_cycles dc
SET ended_on_utc = now(),
status_id = 2,
modified_on_utc = now(),
modified_by = v_system_user_id
WHERE dc.organization_id = p_organization_id
AND dc.ended_on_utc IS NULL
AND dc.is_deleted = false
AND NOT EXISTS (
SELECT 1
FROM tmp_calc c
WHERE c.customer_id = dc.customer_id
);
/* ====================================================
15) Cleanup snapshots & windows for closed cycles
==================================================== */
UPDATE public.delinquency_snapshots ds
SET is_deleted = true,
deleted_on_utc = now(),
modified_on_utc = now(),
modified_by = v_system_user_id
WHERE ds.organization_id = p_organization_id
AND ds.is_deleted = false
AND EXISTS (
SELECT 1
FROM public.delinquency_cycles dc
WHERE dc.id = ds.cycle_id
AND dc.ended_on_utc IS NOT NULL
AND dc.is_deleted = false
);
UPDATE public.delinquency_resolution_window drw
SET is_deleted = true,
deleted_on_utc = now(),
modified_on_utc = now(),
modified_by = v_system_user_id
WHERE drw.is_deleted = false
AND EXISTS (
SELECT 1
FROM public.delinquency_cycles dc
WHERE dc.id = drw.id
AND dc.organization_id = p_organization_id
AND dc.ended_on_utc IS NOT NULL
AND dc.is_deleted = false
);
END;
$function$
-- Function: evaluate_delinquency_for_org
CREATE OR REPLACE FUNCTION public.evaluate_delinquency_for_org(p_organization_id uuid, p_company_id uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_grace_days integer := 0;
v_system_user_id uuid;
BEGIN
/* ====================================================
1) Load latest collection policy
==================================================== */
SELECT COALESCE(cp.grace_days, 0)
INTO v_grace_days
FROM public.collection_policies cp
WHERE cp.organization_id = p_organization_id
AND cp.is_deleted = false
ORDER BY cp.created_on_utc DESC
LIMIT 1;
v_system_user_id := public.get_system_user_id();
/* ====================================================
4) Cleanup temp tables (safe for same session)
==================================================== */
DROP TABLE IF EXISTS tmp_overdue_invoices;
DROP TABLE IF EXISTS tmp_calc;
DROP TABLE IF EXISTS tmp_open_cycles;
DROP TABLE IF EXISTS tmp_cycles_created;
DROP TABLE IF EXISTS tmp_cycle_map;
DROP TABLE IF EXISTS tmp_snapshot_inserted;
DROP TABLE IF EXISTS tmp_active_snapshots;
/* ====================================================
5) MATERIALIZE overdue invoices
==================================================== */
CREATE TEMP TABLE tmp_overdue_invoices ON COMMIT DROP AS
SELECT
company_id,
customer_id,
invoice_id,
invoice_date,
due_date,
total_amount,
settled_amount,
outstanding_amount
FROM public.get_canonical_overdue_invoices(p_company_id);
/* ====================================================
6) Aggregate delinquency per customer
==================================================== */
CREATE TEMP TABLE tmp_calc ON COMMIT DROP AS
SELECT
customer_id,
MIN(due_date) AS oldest_due_date,
SUM(outstanding_amount) AS overdue_amount,
(CURRENT_DATE - MIN(due_date + v_grace_days))::int AS days_past_due,
public.get_aging_bucket(
(CURRENT_DATE - MIN(due_date + v_grace_days))::int
) AS aging_bucket
FROM tmp_overdue_invoices
GROUP BY customer_id;
/* ====================================================
7) Existing open cycles
==================================================== */
CREATE TEMP TABLE tmp_open_cycles ON COMMIT DROP AS
SELECT
dc.customer_id,
dc.id AS cycle_id
FROM public.delinquency_cycles dc
WHERE dc.company_id = p_company_id
AND dc.ended_on_utc IS NULL
AND dc.is_deleted = false;
/* ====================================================
8) Create missing cycles (CTE bridge)
==================================================== */
CREATE TEMP TABLE tmp_cycles_created ON COMMIT DROP AS
WITH ins AS (
INSERT INTO public.delinquency_cycles (
id, organization_id, company_id, customer_id,
start_due_date, started_on_utc,
ended_on_utc,
status_id, resolution_state_id,
created_by, created_on_utc, is_deleted
)
SELECT
gen_random_uuid(),
p_organization_id,
p_company_id,
c.customer_id,
c.oldest_due_date,
now(),
NULL,
1, -- status id
1, -- resolution status id 1
v_system_user_id,
now(),
false
FROM tmp_calc c
LEFT JOIN tmp_open_cycles oc ON oc.customer_id = c.customer_id
WHERE oc.cycle_id IS NULL
RETURNING customer_id, id AS cycle_id
)
SELECT * FROM ins;
/* ====================================================
9) Cycle map (existing + created)
==================================================== */
CREATE TEMP TABLE tmp_cycle_map ON COMMIT DROP AS
SELECT
c.customer_id,
COALESCE(oc.cycle_id, cc.cycle_id) AS cycle_id,
c.oldest_due_date,
c.overdue_amount,
c.days_past_due,
c.aging_bucket
FROM tmp_calc c
LEFT JOIN tmp_open_cycles oc ON oc.customer_id = c.customer_id
LEFT JOIN tmp_cycles_created cc ON cc.customer_id = c.customer_id;
/* ====================================================
10) Insert snapshot if missing (CTE bridge)
==================================================== */
CREATE TEMP TABLE tmp_snapshot_inserted ON COMMIT DROP AS
WITH ins AS (
INSERT INTO public.delinquency_snapshots (
id, organization_id, company_id, customer_id, cycle_id,
overdue_amount, oldest_due_date,
days_past_due, aging_bucket,
last_evaluated_on,
created_by, created_on_utc,
is_deleted
)
SELECT
gen_random_uuid(),
p_organization_id,
p_company_id,
cm.customer_id,
cm.cycle_id,
cm.overdue_amount,
cm.oldest_due_date,
cm.days_past_due,
cm.aging_bucket,
now(),
v_system_user_id,
now(),
false
FROM tmp_cycle_map cm
WHERE NOT EXISTS (
SELECT 1
FROM public.delinquency_snapshots ds
WHERE ds.cycle_id = cm.cycle_id
AND ds.is_deleted = false
)
RETURNING cycle_id, id AS snapshot_id
)
SELECT * FROM ins;
/* ====================================================
11) Resolve active snapshot per cycle
==================================================== */
CREATE TEMP TABLE tmp_active_snapshots ON COMMIT DROP AS
SELECT
cm.customer_id,
cm.cycle_id,
COALESCE(
tsi.snapshot_id,
(
SELECT ds.id
FROM public.delinquency_snapshots ds
WHERE ds.cycle_id = cm.cycle_id
AND ds.is_deleted = false
ORDER BY ds.created_on_utc DESC
LIMIT 1
)
) AS snapshot_id
FROM tmp_cycle_map cm
LEFT JOIN tmp_snapshot_inserted tsi ON tsi.cycle_id = cm.cycle_id;
/* ====================================================
12) Update active snapshots
==================================================== */
UPDATE public.delinquency_snapshots ds
SET overdue_amount = c.overdue_amount,
oldest_due_date = c.oldest_due_date,
days_past_due = c.days_past_due,
aging_bucket = c.aging_bucket,
last_evaluated_on = now(),
modified_on_utc = now(),
modified_by = v_system_user_id
FROM tmp_cycle_map c
JOIN tmp_active_snapshots a ON a.cycle_id = c.cycle_id
WHERE ds.id = a.snapshot_id
AND ds.is_deleted = false;
/* ====================================================
13) Refresh snapshot invoices (UPSERT – idempotent)
==================================================== */
-- Soft delete old rows
UPDATE public.delinquency_snapshot_invoices dsi
SET is_deleted = true,
deleted_on_utc = now(),
modified_on_utc = now(),
modified_by = v_system_user_id
WHERE dsi.company_id = p_company_id
AND dsi.is_deleted = false
AND EXISTS (
SELECT 1
FROM tmp_active_snapshots a
WHERE a.snapshot_id = dsi.snapshot_id
);
-- Insert / revive latest overdue invoices
INSERT INTO public.delinquency_snapshot_invoices (
id,
company_id,
snapshot_id,
invoice_id,
invoice_date,
due_date,
total_amount,
settled_amount,
outstanding_amount,
created_on_utc,
created_by,
modified_on_utc,
modified_by,
is_deleted,
deleted_on_utc
)
SELECT
gen_random_uuid(),
oi.company_id,
a.snapshot_id,
oi.invoice_id,
oi.invoice_date,
oi.due_date,
oi.total_amount,
oi.settled_amount,
oi.outstanding_amount,
now(),
v_system_user_id,
now(),
v_system_user_id,
false,
NULL
FROM tmp_overdue_invoices oi
JOIN tmp_active_snapshots a
ON a.customer_id = oi.customer_id
GROUP BY
oi.company_id,
a.snapshot_id,
oi.invoice_id,
oi.invoice_date,
oi.due_date,
oi.total_amount,
oi.settled_amount,
oi.outstanding_amount
ON CONFLICT (snapshot_id, invoice_id)
DO UPDATE
SET
invoice_date = EXCLUDED.invoice_date,
due_date = EXCLUDED.due_date,
total_amount = EXCLUDED.total_amount,
settled_amount = EXCLUDED.settled_amount,
outstanding_amount = EXCLUDED.outstanding_amount,
is_deleted = false,
deleted_on_utc = NULL,
modified_on_utc = now(),
modified_by = v_system_user_id;
/* ====================================================
14) Close cycles with no overdue invoices
==================================================== */
UPDATE public.delinquency_cycles dc
SET ended_on_utc = now(),
status_id = 2,
modified_on_utc = now(),
modified_by = v_system_user_id
WHERE dc.organization_id = p_organization_id
AND dc.company_id = p_company_id
AND dc.ended_on_utc IS NULL
AND dc.is_deleted = false
AND NOT EXISTS (
SELECT 1
FROM tmp_calc c
WHERE c.customer_id = dc.customer_id
);
/* ====================================================
15) Cleanup snapshots & windows for closed cycles
==================================================== */
UPDATE public.delinquency_snapshots ds
SET is_deleted = true,
deleted_on_utc = now(),
modified_on_utc = now(),
modified_by = v_system_user_id
WHERE ds.organization_id = p_organization_id
AND ds.company_id = p_company_id
AND ds.is_deleted = false
AND EXISTS (
SELECT 1
FROM public.delinquency_cycles dc
WHERE dc.id = ds.cycle_id
AND dc.ended_on_utc IS NOT NULL
AND dc.is_deleted = false
);
UPDATE public.delinquency_resolution_window drw
SET is_deleted = true,
deleted_on_utc = now(),
modified_on_utc = now(),
modified_by = v_system_user_id
WHERE drw.is_deleted = false
AND EXISTS (
SELECT 1
FROM public.delinquency_cycles dc
WHERE dc.id = drw.id
AND dc.organization_id = p_organization_id
AND dc.company_id = p_company_id
AND dc.ended_on_utc IS NOT NULL
AND dc.is_deleted = false
);
END;
$function$
-- Function: create_delinquency_action
CREATE OR REPLACE FUNCTION public.create_delinquency_action(p_organization_id uuid, p_customer_id uuid, p_cycle_id uuid, p_action_type_code text, p_action_on_utc timestamp with time zone, p_actor uuid, p_resolution_state_code text DEFAULT NULL::text, p_notes text DEFAULT NULL::text, p_meta jsonb DEFAULT NULL::jsonb)
RETURNS jsonb
LANGUAGE plpgsql
AS $function$
DECLARE
v_action_type_id int;
v_resolution_state_id int;
v_action_id uuid := gen_random_uuid();
v_snapshot_id uuid;
BEGIN
-- Validate cycle belongs to org + customer and is open
IF NOT EXISTS (
SELECT 1
FROM public.delinquency_cycles dc
WHERE dc.id = p_cycle_id
AND dc.organization_id = p_organization_id
AND dc.customer_id = p_customer_id
AND dc.is_deleted = false
) THEN
RAISE EXCEPTION 'Invalid cycle for org/customer';
END IF;
-- Resolve action type
SELECT dat.id INTO v_action_type_id
FROM public.delinquency_action_types dat
WHERE dat.code = p_action_type_code;
IF v_action_type_id IS NULL THEN
RAISE EXCEPTION 'Unknown action type code %', p_action_type_code;
END IF;
-- Resolve resolution state
IF p_resolution_state_code IS NOT NULL THEN
SELECT drs.id INTO v_resolution_state_id
FROM public.delinquency_resolution_states drs
WHERE drs.code = p_resolution_state_code;
END IF;
-- Attach current active snapshot (if any)
SELECT ds.id INTO v_snapshot_id
FROM public.delinquency_snapshots ds
WHERE ds.cycle_id = p_cycle_id
AND ds.is_deleted = false
ORDER BY ds.created_on_utc DESC
LIMIT 1;
-- Insert action
INSERT INTO public.delinquency_actions (
id, organization_id, customer_id, cycle_id, snapshot_id,
action_type_id, resolution_state_id,
notes, meta, action_on_utc,
created_by, created_on_utc, is_deleted
)
VALUES (
v_action_id, p_organization_id, p_customer_id, p_cycle_id, v_snapshot_id,
v_action_type_id, v_resolution_state_id,
p_notes, p_meta, p_action_on_utc,
p_actor, now(), false
);
-- Update cycle resolution state if provided
IF v_resolution_state_id IS NOT NULL THEN
UPDATE public.delinquency_cycles
SET resolution_state_id = v_resolution_state_id,
modified_on_utc = now(),
modified_by = p_actor
WHERE id = p_cycle_id;
END IF;
-- Upsert resolution window if TIME_GRANTED or PROMISE_TO_PAY
IF p_resolution_state_code IN ('TIME_GRANTED', 'PROMISE_TO_PAY') THEN
INSERT INTO public.delinquency_resolution_window (
cycle_id, pause_evaluation_until, reason,
created_on_utc, created_by, is_deleted
)
VALUES (
p_cycle_id,
COALESCE(
(p_meta ->> 'expectedDate')::date,
(p_meta ->> 'promiseDate')::date,
CURRENT_DATE
),
p_notes,
now(),
p_actor,
false
)
ON CONFLICT (cycle_id) DO UPDATE
SET pause_evaluation_until = EXCLUDED.pause_evaluation_until,
reason = COALESCE(EXCLUDED.reason, public.delinquency_resolution_window.reason),
is_deleted = false,
modified_by = p_actor,
modified_on_utc = now();
END IF;
-- If SETTLED or WAIVED -> close cycle + deactivate snapshot data
IF p_resolution_state_code IN ('SETTLED', 'WAIVED') THEN
UPDATE public.delinquency_cycles
SET ended_on_utc = now(),
status_id = 2,
modified_on_utc = now(),
modified_by = p_actor
WHERE id = p_cycle_id
AND ended_on_utc IS NULL;
-- Soft delete active snapshot
UPDATE public.delinquency_snapshots
SET is_deleted = true,
deleted_on_utc = now(),
deleted_by = p_actor
WHERE cycle_id = p_cycle_id
AND is_deleted = false;
-- Soft delete snapshot invoices
UPDATE public.delinquency_snapshot_invoices
SET is_deleted = true,
deleted_on_utc = now(),
deleted_by = p_actor
WHERE snapshot_id = v_snapshot_id
AND is_deleted = false;
-- Soft delete resolution window
UPDATE public.delinquency_resolution_window
SET is_deleted = true,
deleted_on_utc = now(),
deleted_by = p_actor
WHERE cycle_id = p_cycle_id
AND is_deleted = false;
END IF;
RETURN jsonb_build_object(
'action',
(SELECT row_to_json(a)
FROM (
SELECT da.id, da.organization_id, da.customer_id, da.cycle_id, da.snapshot_id,
dat.code AS action_type_code,
drs.code AS resolution_state_code,
da.notes, da.meta, da.action_on_utc
FROM public.delinquency_actions da
JOIN public.delinquency_action_types dat ON dat.id = da.action_type_id
LEFT JOIN public.delinquency_resolution_states drs ON drs.id = da.resolution_state_id
WHERE da.id = v_action_id
) a),
'cycle',
(SELECT row_to_json(c)
FROM (
SELECT dc.id, dc.resolution_state_id, dc.ended_on_utc, dc.status_id
FROM public.delinquency_cycles dc
WHERE dc.id = p_cycle_id
) c)
);
END;
$function$
-- Function: create_discussion_message
CREATE OR REPLACE FUNCTION public.create_discussion_message(p_organization_id uuid, p_cycle_id uuid, p_sender_id uuid, p_sender_type text, p_message_text text, p_sent_on_utc timestamp with time zone, p_actor uuid)
RETURNS jsonb
LANGUAGE plpgsql
AS $function$
DECLARE
v_message_id uuid := gen_random_uuid();
v_result jsonb;
BEGIN
-- Verify the cycle exists and belongs to the organization
IF NOT EXISTS(
SELECT 1
FROM public.delinquency_cycles dc
WHERE dc.id = p_cycle_id
AND dc.organization_id = p_organization_id
AND dc.is_deleted = false
) THEN
RAISE EXCEPTION 'Delinquency cycle % not found or does not belong to organization %', p_cycle_id, p_organization_id;
END IF;
-- Validate sender_type
IF p_sender_type NOT IN ('RESIDENT', 'FACILITY_MANAGER') THEN
RAISE EXCEPTION 'Invalid sender_type: %. Must be RESIDENT or FACILITY_MANAGER', p_sender_type;
END IF;
-- Insert the message
INSERT INTO public.delinquency_discussion_messages (
id,
organization_id,
cycle_id,
sender_id,
sender_type,
message_text,
sent_on_utc,
created_by,
created_on_utc,
is_deleted
)
VALUES (
v_message_id,
p_organization_id,
p_cycle_id,
p_sender_id,
p_sender_type,
p_message_text,
p_sent_on_utc,
p_actor,
now(),
false
);
-- Return the created message
v_result := jsonb_build_object(
'message',
(SELECT row_to_json(msg)
FROM (SELECT ddm.id,
ddm.cycle_id,
ddm.sender_id,
ddm.sender_type,
ddm.message_text,
ddm.sent_on_utc
FROM public.delinquency_discussion_messages ddm
WHERE ddm.id = v_message_id) msg)
);
RETURN v_result;
END;
$function$
-- Function: get_defaulter_contact
CREATE OR REPLACE FUNCTION public.get_defaulter_contact(p_customer_id uuid)
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
v_contact_number text;
BEGIN
SELECT
r.contact_number INTO v_contact_number
FROM
fdw_community.units u
INNER JOIN
fdw_community.resident_units ru ON u.id = ru.unit_id
INNER JOIN
fdw_community.residents r ON ru.resident_id = r.id
WHERE
u.customer_id = p_customer_id
AND u.is_deleted = false
AND ru.is_deleted = false
AND r.is_deleted = false
AND r.contact_number IS NOT NULL
LIMIT 1;
RETURN v_contact_number;
END;
$function$
-- Function: get_defaulter_list
CREATE OR REPLACE FUNCTION public.get_defaulter_list(p_company_id uuid)
RETURNS TABLE(id integer, cycle_id uuid, customer_id uuid, snapshot_id uuid, overdue_amount numeric, days_past_due integer, aging_bucket text, resolution_state text, is_anonymized boolean, can_reveal_unit boolean, next_expected_action_date date, customer_name text, resident_contact text, oldest_due_date date)
LANGUAGE plpgsql
AS $function$
DECLARE
v_organization_id uuid;
BEGIN
/* ------------------------------------------------------------
1️⃣ Resolve organization_id
------------------------------------------------------------ */
SELECT c.organization_id
INTO v_organization_id
FROM public.companies c
WHERE c.id = p_company_id;
/* ------------------------------------------------------------
2️⃣ Main query
------------------------------------------------------------ */
RETURN QUERY
WITH policy AS (
SELECT
COALESCE(cp.grace_days, 0)::integer AS grace_days,
COALESCE(cp.anonymize_until_days, 0)::integer AS anonymize_until_days,
COALESCE(cp.reveal_unit_after_days, 0)::integer AS reveal_unit_after_days
FROM public.collection_policies cp
WHERE cp.organization_id = v_organization_id
AND cp.is_deleted = false
ORDER BY cp.created_on_utc DESC
LIMIT 1
),
open_cycles AS (
SELECT
dc.id,
dc.customer_id,
dc.resolution_state_id
FROM public.delinquency_cycles dc
WHERE dc.company_id = p_company_id
AND dc.ended_on_utc IS NULL
AND dc.is_deleted = false
),
active_snapshots AS (
SELECT
ds.id,
ds.cycle_id,
ds.overdue_amount,
ds.days_past_due,
ds.aging_bucket::text,
ds.oldest_due_date::date
FROM public.delinquency_snapshots ds
WHERE ds.company_id = p_company_id
AND ds.is_deleted = false
),
last_action AS (
SELECT DISTINCT ON (da.cycle_id)
da.cycle_id,
da.meta,
da.action_on_utc
FROM public.delinquency_actions da
WHERE da.company_id = p_company_id
AND da.is_deleted = false
ORDER BY da.cycle_id, da.action_on_utc DESC
),
base AS (
SELECT
/* row_number() → bigint → integer */
row_number() OVER (
ORDER BY ds.days_past_due DESC, ds.overdue_amount DESC
)::integer AS row_no,
oc.id AS cycle_id,
oc.customer_id,
ds.id AS snapshot_id,
ds.overdue_amount,
ds.days_past_due,
ds.aging_bucket,
rs.code::text AS resolution_state_code,
/* Policy-driven flags */
(ds.days_past_due < COALESCE(p.anonymize_until_days, 0)) AS is_anonymized,
(ds.days_past_due >= COALESCE(p.reveal_unit_after_days, 0)) AS can_reveal_unit,
/* JSON → text → date */
NULLIF(la.meta ->> 'expectedDate', '')::date
AS next_expected_action_date,
cust.name::text AS customer_name,
r.contact_number::text AS resident_contact,
ds.oldest_due_date
FROM open_cycles oc
JOIN active_snapshots ds
ON ds.cycle_id = oc.id
LEFT JOIN policy p ON TRUE
LEFT JOIN last_action la ON la.cycle_id = oc.id
LEFT JOIN public.delinquency_resolution_states rs
ON rs.id = oc.resolution_state_id
LEFT JOIN public.customers cust
ON cust.id = oc.customer_id
/* ---------- Community FDW joins ---------- */
LEFT JOIN fdw_community.units u
ON u.customer_id = oc.customer_id
AND u.is_deleted = false
/* Pick exactly ONE resident per unit */
LEFT JOIN LATERAL (
SELECT DISTINCT ON (ru.unit_id)
ru.unit_id,
ru.resident_id
FROM fdw_community.resident_units ru
JOIN fdw_community.residents r2
ON r2.id = ru.resident_id
AND r2.is_deleted = false
AND r2.contact_number IS NOT NULL
WHERE ru.unit_id = u.id
AND ru.is_deleted = false
ORDER BY
ru.unit_id,
ru.is_primary_owner DESC,
ru.created_on_utc ASC
) ru ON TRUE
LEFT JOIN fdw_community.residents r
ON r.id = ru.resident_id
/* Visibility rule */
WHERE ds.days_past_due >= COALESCE(p.reveal_unit_after_days, 0)
)
SELECT
b.row_no AS id,
b.cycle_id,
b.customer_id,
b.snapshot_id,
b.overdue_amount,
b.days_past_due,
b.aging_bucket,
COALESCE(b.resolution_state_code, 'NONE')::text AS resolution_state,
b.is_anonymized,
b.can_reveal_unit,
b.next_expected_action_date,
b.customer_name,
b.resident_contact,
b.oldest_due_date
FROM base b
ORDER BY
b.days_past_due DESC,
b.overdue_amount DESC;
END;
$function$
-- Function: get_defaulter_details
CREATE OR REPLACE FUNCTION public.get_defaulter_details(p_company_id uuid, p_customer_id uuid)
RETURNS jsonb
LANGUAGE plpgsql
AS $function$
DECLARE
v_cycle_id uuid;
v_snapshot_id uuid;
BEGIN
SELECT dc.id
INTO v_cycle_id
FROM public.delinquency_cycles dc
WHERE dc.company_id = p_company_id
AND dc.customer_id = p_customer_id
AND dc.ended_on_utc IS NULL
AND dc.is_deleted = false
ORDER BY dc.started_on_utc DESC
LIMIT 1;
IF v_cycle_id IS NULL THEN
RETURN '{}'::jsonb;
END IF;
SELECT ds.id
INTO v_snapshot_id
FROM public.delinquency_snapshots ds
WHERE ds.cycle_id = v_cycle_id
AND ds.is_deleted = false
ORDER BY ds.created_on_utc DESC
LIMIT 1;
RETURN jsonb_build_object(
'cycle',
(SELECT row_to_json(c)
FROM (
SELECT dc.id, dc.organization_id, dc.company_id, dc.customer_id,
dc.start_due_date, dc.started_on_utc, dc.ended_on_utc,
dc.status_id, dc.resolution_state_id
FROM public.delinquency_cycles dc
WHERE dc.id = v_cycle_id
) c),
'snapshot',
(SELECT row_to_json(s)
FROM (
SELECT ds.id, ds.cycle_id, ds.company_id, ds.customer_id,
ds.overdue_amount, ds.oldest_due_date,
ds.days_past_due, ds.aging_bucket,
ds.last_evaluated_on
FROM public.delinquency_snapshots ds
WHERE ds.id = v_snapshot_id
) s),
'invoices',
(SELECT COALESCE(jsonb_agg(row_to_json(inv)), '[]'::jsonb)
FROM (
SELECT dsi.invoice_id, dsi.snapshot_id, dsi.company_id,
dsi.invoice_date, dsi.due_date,
dsi.total_amount, dsi.settled_amount, dsi.outstanding_amount,
ih.invoice_number, ih.note
FROM public.delinquency_snapshot_invoices dsi
JOIN public.invoice_headers ih ON ih.id = dsi.invoice_id
WHERE dsi.snapshot_id = v_snapshot_id
AND dsi.is_deleted = false
) inv),
'actions',
(SELECT COALESCE(jsonb_agg(row_to_json(act) ORDER BY act.action_on_utc DESC), '[]'::jsonb)
FROM (
SELECT da.id, da.cycle_id, da.customer_id, da.organization_id, da.snapshot_id,
dat.code AS action_type_code,
drs.code AS resolution_state_code,
da.notes, da.meta, da.action_on_utc
FROM public.delinquency_actions da
JOIN public.delinquency_action_types dat ON dat.id = da.action_type_id
LEFT JOIN public.delinquency_resolution_states drs ON drs.id = da.resolution_state_id
WHERE da.cycle_id = v_cycle_id
AND da.is_deleted = false
) act)
);
END;
$function$
-- Function: get_canonical_overdue_invoices
CREATE OR REPLACE FUNCTION public.get_canonical_overdue_invoices(p_company_id uuid)
RETURNS TABLE(company_id uuid, customer_id uuid, invoice_id uuid, invoice_date date, due_date date, total_amount numeric, settled_amount numeric, outstanding_amount numeric, days_past_due integer)
LANGUAGE sql
STABLE
AS $function$
SELECT
ih.company_id,
ih.customer_id,
ih.id AS invoice_id,
ih.invoice_date::date,
ih.due_date::date,
ih.total_amount,
COALESCE(ih.settled_amount, 0) AS settled_amount,
(ih.total_amount - COALESCE(ih.settled_amount, 0)) AS outstanding_amount,
(CURRENT_DATE - (ih.due_date::date + cp.grace_days))::int AS days_past_due
FROM public.invoice_headers ih
JOIN public.companies co
ON co.id = ih.company_id
AND co.is_deleted = false
JOIN public.collection_policies cp
ON cp.organization_id = co.organization_id
AND cp.is_deleted = false
WHERE ih.company_id = p_company_id
AND ih.is_deleted = false
AND (ih.total_amount - COALESCE(ih.settled_amount, 0)) > 0
AND (ih.due_date::date + cp.grace_days) < CURRENT_DATE;
$function$
-- Function: get_defaulter_summary
CREATE OR REPLACE FUNCTION public.get_defaulter_summary(p_company_id uuid)
RETURNS TABLE(id integer, total_defaulters_count integer, total_outstanding numeric)
LANGUAGE sql
STABLE
AS $function$
SELECT
1 AS id,
COUNT(DISTINCT customer_id)::int AS total_defaulters_count,
SUM(outstanding_amount) AS total_outstanding
FROM public.get_canonical_overdue_invoices(p_company_id);
$function$
-- Procedure: insert_invoice_by_excel
CREATE OR REPLACE PROCEDURE public.insert_invoice_by_excel(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_invoice_date timestamp without time zone, IN p_invoice_number text, IN p_payment_term integer, IN p_invoice_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_so_no text, IN p_so_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_invoice_voucher text, IN p_created_by uuid, IN p_invoice_details jsonb, IN p_type integer, IN p_settled_amount numeric DEFAULT 0)
LANGUAGE plpgsql
AS $procedure$
DECLARE
detail RECORD;
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
v_invoice_number text;
BEGIN
-- Set default values for warehouses
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
IF p_invoice_number IS NULL OR p_invoice_number = '' THEN
v_invoice_number := get_new_invoice_number(p_company_id, p_invoice_date::date);
ELSE
v_invoice_number = p_invoice_number;
END IF;
-- Validate JSON structure
BEGIN
PERFORM jsonb_array_elements(p_invoice_details)::jsonb;
EXCEPTION
WHEN others THEN
RAISE EXCEPTION 'Invalid JSON format for invoice details';
END;
RAISE NOTICE 'Processing Invoice: %', p_id;
-- Insert into the invoice header
INSERT INTO
public.invoice_headers(
id,
company_id,
customer_id,
credit_account_id,
debit_account_id,
invoice_number,
invoice_date,
due_date,
payment_term,
taxable_amount,
cgst_amount,
sgst_amount,
igst_amount,
total_amount,
discount,
fees,
round_off,
currency_id,
note,
invoice_status_id,
created_by,
invoice_voucher,
source_warehouse_id,
destination_warehouse_id,
created_on_utc,
so_no,
so_date,
type
)
VALUES (
p_id,
p_company_id,
p_customer_id,
p_credit_account_id,
p_debit_account_id,
v_invoice_number,
p_invoice_date,
p_due_date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_currency_id,
p_note,
p_invoice_status_id,
p_created_by,
p_invoice_voucher,
p_source_warehouse_id,
p_destination_warehouse_id,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
p_so_no,
p_so_date,
p_type
);
RAISE NOTICE 'Invoice header inserted with ID: %', p_id;
-- Loop through the JSONB array of invoice details
FOR detail IN
SELECT * FROM jsonb_to_recordset(p_invoice_details) AS (
id uuid, product_id uuid, price numeric, quantity numeric,
fees numeric, discount numeric, taxable_amount numeric,
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
total_amount numeric, serial_number integer
)
LOOP
-- Insert into invoice details
INSERT INTO public.invoice_details (
id, invoice_header_id, product_id, price, quantity, fees, discount,
taxable_amount, sgst_amount, cgst_amount, igst_amount, total_amount,
serial_number, is_deleted
) VALUES (
detail.id, p_id, detail.product_id, detail.price, detail.quantity, detail.fees,
detail.discount, detail.taxable_amount, detail.sgst_amount, detail.cgst_amount,
detail.igst_amount, detail.total_amount, detail.serial_number, false
);
RAISE NOTICE 'Invoice detail inserted with ID: %', detail.id;
END LOOP;
-- No explicit COMMIT here
RAISE NOTICE 'Transaction completed successfully';
EXCEPTION
-- Catch all other exceptions
WHEN OTHERS THEN
RAISE EXCEPTION 'An error occurred: %', SQLERRM;
END;
$procedure$
-- Procedure: create_invoice_template
CREATE OR REPLACE PROCEDURE public.create_invoice_template(IN p_invoiceheader_id uuid, IN p_frequency_cycle text, IN p_month_of_year integer, IN p_day_of_month integer, IN p_day_of_week integer, IN p_time_of_day time without time zone, IN p_starts_from date, IN p_end_date date, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_template_id uuid := gen_random_uuid(); -- Generate a new UUID for the template ID
BEGIN
-- Insert the new template into the invoice_template table
INSERT INTO public.invoice_template (
id, invoice_header_id, frequency_cycle, month_of_year, day_of_month,
day_of_week, time_of_day, starts_from, end_date, created_on_utc, created_by, is_deleted
)
VALUES (
v_template_id, p_invoiceheader_id, p_frequency_cycle, p_month_of_year, p_day_of_month,
p_day_of_week, p_time_of_day, p_starts_from, p_end_date, NOW(), p_created_by, false
);
RAISE NOTICE 'Invoice template created successfully with ID: %', v_template_id;
END;
$procedure$
-- Procedure: create_multiple_invoice_payments
CREATE OR REPLACE PROCEDURE public.create_multiple_invoice_payments(IN p_invoice_payments jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
payment RECORD;
payment_statuses TEXT[] := ARRAY[]::TEXT[];
BEGIN
-- Loop through each payment in the JSONB array
FOR payment IN
SELECT * FROM jsonb_to_recordset(p_invoice_payments) AS (
invoice_payment_header_id uuid,
company_id uuid,
customer_id uuid,
received_date date,
mode_of_payment text,
credit_account_id uuid,
debit_account_id uuid,
reference text,
received_amount numeric,
grand_total_amount numeric,
advance_amount numeric,
description text,
tds_amount numeric,
created_by uuid,
invoice_payment_details jsonb
)
LOOP
BEGIN
IF NOT EXISTS (
SELECT 1
FROM public.invoice_payment_headers
WHERE reference = payment.reference
AND company_id = payment.company_id
) THEN
CALL public.create_invoice_payment(
payment.invoice_payment_header_id,
payment.company_id,
payment.customer_id,
payment.received_date,
payment.mode_of_payment,
payment.credit_account_id,
payment.debit_account_id,
payment.reference,
payment.received_amount,
payment.grand_total_amount,
payment.advance_amount,
payment.description,
payment.tds_amount,
payment.created_by,
payment.invoice_payment_details
);
payment_statuses := payment_statuses || format('%s:success', payment.invoice_payment_header_id);
ELSE
RAISE NOTICE 'Duplicate invoice payment found for reference: %, company_id: %. Skipping.',
payment.reference, payment.company_id;
payment_statuses := payment_statuses || format('%s:duplicate', payment.invoice_payment_header_id);
CONTINUE;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Error processing payment ID: %, Error: %',
payment.invoice_payment_header_id, SQLERRM;
payment_statuses := payment_statuses || format('%s:failed', payment.invoice_payment_header_id);
END;
END LOOP;
-- Final status notice (for app to parse)
RAISE NOTICE 'PAYMENT_STATUS:%', to_json(payment_statuses);
RAISE NOTICE 'All invoice payments processed.';
END;
$procedure$
-- Procedure: get_all_customer_names
CREATE OR REPLACE PROCEDURE public.get_all_customer_names(IN p_company_id uuid, OUT customer_list jsonb)
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Fetch customer details (Id, Name) for the given CompanyId
SELECT jsonb_agg(
jsonb_build_object(
'Id', c.id,
'Name', c.name
)
)
INTO customer_list
FROM public.customers c
WHERE c.company_id = p_company_id;
-- If no customers are found, return an empty array
IF customer_list IS NULL THEN
customer_list := '[]'::jsonb;
END IF;
END;
$procedure$
-- Procedure: run_grouped_invoices
-- SOURCE
CREATE OR REPLACE PROCEDURE public.run_grouped_invoices(IN p_template_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
schedule_record RECORD;
v_unit RECORD;
v_group_invoice_header_id UUID;
v_invoice_header_id UUID;
v_total_amount NUMERIC;
v_status TEXT;
v_error_message TEXT;
v_total_count INTEGER;
v_success_count INTEGER;
v_calculation_type INTEGER;
v_company_id UUID;
v_sales_account_id UUID;
v_account_receivable_id UUID;
v_invoice_date DATE;
v_starts_from DATE;
v_end_date DATE;
v_due_date DATE;
v_payment_term INTEGER;
v_currency_id INTEGER;
v_fixed_product_id UUID;
v_bhk_product_id UUID;
v_sqft_product_id UUID;
v_fixed_product_rate NUMERIC;
v_bhk_product_rate NUMERIC;
v_sqft_product_rate NUMERIC;
v_note TEXT;
v_invoice_status_id INTEGER;
v_due_days INTEGER;
v_billing_cycle TEXT;
v_active_status BOOLEAN;
v_day_of_week INTEGER;
v_day_of_month INTEGER;
v_month_of_year INTEGER;
v_time_of_day TIME;
v_created_by UUID;
v_group_invoice_number VARCHAR;
v_serial_number INTEGER := 1;
v_product_rate NUMERIC;
v_quantity NUMERIC;
v_product_id UUID;
v_template_exists BOOL;
v_template_is_deleted BOOL;
BEGIN
RAISE NOTICE '[START] run_grouped_invoices(template_id=%)', p_template_id;
-- Extra visibility on template state
SELECT EXISTS (SELECT 1 FROM public.group_invoice_templates WHERE id = p_template_id) AS exists,
COALESCE( (SELECT is_deleted FROM public.group_invoice_templates WHERE id = p_template_id LIMIT 1), NULL) AS is_deleted
INTO v_template_exists, v_template_is_deleted;
RAISE NOTICE '[CHECK] template exists? %, is_deleted? %', v_template_exists, v_template_is_deleted;
IF NOT EXISTS (
SELECT 1 FROM public.group_invoice_templates
WHERE id = p_template_id AND is_deleted = FALSE
) THEN
RAISE EXCEPTION 'Invalid template_id: % (exists=%, is_deleted=%)', p_template_id, v_template_exists, v_template_is_deleted;
END IF;
v_time_of_day := CURRENT_TIME;
RAISE NOTICE '[INFO] current_time=%', v_time_of_day;
FOR schedule_record IN
SELECT *
FROM batch_schedules bs
WHERE bs.group_invoice_template_id = p_template_id
AND bs.is_deleted = FALSE
AND bs.last_executed_at IS DISTINCT FROM CURRENT_DATE::timestamp
LOOP
RAISE NOTICE '[SCHEDULE] processing schedule_id=% last_executed_at=%', schedule_record.id, schedule_record.last_executed_at;
SELECT calculation_type_id, company_id, sales_account_id, invoice_date, starts_from, grace_period, due_days,
fixed_rate, bhk_rate, sqft_rate, invoice_description, billing_cycle, account_receivable_id,
currency_id, bhk_product_id, fixed_product_id, sqft_product_id, end_date, created_by, active_status
INTO v_calculation_type, v_company_id, v_sales_account_id, v_invoice_date, v_starts_from, v_payment_term,
v_due_days, v_fixed_product_rate, v_bhk_product_rate, v_sqft_product_rate, v_note, v_billing_cycle,
v_account_receivable_id, v_currency_id, v_bhk_product_id, v_fixed_product_id, v_sqft_product_id, v_end_date,
v_created_by, v_active_status
FROM public.group_invoice_templates git
WHERE git.id = p_template_id;
RAISE NOTICE '[TEMPLATE] company_id=% calc_type=% inv_date=% starts_from=% due_days=% end_date=% active=%',
v_company_id, v_calculation_type, v_invoice_date, v_starts_from, v_due_days, v_end_date, v_active_status;
-- Force System user
SELECT id INTO v_created_by
FROM users
WHERE first_name = 'System'
LIMIT 1;
RAISE NOTICE '[USER] created_by (System) id=%', v_created_by;
IF v_created_by IS NULL THEN
RAISE EXCEPTION 'System user not found in the users table';
END IF;
IF NOT v_active_status THEN
RAISE NOTICE '[SKIP] template inactive';
CONTINUE;
END IF;
v_due_date := v_invoice_date + v_due_days;
RAISE NOTICE '[DATES] invoice_date=% due_days=% due_date=%', v_invoice_date, v_due_days, v_due_date;
IF v_starts_from IS NOT NULL AND v_due_date < v_starts_from THEN
RAISE NOTICE '[SKIP] due_date % < starts_from %', v_due_date, v_starts_from;
CONTINUE;
END IF;
IF v_end_date IS NOT NULL AND v_end_date < CURRENT_DATE THEN
RAISE NOTICE '[SKIP] end_date % < today %', v_end_date, CURRENT_DATE;
CONTINUE;
END IF;
v_group_invoice_header_id := gen_random_uuid();
v_total_count := 0;
v_success_count := 0;
v_status := 'pending';
v_error_message := '';
v_group_invoice_number := get_new_group_invoice_number(v_company_id, v_invoice_date);
RAISE NOTICE '[GROUP] new group_invoice_header_id=% number=%', v_group_invoice_header_id, v_group_invoice_number;
INSERT INTO public.group_invoice_headers (
id, group_invoice_template_id, execution_date, success_count,
created_on_utc, created_by, total_count, error_message, company_id, group_invoice_number
)
VALUES (
v_group_invoice_header_id, p_template_id, NOW(), v_success_count,
NOW(), v_created_by, v_total_count, v_error_message, v_company_id, v_group_invoice_number
);
SELECT status
INTO v_invoice_status_id
FROM public.invoice_workflow iw
WHERE iw.company_id = v_company_id
AND iw.is_deleted = FALSE
AND iw.status = 3
LIMIT 1;
RAISE NOTICE '[WORKFLOW] invoice_status_id=%', v_invoice_status_id;
FOR v_unit IN
SELECT unit_id, bhk, sqft_area, customer_id
FROM public.group_invoice_template_customers gitc
WHERE gitc.group_invoice_template_id = p_template_id
LOOP
BEGIN
v_total_count := v_total_count + 1;
v_invoice_header_id := gen_random_uuid();
v_total_amount := 0;
v_serial_number := 1;
RAISE NOTICE ' [UNIT] unit_id=% customer_id=% bhk=% sqft=%', v_unit.unit_id, v_unit.customer_id, v_unit.bhk, v_unit.sqft_area;
-- Compute total first
CASE v_calculation_type
WHEN 1 THEN v_total_amount := v_fixed_product_rate;
WHEN 2 THEN v_total_amount := v_bhk_product_rate * (v_unit.bhk)::NUMERIC;
WHEN 3 THEN v_total_amount := v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC;
WHEN 4 THEN v_total_amount := v_fixed_product_rate + (v_bhk_product_rate * (v_unit.bhk)::NUMERIC);
WHEN 5 THEN v_total_amount := v_fixed_product_rate + (v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC);
ELSE RAISE EXCEPTION 'Invalid calculation type: %', v_calculation_type;
END CASE;
RAISE NOTICE ' [AMOUNT] calc_type=% total_amount=%', v_calculation_type, v_total_amount;
-- HEADER first (fixes FK)
CALL public.create_invoice_header(
v_invoice_header_id,
v_company_id,
v_unit.customer_id,
v_account_receivable_id,
v_sales_account_id,
v_invoice_date,
v_due_date,
v_payment_term,
v_total_amount,
0,0,0,
v_total_amount,
0,0,0,
v_currency_id,
v_note,
v_invoice_status_id,
v_created_by,
'IVAP000',
'00000000-0000-0000-0000-000000000000',
'00000000-0000-0000-0000-000000000000',
'AP',
v_invoice_date,
3
);
RAISE NOTICE ' [HEADER CREATED] invoice_header_id=%', v_invoice_header_id;
-- DETAILS now
CASE v_calculation_type
WHEN 1 THEN
v_product_rate := v_fixed_product_rate; v_quantity := 1; v_product_id := v_fixed_product_id;
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
RAISE NOTICE ' [DETAIL ADDED] fixed rate line';
WHEN 2 THEN
v_product_rate := v_bhk_product_rate * (v_unit.bhk)::NUMERIC; v_quantity := 1; v_product_id := v_bhk_product_id;
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
RAISE NOTICE ' [DETAIL ADDED] bhk line';
WHEN 3 THEN
v_product_rate := v_sqft_product_rate; v_quantity := v_unit.sqft_area; v_product_id := v_sqft_product_id;
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate*v_quantity, v_product_rate*v_quantity, v_serial_number, v_product_id);
RAISE NOTICE ' [DETAIL ADDED] sqft line';
WHEN 4 THEN
-- fixed
v_product_rate := v_fixed_product_rate; v_quantity := 1; v_product_id := v_fixed_product_id;
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
v_serial_number := v_serial_number + 1;
-- bhk
v_product_rate := v_bhk_product_rate * (v_unit.bhk)::NUMERIC; v_quantity := 1; v_product_id := v_bhk_product_id;
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
RAISE NOTICE ' [DETAIL ADDED] fixed + bhk lines';
WHEN 5 THEN
-- fixed
v_product_rate := v_fixed_product_rate; v_quantity := 1; v_product_id := v_fixed_product_id;
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate, v_product_rate, v_serial_number, v_product_id);
v_serial_number := v_serial_number + 1;
-- sqft
v_product_rate := v_sqft_product_rate; v_quantity := v_unit.sqft_area; v_product_id := v_sqft_product_id;
CALL public.create_invoice_detail(v_invoice_header_id, v_product_rate, v_quantity, 0,0, 0,0,0, v_product_rate*v_quantity, v_product_rate*v_quantity, v_serial_number, v_product_id);
RAISE NOTICE ' [DETAIL ADDED] fixed + sqft lines';
END CASE;
v_status := 'success';
v_success_count := v_success_count + 1;
INSERT INTO public.group_invoice_details(
id, group_invoice_header_id, unit_id, invoice_header_id, status, posted_on,
error_message, created_on_utc, created_by, company_id
)
VALUES (
gen_random_uuid(), v_group_invoice_header_id, v_unit.unit_id, v_invoice_header_id, v_status, NOW(),
'', NOW(), v_created_by, v_company_id
);
RAISE NOTICE ' [UNIT DONE] unit_id=% status=%', v_unit.unit_id, v_status;
EXCEPTION
WHEN OTHERS THEN
v_status := 'failed';
v_error_message := COALESCE(SQLERRM, 'Unknown error');
RAISE NOTICE ' [UNIT ERROR] unit_id=% sqlstate=% errmsg=%', v_unit.unit_id, SQLSTATE, v_error_message;
INSERT INTO public.group_invoice_details(
id, group_invoice_header_id, unit_id, status, invoice_header_id, error_message, posted_on,
created_on_utc, created_by, company_id
)
VALUES (
gen_random_uuid(), v_group_invoice_header_id, v_unit.unit_id, v_status, v_invoice_header_id,
v_error_message, NOW(), NOW(), v_created_by, v_company_id
);
END;
END LOOP;
UPDATE public.group_invoice_headers
SET success_count = v_success_count,
total_count = v_total_count,
modified_on_utc = NOW(),
modified_by = v_created_by,
error_message = COALESCE(v_error_message, '')
WHERE id = v_group_invoice_header_id;
RAISE NOTICE '[GROUP DONE] group_header_id=% success=% total=%', v_group_invoice_header_id, v_success_count, v_total_count;
UPDATE batch_schedules
SET last_executed_at = NOW()
WHERE id = schedule_record.id;
RAISE NOTICE '[SCHEDULE DONE] schedule_id=% last_executed_at updated', schedule_record.id;
END LOOP;
RAISE NOTICE '[END] run_grouped_invoices(template_id=%)', p_template_id;
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.run_grouped_invoices(IN p_template_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
schedule_record RECORD;
v_unit RECORD;
v_group_invoice_header_id UUID;
v_invoice_header_id UUID;
v_total_amount NUMERIC;
v_status TEXT;
v_error_message TEXT;
v_total_count INTEGER;
v_success_count INTEGER;
v_calculation_type INTEGER;
v_company_id UUID;
v_sales_account_id UUID;
v_account_receivable_id UUID;
v_invoice_date DATE;
v_starts_from DATE;
v_end_date DATE;
v_due_date DATE;
v_payment_term INTEGER;
v_currency_id INTEGER;
v_fixed_product_id UUID;
v_bhk_product_id UUID;
v_sqft_product_id UUID;
v_fixed_product_rate NUMERIC;
v_bhk_product_rate NUMERIC;
v_sqft_product_rate NUMERIC;
v_note TEXT;
v_invoice_status_id INTEGER;
v_due_days INTEGER;
v_billing_cycle TEXT;
v_active_status BOOLEAN;
v_half_year INTEGER;
v_quarters_of_month INTEGER;
v_bi_month INTEGER;
v_day_of_week INTEGER;
v_day_of_month INTEGER;
v_month_of_year INTEGER;
v_time_of_day TIME; -- Change to store time directly
v_created_by UUID; -- Store the system user ID
v_group_invoice_number VARCHAR;
v_serial_number INTEGER := 1; -- Initialize serial number
v_product_rate NUMERIC;
v_quantity NUMERIC;
v_product_id UUID;
BEGIN
-- Set the current time for execution
v_time_of_day := CURRENT_TIME;
-- Validate input: Ensure p_template_id is valid
IF NOT EXISTS (
SELECT 1
FROM public.group_invoice_templates
WHERE id = p_template_id AND is_deleted = FALSE
) THEN
RAISE EXCEPTION 'Invalid template_id: %', p_template_id;
END IF;
-- Loop through batch schedules for the specific template_id
FOR schedule_record IN
SELECT *
FROM batch_schedules bs
WHERE bs.group_invoice_template_id = p_template_id
AND bs.is_deleted = FALSE
AND bs.last_executed_at IS DISTINCT FROM CURRENT_DATE::timestamp
LOOP
-- Fetch template details
SELECT calculation_type_id, company_id, sales_account_id, invoice_date, starts_from, grace_period, due_days,
fixed_rate, bhk_rate, sqft_rate, invoice_description, billing_cycle, account_receivable_id,
currency_id, bhk_product_id, fixed_product_id, sqft_product_id, end_date, created_by, active_status
INTO v_calculation_type, v_company_id, v_sales_account_id, v_invoice_date, v_starts_from, v_payment_term,
v_due_days, v_fixed_product_rate, v_bhk_product_rate, v_sqft_product_rate, v_note, v_billing_cycle,
v_account_receivable_id, v_currency_id, v_bhk_product_id, v_fixed_product_id, v_sqft_product_id, v_end_date,
v_created_by, v_active_status
FROM public.group_invoice_templates git
WHERE git.id = p_template_id;
-- Fetch the system user ID from the users table
SELECT id
INTO v_created_by
FROM users
WHERE first_name = 'System'
LIMIT 1;
-- Raise an exception if the system user is not found
IF v_created_by IS NULL THEN
RAISE EXCEPTION 'System user not found in the users table';
END IF;
-- Skip processing if the template is not active
IF NOT v_active_status THEN
RAISE NOTICE 'Skipping template with ID: %, as active_status is FALSE', p_template_id;
CONTINUE;
END IF;
-- Calculate the due date
v_due_date := v_invoice_date + v_due_days;
-- Skip processing if the due date is before the starts_from date
IF v_starts_from IS NOT NULL AND v_due_date < v_starts_from THEN
CONTINUE;
END IF;
-- Skip processing if the template has ended
IF v_end_date IS NOT NULL AND v_end_date < CURRENT_DATE THEN
CONTINUE;
END IF;
v_group_invoice_header_id := gen_random_uuid();
v_total_count := 0;
v_success_count := 0;
v_status := 'pending';
v_error_message := '';
v_group_invoice_number := get_new_group_invoice_number(v_company_id, v_invoice_date);
-- Insert into group_invoice_headers
INSERT INTO public.group_invoice_headers (
id, group_invoice_template_id, execution_date, success_count, created_on_utc, created_by, total_count, error_message, company_id, group_invoice_number
)
VALUES (
v_group_invoice_header_id, p_template_id, NOW(), v_success_count, NOW(), v_created_by, v_total_count, v_error_message, v_company_id, v_group_invoice_number
);
-- Fetch invoice status with status = 3 for a specific company
SELECT status
INTO v_invoice_status_id
FROM public.invoice_workflow iw
WHERE iw.company_id = v_company_id
AND iw.is_deleted = FALSE
AND iw.status = 3
LIMIT 1;
-- Process units associated with the template_id
FOR v_unit IN
SELECT unit_id, bhk, sqft_area, customer_id
FROM public.group_invoice_template_customers gitc
WHERE gitc.group_invoice_template_id = p_template_id
LOOP
BEGIN
v_total_count := v_total_count + 1;
v_invoice_header_id := gen_random_uuid();
v_total_amount := 0;
v_serial_number := 1;
-- Crete invoice detail
CASE v_calculation_type
WHEN 1 THEN -- Fixed method
v_total_amount := v_fixed_product_rate;
v_product_rate := v_fixed_product_rate;
v_quantity := 1; -- Fixed rate always has a quantity of 1
v_product_id := v_fixed_product_id;
-- Insert Fixed Rate detail
CALL public.create_invoice_detail(
v_invoice_header_id,
v_product_rate, -- Product rate for Fixed
v_quantity, -- Quantity (1)
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
v_product_rate, -- Taxable amount
v_total_amount, -- Total amount
v_serial_number, -- Serial number (1)
v_product_id -- Product ID for Fixed
);
WHEN 2 THEN -- BHK method
v_total_amount := v_bhk_product_rate * (v_unit.bhk)::NUMERIC;
v_product_rate := v_total_amount; -- Total amount acts as the product rate
v_quantity := 1; -- Single entry for BHK calculation
v_product_id := v_bhk_product_id;
-- Insert BHK Rate detail
CALL public.create_invoice_detail(
v_invoice_header_id,
v_product_rate, -- Product rate for BHK
v_quantity, -- Quantity (1)
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
v_product_rate, -- Taxable amount
v_total_amount, -- Total amount
v_serial_number, -- Serial number (1)
v_product_id -- Product ID for BHK
);
WHEN 3 THEN -- SFT method
v_total_amount := v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC;
v_product_rate := v_sqft_product_rate; -- the product rate
v_quantity := v_unit.sqft_area; -- Single entry for SFT calculation
v_product_id := v_sqft_product_id;
-- Insert SFT Rate detail
CALL public.create_invoice_detail(
v_invoice_header_id,
v_product_rate, -- Product rate for SFT
v_quantity, -- Quantity
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
v_product_rate * v_quantity, -- Taxable amount
v_total_amount, -- Total amount
v_serial_number, -- Serial number (1)
v_product_id -- Product ID for SFT
);
WHEN 4 THEN -- Fixed + BHK
-- Total amount is a combination of fixed rate and BHK calculation
v_total_amount := v_fixed_product_rate + (v_bhk_product_rate * (v_unit.bhk)::NUMERIC);
-- Insert Fixed rate detail
CALL public.create_invoice_detail(
v_invoice_header_id,
v_fixed_product_rate,
1, -- Quantity for fixed rate
0, 0, 0, 0, 0,
v_fixed_product_rate, -- Taxable amount
v_fixed_product_rate, -- Total amount
v_serial_number, -- Serial number
v_fixed_product_id
);
v_serial_number := v_serial_number + 1; -- Increment serial number
-- Prepare BHK rate detail for the second entry
v_product_rate := v_bhk_product_rate * (v_unit.bhk)::NUMERIC;
v_quantity := 1; -- Single entry for BHK calculation
v_product_id := v_bhk_product_id;
-- Insert BHK Rate detail
CALL public.create_invoice_detail(
v_invoice_header_id,
v_product_rate, -- Product rate for BHK
v_quantity, -- Quantity (1)
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
v_product_rate, -- Taxable amount
v_product_rate, -- Total amount
v_serial_number, -- Serial number (2)
v_product_id -- Product ID for BHK
);
WHEN 5 THEN -- Fixed + SFT
-- Total amount is a combination of fixed rate and SFT calculation
v_total_amount := v_fixed_product_rate + (v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC);
-- Insert Fixed rate detail
CALL public.create_invoice_detail(
v_invoice_header_id,
v_fixed_product_rate,
1, -- Quantity for fixed rate
0, 0, 0, 0, 0,
v_fixed_product_rate, -- Taxable amount
v_fixed_product_rate, -- Total amount
v_serial_number, -- Serial number
v_fixed_product_id
);
v_serial_number := v_serial_number + 1; -- Increment serial number
-- Prepare SFT rate detail for the second entry
v_product_rate := v_sqft_product_rate; -- Per unit rate (rate per sqft)
v_quantity := v_unit.sqft_area; -- Use sqft_area as quantity
v_product_id := v_sqft_product_id;
-- Insert SFT Rate detail
CALL public.create_invoice_detail(
v_invoice_header_id,
v_product_rate, -- Product rate per sqft
v_quantity, -- Quantity (sqft_area)
0, 0, 0, 0, 0, -- No discounts, fees, or taxes
v_product_rate * v_quantity, -- Taxable amount (rate × quantity)
v_product_rate * v_quantity, -- Total amount (rate × quantity)
v_serial_number, -- Serial number (2)
v_product_id -- Product ID for SFT
);
ELSE
RAISE NOTICE 'Invalid calculation type: %', v_calculation_type;
RETURN;
END CASE;
-- Crete invoice herader
CALL public.create_invoice_header(
v_invoice_header_id, -- p_id
v_company_id, -- p_company_id
v_unit.customer_id, -- p_customer_id
v_account_receivable_id, -- p_debit_account_id
v_sales_account_id, -- p_credit_account_id
v_invoice_date, -- p_invoice_date
v_due_date, -- p_due_date
v_payment_term, -- p_payment_term
v_total_amount, -- p_taxable_amount
0, -- p_cgst_amount (set as needed)
0, -- p_sgst_amount (set as needed)
0, -- p_igst_amount (set as needed)
v_total_amount, -- p_total_amount
0, -- p_discount
0, -- p_fees
0, -- p_round_off
v_currency_id, -- p_currency_id
v_note, -- p_note
v_invoice_status_id, -- p_invoice_status_id
v_created_by, -- p_created_by
'IVAP000', -- p_invoice_voucher
'00000000-0000-0000-0000-000000000000', -- p_source_warehouse_id
'00000000-0000-0000-0000-000000000000', -- p_destination_warehouse_id
'AP', -- p_so_no
v_invoice_date, -- p_so_date
3 -- p_type
);
v_status := 'success';
v_success_count := v_success_count + 1;
-- Insert into group_invoice_details
INSERT INTO public.group_invoice_details(
id, group_invoice_header_id, unit_id, invoice_header_id, status, posted_on, error_message, created_on_utc, created_by, company_id
)
VALUES (
gen_random_uuid(), v_group_invoice_header_id, v_unit.unit_id, v_invoice_header_id, v_status, NOW(), v_error_message, NOW(), v_created_by, v_company_id
);
EXCEPTION
WHEN OTHERS THEN
v_status := 'failed';
v_error_message := COALESCE(SQLERRM, 'Unknown error');
INSERT INTO public.group_invoice_details(
id, group_invoice_header_id, unit_id, status, invoice_header_id, error_message, posted_on, created_on_utc, created_by, company_id
)
VALUES (
gen_random_uuid(), v_group_invoice_header_id, v_unit.unit_id, v_status, v_invoice_header_id, v_error_message, NOW(), NOW(), v_created_by, v_company_id
);
END;
END LOOP;
-- Update group_invoice_headers with final status
UPDATE public.group_invoice_headers
SET success_count = v_success_count,
total_count = v_total_count,
modified_on_utc = NOW(),
modified_by = v_created_by,
error_message = COALESCE(v_error_message, '')
WHERE id = v_group_invoice_header_id;
-- Update last executed timestamp for the schedule
UPDATE batch_schedules
SET last_executed_at = NOW()
WHERE id = schedule_record.id;
END LOOP;
END;
$procedure$
-- Procedure: update_account_level_approval_configuration
-- SOURCE
CREATE OR REPLACE PROCEDURE public.update_account_level_approval_configuration(IN p_company_id uuid, IN p_account_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
approval_item JSONB;
existing_id INT;
BEGIN
-- 1️⃣ Soft delete old records not present in incoming approvals:
UPDATE invoice_approval_users_account
SET is_deleted = TRUE,
deleted_on_utc = NOW(),
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE company_id = p_company_id
AND account_id = p_account_id
AND NOT EXISTS (
SELECT 1
FROM jsonb_array_elements(p_approvals) AS elem
WHERE (elem->>'userId')::UUID = invoice_approval_users_account.user_id
AND (elem->>'approvalLevel')::INT = invoice_approval_users_account.approval_level
);
-- 2️⃣ Upsert incoming approvals:
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
LOOP
SELECT id INTO existing_id
FROM invoice_approval_users_account
WHERE company_id = p_company_id
AND account_id = p_account_id
AND user_id = (approval_item->>'userId')::UUID
AND approval_level = (approval_item->>'approvalLevel')::INT
AND is_deleted = FALSE
LIMIT 1;
IF existing_id IS NOT NULL THEN
UPDATE invoice_approval_users_account
SET status_id = (approval_item->>'statusId')::INT,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE id = existing_id;
ELSE
INSERT INTO invoice_approval_users_account (
company_id,
account_id,
status_id,
user_id,
approval_level,
is_deleted,
created_on_utc,
created_by
)
VALUES (
p_company_id,
p_account_id,
(approval_item->>'statusId')::INT,
(approval_item->>'userId')::UUID,
(approval_item->>'approvalLevel')::INT,
FALSE,
NOW(),
p_modified_by
);
END IF;
END LOOP;
-- 3️⃣ Upsert invoice_account_approval_levels:
IF EXISTS (
SELECT 1
FROM invoice_account_approval_levels
WHERE company_id = p_company_id
AND account_id = p_account_id
AND status_id = 2 -- temparary updating only for status 2
) THEN
UPDATE invoice_account_approval_levels
SET approval_level_required = p_required_approval_levels,
status_id = 2,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE company_id = p_company_id
AND account_id = p_account_id;
--AND status_id = p_status_id;
ELSE
INSERT INTO invoice_account_approval_levels (
company_id,
account_id,
status_id,
approval_level_required,
created_on_utc,
created_by
)
VALUES (
p_company_id,
p_account_id,
2,
p_required_approval_levels,
NOW(),
p_modified_by
);
END IF;
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.update_account_level_approval_configuration(IN p_company_id uuid, IN p_account_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
approval_item JSONB;
existing_id INT;
BEGIN
-- 1️⃣ Soft delete old records not present in incoming approvals:
UPDATE invoice_approval_users_account
SET is_deleted = TRUE,
deleted_on_utc = NOW(),
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE company_id = p_company_id
AND account_id = p_account_id
AND NOT EXISTS (
SELECT 1
FROM jsonb_array_elements(p_approvals) AS elem
WHERE (elem->>'userId')::UUID = invoice_approval_users_account.user_id
AND (elem->>'approvalLevel')::INT = invoice_approval_users_account.approval_level
);
-- 2️⃣ Upsert incoming approvals:
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
LOOP
SELECT id INTO existing_id
FROM invoice_approval_users_account
WHERE company_id = p_company_id
AND account_id = p_account_id
AND user_id = (approval_item->>'userId')::UUID
AND approval_level = (approval_item->>'approvalLevel')::INT
AND is_deleted = FALSE
LIMIT 1;
IF existing_id IS NOT NULL THEN
UPDATE invoice_approval_users_account
SET status_id = (approval_item->>'statusId')::INT,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE id = existing_id;
ELSE
INSERT INTO invoice_approval_users_account (
company_id,
account_id,
status_id,
user_id,
approval_level,
is_deleted,
created_on_utc,
created_by
)
VALUES (
p_company_id,
p_account_id,
(approval_item->>'statusId')::INT,
(approval_item->>'userId')::UUID,
(approval_item->>'approvalLevel')::INT,
FALSE,
NOW(),
p_modified_by
);
END IF;
END LOOP;
-- 3️⃣ Upsert invoice_account_approval_levels:
IF EXISTS (
SELECT 1
FROM invoice_account_approval_levels
WHERE company_id = p_company_id
AND account_id = p_account_id
AND status_id = p_status_id
) THEN
UPDATE invoice_account_approval_levels
SET approval_level_required = p_required_approval_levels,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE company_id = p_company_id
AND account_id = p_account_id
AND status_id = p_status_id;
ELSE
INSERT INTO invoice_account_approval_levels (
company_id,
account_id,
status_id,
approval_level_required,
created_on_utc,
created_by
)
VALUES (
p_company_id,
p_account_id,
p_status_id,
p_required_approval_levels,
NOW(),
p_modified_by
);
END IF;
END;
$procedure$
-- Procedure: update_company_level_approval_configuration
-- SOURCE
CREATE OR REPLACE PROCEDURE public.update_company_level_approval_configuration(IN p_company_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
approval_item JSONB;
existing_id INT;
BEGIN
-- 1️⃣ Soft delete old records not present in incoming approvals:
UPDATE invoice_approval_user_company
SET is_deleted = TRUE,
deleted_on_utc = NOW(),
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE company_id = p_company_id
AND NOT EXISTS (
SELECT 1
FROM jsonb_array_elements(p_approvals) AS elem
WHERE (elem->>'userId')::UUID = invoice_approval_user_company.user_id
AND (elem->>'statusId')::INT = invoice_approval_user_company.status_id
AND (elem->>'approvalLevel')::INT = invoice_approval_user_company.approval_level
);
-- 2️⃣ Upsert incoming approvals:
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
LOOP
SELECT id INTO existing_id
FROM invoice_approval_user_company
WHERE company_id = p_company_id
AND user_id = (approval_item->>'userId')::UUID
AND status_id = (approval_item->>'statusId')::INT
AND approval_level = (approval_item->>'approvalLevel')::INT
AND is_deleted = FALSE
LIMIT 1;
IF existing_id IS NOT NULL THEN
UPDATE invoice_approval_user_company
SET status_id = (approval_item->>'statusId')::INT,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE id = existing_id;
ELSE
INSERT INTO invoice_approval_user_company (
company_id,
status_id,
user_id,
approval_level,
is_deleted,
created_on_utc,
created_by
)
VALUES (
p_company_id,
(approval_item->>'statusId')::INT,
(approval_item->>'userId')::UUID,
(approval_item->>'approvalLevel')::INT,
FALSE,
NOW(),
p_modified_by
);
END IF;
END LOOP;
-- 3️⃣ Update workflow approval level:
UPDATE invoice_workflow
SET approval_level = p_required_approval_levels
WHERE company_id = p_company_id
-- AND status = p_status_id; -- 🔔 use p_status_id here unless deliberately hardcoded as 2.
AND status = 2; -- 🔔 use p_status_id here unless deliberately hardcoded as 2.
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.update_company_level_approval_configuration(IN p_company_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
approval_item JSONB;
existing_id INT;
BEGIN
-- 1️⃣ Soft delete old records not present in incoming approvals:
UPDATE invoice_approval_user_company
SET is_deleted = TRUE,
deleted_on_utc = NOW(),
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE company_id = p_company_id
AND NOT EXISTS (
SELECT 1
FROM jsonb_array_elements(p_approvals) AS elem
WHERE (elem->>'userId')::UUID = invoice_approval_user_company.user_id
AND (elem->>'approvalLevel')::INT = invoice_approval_user_company.approval_level
);
-- 2️⃣ Upsert incoming approvals:
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
LOOP
SELECT id INTO existing_id
FROM invoice_approval_user_company
WHERE company_id = p_company_id
AND user_id = (approval_item->>'userId')::UUID
AND approval_level = (approval_item->>'approvalLevel')::INT
AND is_deleted = FALSE
LIMIT 1;
IF existing_id IS NOT NULL THEN
UPDATE invoice_approval_user_company
SET status_id = (approval_item->>'statusId')::INT,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE id = existing_id;
ELSE
INSERT INTO invoice_approval_user_company (
company_id,
status_id,
user_id,
approval_level,
is_deleted,
created_on_utc,
created_by
)
VALUES (
p_company_id,
(approval_item->>'statusId')::INT,
(approval_item->>'userId')::UUID,
(approval_item->>'approvalLevel')::INT,
FALSE,
NOW(),
p_modified_by
);
END IF;
END LOOP;
-- 3️⃣ Update workflow approval level:
UPDATE invoice_workflow
SET approval_level = p_required_approval_levels
WHERE company_id = p_company_id
AND status = p_status_id; -- 🔔 use p_status_id here unless deliberately hardcoded as 2.
END;
$procedure$
-- Procedure: update_invoice_next_status
CREATE OR REPLACE PROCEDURE public.update_invoice_next_status(IN p_company_id uuid, IN p_invoice_status_id integer, IN p_invoice_ids uuid[], IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_draft_invoice RECORD;
v_next_status integer;
v_account_id uuid;
v_has_account_workflow BOOLEAN;
v_has_user_account_level_approval BOOLEAN;
v_has_user_company_level_approval BOOLEAN;
v_is_data_available_for_company BOOLEAN;
APPROVED_STATUS CONSTANT INTEGER := 3;
FIRST_LEVEL_APPROVAL CONSTANT INTEGER := 8;
SECOND_LEVEL_APPROVAL CONSTANT INTEGER := 9;
BEGIN
FOR v_draft_invoice IN
SELECT id, invoice_status_id,credit_account_id
FROM public.draft_invoice_headers
WHERE id = ANY(p_invoice_ids)
LOOP
v_account_id := v_draft_invoice.credit_account_id;
SELECT EXISTS (
SELECT 1 FROM public.invoice_account_workflows
WHERE company_id = p_company_id
AND account_id = v_account_id
AND status = v_draft_invoice.invoice_status_id
AND is_deleted = false
) INTO v_has_account_workflow;
IF v_has_account_workflow THEN
SELECT next_status INTO v_next_status
FROM public.invoice_account_workflows
WHERE company_id = p_company_id
AND account_id = v_account_id
AND status = v_draft_invoice.invoice_status_id
AND is_deleted = false
LIMIT 1;
SELECT EXISTS (
SELECT 1 FROM public.invoice_account_approval_users
WHERE company_id = p_company_id
AND account_id = v_account_id
AND status = v_next_status
AND user_id = p_modified_by
) INTO v_has_user_account_level_approval;
IF NOT v_has_user_account_level_approval THEN
RAISE EXCEPTION 'User % is not authorized to approve at this level for account % ', p_modified_by, v_account_id ;
EXIT;
END IF;
ELSE
v_next_status := p_invoice_status_id;
SELECT EXISTS (
SELECT 1 FROM public.invoice_approvals
WHERE company_id = p_company_id
) INTO v_is_data_available_for_company;
IF v_is_data_available_for_company THEN
SELECT EXISTS (
SELECT 1 FROM public.invoice_approvals
WHERE company_id = p_company_id
AND status_id = v_next_status
AND user_id = p_modified_by
) INTO v_has_user_company_level_approval;
IF NOT v_has_user_company_level_approval THEN
RAISE EXCEPTION 'User % is not authorized to approve at this level', p_modified_by;
EXIT;
END IF;
END IF;
END IF;
UPDATE public.draft_invoice_headers
SET invoice_status_id = v_next_status,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_draft_invoice.id;
IF v_next_status = APPROVED_STATUS OR v_next_status = FIRST_LEVEL_APPROVAL OR v_next_status = SECOND_LEVEL_APPROVAL THEN
INSERT INTO public.invoice_approval_logs(
invoice_id,
status_id,
approved_by,
approved_on,
"comment",
created_on_utc,
created_by
)
VALUES(
v_draft_invoice.id,
v_next_status,
p_modified_by,
now(),
CASE
WHEN v_next_status = FIRST_LEVEL_APPROVAL THEN 'Level 1 Approval'
WHEN v_next_status = SECOND_LEVEL_APPROVAL THEN 'Level 2 Approval'
WHEN v_next_status = APPROVED_STATUS THEN 'Final Approval'
ELSE 'Status updated'
END,
now(),
p_modified_by
);
END IF;
IF p_invoice_status_id = APPROVED_STATUS THEN
CALL transfer_draft_to_invoice(v_draft_invoice.id, p_modified_by);
RAISE NOTICE 'Draft Invoice Approved. Transferring to Final Invoice: %', v_draft_invoice.id;
END IF;
END LOOP;
END
$procedure$
-- Procedure: clean_up_org_sales
CREATE OR REPLACE PROCEDURE public.clean_up_org_sales(IN p_organization_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_company_id uuid;
v_customer_id uuid;
v_customer_note_header_id uuid;
v_invoice_header_id uuid;
v_invoice_payment_header_id uuid;
v_draft_invoice_header_id uuid;
v_group_invoice_header_id uuid;
v_gate_pass_header_id uuid;
v_user_id uuid;
BEGIN
FOR v_company_id IN
SELECT id FROM companies WHERE organization_id = p_organization_id
LOOP
-- Customers and all their detail tables
FOR v_customer_id IN SELECT id FROM customers WHERE company_id = v_company_id LOOP
DELETE FROM customer_contacts WHERE customer_id = v_customer_id;
DELETE FROM customer_bank_accounts WHERE customer_id = v_customer_id;
DELETE FROM customer_upis WHERE customer_id = v_customer_id;
DELETE FROM customer_default_accounts WHERE customer_id = v_customer_id;
FOR v_customer_note_header_id IN SELECT id FROM customer_note_headers WHERE customer_id = v_customer_id LOOP
DELETE FROM customer_note_details WHERE customer_note_header_id = v_customer_note_header_id;
END LOOP;
DELETE FROM customer_note_headers WHERE customer_id = v_customer_id;
END LOOP;
DELETE FROM customers WHERE company_id = v_company_id;
DELETE FROM customer_note_statuses WHERE created_by = v_company_id OR modified_by = v_company_id;
DELETE FROM customer_note_work_flows WHERE company_id = v_company_id;
-- Draft Invoices and their details
FOR v_draft_invoice_header_id IN SELECT id FROM draft_invoice_headers WHERE company_id = v_company_id LOOP
DELETE FROM draft_invoice_details WHERE invoice_header_id = v_draft_invoice_header_id;
END LOOP;
DELETE FROM draft_invoice_headers WHERE company_id = v_company_id;
DELETE FROM draft_invoice_header_ids WHERE company_id = v_company_id;
-- Group Invoices and their details/templates
FOR v_group_invoice_header_id IN SELECT id FROM group_invoice_headers WHERE company_id = v_company_id LOOP
DELETE FROM group_invoice_details WHERE group_invoice_header_id = v_group_invoice_header_id;
END LOOP;
DELETE FROM group_invoice_headers WHERE company_id = v_company_id;
DELETE FROM group_invoice_header_ids WHERE company_id = v_company_id;
DELETE FROM group_invoice_templates WHERE company_id = v_company_id;
DELETE FROM group_invoice_template_customers WHERE customer_id IN (
SELECT id FROM customers WHERE company_id = v_company_id
);
-- Invoice Headers and all their details
FOR v_invoice_header_id IN SELECT id FROM invoice_headers WHERE company_id = v_company_id LOOP
DELETE FROM invoice_details WHERE invoice_header_id = v_invoice_header_id;
DELETE FROM invoice_approval_logs WHERE invoice_id = v_invoice_header_id;
DELETE FROM invoice_approval_issue_log WHERE invoice_id = v_invoice_header_id;
DELETE FROM invoice_penalties WHERE invoice_header_id = v_invoice_header_id;
DELETE FROM penalty_processing_logs WHERE invoice_id = v_invoice_header_id;
DELETE FROM recurring_sales_schedules WHERE invoice_header_id = v_invoice_header_id;
DELETE FROM group_invoice_details WHERE invoice_header_id = v_invoice_header_id;
END LOOP;
DELETE FROM invoice_headers WHERE company_id = v_company_id;
DELETE FROM invoice_header_ids WHERE company_id = v_company_id;
-- Invoice payments and their details
FOR v_invoice_payment_header_id IN SELECT id FROM invoice_payment_headers WHERE company_id = v_company_id LOOP
DELETE FROM invoice_payment_details WHERE invoice_payment_header_id = v_invoice_payment_header_id;
END LOOP;
DELETE FROM invoice_payment_headers WHERE company_id = v_company_id;
DELETE FROM invoice_payment_header_ids WHERE company_id = v_company_id;
-- Invoice workflow/configs
DELETE FROM invoice_workflow WHERE company_id = v_company_id;
DELETE FROM invoice_status_company_configs WHERE company_id = v_company_id;
DELETE FROM invoice_account_approval_levels WHERE company_id = v_company_id;
DELETE FROM invoice_approval_users_account WHERE company_id = v_company_id;
DELETE FROM invoice_approval_user_company WHERE company_id = v_company_id;
-- Invoice voucher ids
DELETE FROM invoice_voucher_ids WHERE company_id = v_company_id;
-- Penalties and configs
DELETE FROM penalty_configs WHERE company_id = v_company_id;
DELETE FROM penalty_frequencies WHERE created_by = v_company_id OR modified_by = v_company_id;
-- Warehouses (corrected)
DELETE FROM warehouses WHERE customer_id IN (
SELECT id FROM customers WHERE company_id = v_company_id
);
-- Gate pass and their details
FOR v_gate_pass_header_id IN SELECT id FROM gate_pass_headers WHERE company_id = v_company_id LOOP
DELETE FROM gate_pass_details WHERE gate_pass_header_id = v_gate_pass_header_id;
END LOOP;
DELETE FROM gate_pass_headers WHERE company_id = v_company_id;
-- Users and user roles
FOR v_user_id IN SELECT id FROM users WHERE company_id = v_company_id LOOP
DELETE FROM user_roles WHERE user_id = v_user_id;
END LOOP;
DELETE FROM users WHERE company_id = v_company_id;
-- Finally, company itself
DELETE FROM companies WHERE id = v_company_id;
END LOOP;
-- Organization record itself
DELETE FROM organizations WHERE id = p_organization_id;
RAISE NOTICE 'Organization % and all related sales data deleted.', p_organization_id;
END;
$procedure$
-- Procedure: close_resolved_delinquency_cycles
CREATE OR REPLACE PROCEDURE public.close_resolved_delinquency_cycles(IN p_organization_id uuid, IN p_company_ids uuid[], IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
CYCLE_STATUS_CLOSE CONSTANT int := 2;
BEGIN
UPDATE delinquency_cycles dc
SET
ended_on_utc = CURRENT_TIMESTAMP,
status_id = CYCLE_STATUS_CLOSE, -- CLOSED
modified_on_utc = CURRENT_TIMESTAMP,
modified_by = p_modified_by
WHERE dc.organization_id = p_organization_id
AND dc.ended_on_utc IS NULL
AND dc.is_deleted = false
AND NOT EXISTS (
SELECT 1
FROM invoice_headers ih
WHERE ih.company_id = ANY (p_company_ids)
AND ih.customer_id = dc.customer_id
AND ih.is_deleted = false
AND (ih.total_amount - ih.settled_amount) > 0
);
END;
$procedure$
-- Procedure: schedule_invoice
CREATE OR REPLACE PROCEDURE public.schedule_invoice()
LANGUAGE plpgsql
AS $procedure$
DECLARE
templateRecord RECORD;
executionDate DATE;
nextExecutionDate DATE;
newDraftInvoiceId UUID;
invoiceHeaderRecord RECORD;
invoiceDetailRecord RECORD;
BEGIN
-- Loop through all active templates in the invoice_template table
FOR templateRecord IN
SELECT *
FROM public.invoice_template
WHERE is_deleted = false
AND starts_from <= CURRENT_DATE
AND end_date >= CURRENT_DATE
LOOP
-- Initialize executionDate to the start date of the template
executionDate := templateRecord.starts_from;
-- Fetch data from invoice_headers or draft_invoice_headers based on invoiceHeaderId
SELECT * INTO invoiceHeaderRecord
FROM public.invoice_headers
WHERE id = templateRecord.invoice_header_id AND is_deleted = false;
IF NOT FOUND THEN
-- If not found in invoice_headers, check in draft_invoice_headers
SELECT * INTO invoiceHeaderRecord
FROM public.draft_invoice_headers
WHERE id = templateRecord.invoice_header_id AND is_deleted = false;
END IF;
-- Loop through the date range to create execution dates
WHILE executionDate <= templateRecord.end_date LOOP
-- Calculate the next execution date based on frequency
CASE templateRecord.frequency_cycle
WHEN 'daily' THEN
nextExecutionDate := executionDate + INTERVAL '1 day';
WHEN 'weekly' THEN
nextExecutionDate := executionDate + INTERVAL '1 week';
WHEN 'monthly' THEN
nextExecutionDate := executionDate + INTERVAL '1 month';
WHEN 'yearly' THEN
nextExecutionDate := executionDate + INTERVAL '1 year';
ELSE
RAISE EXCEPTION 'Invalid frequency cycle: %', templateRecord.frequency_cycle;
END CASE;
-- Create a new draft invoice
newDraftInvoiceId := gen_random_uuid();
CALL public.create_draft_invoice_header(
newDraftInvoiceId,
invoiceHeaderRecord.company_id,
invoiceHeaderRecord.customer_id,
invoiceHeaderRecord.debit_account_id,
invoiceHeaderRecord.credit_account_id,
executionDate,
executionDate + INTERVAL '30 days', -- Set due date
invoiceHeaderRecord.payment_term,
invoiceHeaderRecord.taxable_amount,
invoiceHeaderRecord.cgst_amount,
invoiceHeaderRecord.sgst_amount,
invoiceHeaderRecord.igst_amount,
invoiceHeaderRecord.total_amount,
invoiceHeaderRecord.discount,
invoiceHeaderRecord.fees,
invoiceHeaderRecord.round_off,
invoiceHeaderRecord.currency_id,
invoiceHeaderRecord.note,
1, -- Set initial status to 'draft'
invoiceHeaderRecord.created_by,
invoiceHeaderRecord.invoice_voucher,
invoiceHeaderRecord.source_warehouse_id,
invoiceHeaderRecord.destination_warehouse_id,
invoiceHeaderRecord.so_no,
invoiceHeaderRecord.so_date,
invoiceHeaderRecord.type
);
-- Insert related draft invoice details
FOR invoiceDetailRecord IN
SELECT * FROM public.invoice_details
WHERE invoice_header_id = templateRecord.invoice_header_id AND is_deleted = false
LOOP
CALL public.create_draft_invoice_detail(
newDraftInvoiceId,
invoiceDetailRecord.price,
invoiceDetailRecord.quantity,
invoiceDetailRecord.discount,
invoiceDetailRecord.fees,
invoiceDetailRecord.cgst_amount,
invoiceDetailRecord.igst_amount,
invoiceDetailRecord.sgst_amount,
invoiceDetailRecord.taxable_amount,
invoiceDetailRecord.total_amount,
invoiceDetailRecord.serial_number,
invoiceDetailRecord.product_id
);
END LOOP;
-- Insert into apartment_invoice_template_executions
INSERT INTO public.apartment_invoice_template_executions (
id, template_id, execution_date, error_message, success_count,
total_count, created_on_utc, created_by, is_deleted
)
VALUES (
gen_random_uuid(), templateRecord.id, executionDate, '', 1, 1,
NOW(), invoiceHeaderRecord.created_by, false
);
-- Update executionDate for the next iteration
executionDate := nextExecutionDate;
END LOOP;
END LOOP;
RAISE NOTICE 'Invoice scheduling completed successfully.';
END;
$procedure$
-- Procedure: test_run_batches
CREATE OR REPLACE PROCEDURE public.test_run_batches()
LANGUAGE plpgsql
AS $procedure$
DECLARE
schedule_record RECORD;
v_unit RECORD;
v_execution_id UUID;
v_invoice_header_id UUID;
v_total_amount NUMERIC;
v_status TEXT;
v_error_message TEXT;
v_total_count INTEGER;
v_success_count INTEGER;
v_calculation_type INTEGER;
v_company_id UUID;
v_sales_account_id UUID;
v_account_receivable_id UUID;
v_invoice_date DATE;
v_starts_from DATE;
v_end_date DATE;
v_due_date DATE;
v_payment_term INTEGER;
v_currency_id INTEGER;
v_fixed_product_id UUID;
v_bhk_product_id UUID;
v_sqft_product_id UUID;
v_fixed_product_rate NUMERIC;
v_bhk_product_rate NUMERIC;
v_sqft_product_rate NUMERIC;
v_note TEXT;
v_invoice_status_id INTEGER;
v_due_days INTEGER;
v_billing_cycle TEXT;
v_half_year INTEGER;
v_quarters_of_month INTEGER;
v_bi_month INTEGER;
v_day_of_week INTEGER;
v_day_of_month INTEGER;
v_month_of_year INTEGER;
v_time_of_day INTERVAL;
v_created_by UUID;
v_time_as_time TIME;
BEGIN
-- Loop through all non-deleted schedules that should run today
FOR schedule_record IN
SELECT *
FROM batch_schedules
WHERE is_deleted = FALSE
AND template_id IN (
SELECT id
FROM public.apartment_invoice_templates
WHERE is_deleted = FALSE
AND (starts_from <= CURRENT_DATE
AND (end_date IS NULL OR end_date >= CURRENT_DATE))
)
AND last_executed_at IS DISTINCT FROM CURRENT_DATE::timestamp
LOOP
-- Assign values from schedule_record
v_half_year := schedule_record.half_year;
v_quarters_of_month := schedule_record.quarters_of_month;
v_bi_month := schedule_record.bi_month;
v_day_of_week := schedule_record.day_of_week;
v_day_of_month := schedule_record.day_of_month;
v_month_of_year := schedule_record.month_of_year;
v_time_of_day := schedule_record.time_of_day;
-- Convert v_time_of_day interval to time by casting extracted values to integer
v_time_as_time := make_time(
EXTRACT(HOUR FROM v_time_of_day)::INTEGER,
EXTRACT(MINUTE FROM v_time_of_day)::INTEGER,
EXTRACT(SECOND FROM v_time_of_day)::INTEGER
);
-- Log important information before entering the IF block
RAISE NOTICE 'Checking if batch should run for template_id: %', schedule_record.template_id;
-- Process based on billing cycle and check if current time matches the set time
IF (
-- Daily schedules
schedule_record.billing_cycle = 'Daily'
OR (schedule_record.billing_cycle = 'Weekly' AND v_day_of_week = EXTRACT(DOW FROM CURRENT_DATE))
OR (schedule_record.billing_cycle = 'Monthly' AND v_day_of_month = EXTRACT(DAY FROM CURRENT_DATE) AND CURRENT_DATE <= v_end_date)
OR (schedule_record.billing_cycle = 'Bi-Monthly' AND v_bi_month = CEIL(EXTRACT(MONTH FROM CURRENT_DATE) / 2) AND CURRENT_DATE <= v_end_date)
OR (schedule_record.billing_cycle = 'Quarterly' AND v_quarters_of_month = CEIL(EXTRACT(MONTH FROM CURRENT_DATE) / 3) AND CURRENT_DATE <= v_end_date)
OR (schedule_record.billing_cycle = 'Half-yearly' AND v_half_year = CASE WHEN EXTRACT(MONTH FROM CURRENT_DATE) <= 6 THEN 1 ELSE 2 END AND CURRENT_DATE <= v_end_date)
OR (schedule_record.billing_cycle = 'Yearly' AND v_month_of_year = EXTRACT(MONTH FROM CURRENT_DATE) AND CURRENT_DATE <= v_end_date)
)
AND CURRENT_TIME >= v_time_as_time THEN
-- Log the execution details
RAISE NOTICE 'Batch should run for template_id: % at time: %', schedule_record.template_id, v_time_as_time;
-- Begin processing the batch for this template
v_execution_id := gen_random_uuid();
v_total_count := 0;
v_success_count := 0;
v_status := 'pending';
v_error_message := '';
-- Insert into apartment_invoice_template_execution
BEGIN
INSERT INTO public.apartment_invoice_template_executions (
id, template_id, execution_date, success_count, created_on_utc, created_by, total_count, error_message
)
VALUES (
v_execution_id, schedule_record.template_id, NOW(), v_success_count, NOW(), v_created_by, v_total_count, v_error_message
);
RAISE NOTICE 'Inserted into apartment_invoice_template_executions for template_id: % with execution_id: %', schedule_record.template_id, v_execution_id;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Error inserting into apartment_invoice_template_executions for template_id: % - Error: %', schedule_record.template_id, SQLERRM;
END;
-- Select Status from the invoice workflow
SELECT MIN(status)
INTO v_invoice_status_id
FROM public.invoice_workflow
WHERE company_id = v_company_id
AND is_deleted = FALSE;
-- Loop through units associated with the template
FOR v_unit IN
SELECT unit_id, bhk, sqft_area, customer_id
FROM public.apartment_invoice_template_units
WHERE template_id = schedule_record.template_id
LOOP
BEGIN
v_total_count := v_total_count + 1;
v_invoice_header_id := gen_random_uuid();
v_total_amount := 0;
-- Calculate the total amount based on the calculation type
CASE v_calculation_type
WHEN 1 THEN -- Fixed method
v_total_amount := v_fixed_product_rate;
WHEN 2 THEN -- BHK
v_total_amount := v_bhk_product_rate * (v_unit.bhk)::NUMERIC;
WHEN 3 THEN -- SFT
v_total_amount := v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC;
WHEN 4 THEN -- Fixed + BHK
v_total_amount := v_fixed_product_rate + (v_bhk_product_rate * (v_unit.bhk)::NUMERIC);
WHEN 5 THEN -- Fixed + SFT
v_total_amount := v_fixed_product_rate + (v_sqft_product_rate * (v_unit.sqft_area)::NUMERIC);
END CASE;
-- Call to create invoice header
CALL public.create_invoice_header(
v_invoice_header_id,
v_company_id,
v_unit.customer_id,
v_account_receivable_id,
v_sales_account_id,
v_invoice_date,
v_due_date, -- Now passing the calculated due_date
v_payment_term,
v_total_amount,
0.0,
0.0,
0.0,
v_total_amount,
0.0,
0.0,
0.0,
v_currency_id,
v_note,
v_invoice_status_id,
v_created_by,
'APINV',
'00000000-0000-0000-0000-000000000000',
'00000000-0000-0000-0000-000000000000',
(CURRENT_TIMESTAMP AT TIME ZONE 'UTC'), -- created_on_utc set to current UTC time
COALESCE(NULLIF('', ''), ''), -- so_no as empty string
v_invoice_date,
3, -- Apartment type
v_created_by
);
-- Log success for invoice header creation
RAISE NOTICE 'Created invoice header for unit_id: %', v_unit.unit_id;
EXCEPTION
WHEN OTHERS THEN
-- Handle any errors during invoice posting
v_status := 'failed';
v_error_message := COALESCE(SQLERRM, 'Unknown error');
RAISE NOTICE 'Error creating invoice header for unit_id: % - Error: %', v_unit.unit_id, v_error_message;
END;
END LOOP;
-- Final logging after processing template
RAISE NOTICE 'Processing complete for template_id: %', schedule_record.template_id;
ELSE
RAISE NOTICE 'Batch skipped for template_id: %', schedule_record.template_id;
END IF;
END LOOP;
END;
$procedure$
-- Procedure: create_draft_invoice_header
-- SOURCE
CREATE OR REPLACE PROCEDURE public.create_draft_invoice_header(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_invoice_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_currency_id integer, IN p_note character varying, IN p_invoice_status_id integer, IN p_created_by uuid, IN p_invoice_voucher character varying, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_so_no character varying, IN p_so_date date, IN p_type integer)
LANGUAGE plpgsql
AS $procedure$
DECLARE
invoice_number character varying;
BEGIN
invoice_number := get_new_draft_invoice_number(p_company_id,p_invoice_date);
RAISE NOTICE 'Value: %', invoice_number;
INSERT INTO
public.draft_invoice_headers(
id,
company_id,
customer_id,
credit_account_id,
debit_account_id,
invoice_number,
invoice_date,
due_date,
payment_term,
taxable_amount,
cgst_amount,
sgst_amount,
igst_amount,
total_amount,
discount,
fees,
round_off,
currency_id,
note,
invoice_status_id,
created_by,
invoice_voucher,
source_warehouse_id,
destination_warehouse_id,
created_on_utc,
so_no,
so_date,
type,
current_approval_level,
payment_status_id)
VALUES (
p_id,
p_company_id,
p_customer_id,
p_debit_account_id,
p_credit_account_id,
invoice_number,
p_invoice_date,
p_due_date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_currency_id,
p_note,
p_invoice_status_id,
p_created_by,
p_invoice_voucher,
p_source_warehouse_id,
p_destination_warehouse_id,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
p_so_no,
p_so_date,
p_type,
0,
0
);
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.create_draft_invoice_header(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_invoice_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_currency_id integer, IN p_note character varying, IN p_invoice_status_id integer, IN p_created_by uuid, IN p_invoice_voucher character varying, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_so_no character varying, IN p_so_date date, IN p_type integer)
LANGUAGE plpgsql
AS $procedure$
DECLARE
invoice_number character varying;
BEGIN
invoice_number := get_new_draft_invoice_number(p_company_id,p_invoice_date);
RAISE NOTICE 'Value: %', invoice_number;
INSERT INTO
public.draft_invoice_headers(
id,
company_id,
customer_id,
credit_account_id,
debit_account_id,
invoice_number,
invoice_date,
due_date,
payment_term,
taxable_amount,
cgst_amount,
sgst_amount,
igst_amount,
total_amount,
discount,
fees,
round_off,
currency_id,
note,
invoice_status_id,
created_by,
invoice_voucher,
source_warehouse_id,
destination_warehouse_id,
created_on_utc,
so_no,
so_date,
type,
current_approval_level)
VALUES (
p_id,
p_company_id,
p_customer_id,
p_debit_account_id,
p_credit_account_id,
invoice_number,
p_invoice_date,
p_due_date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_currency_id,
p_note,
p_invoice_status_id,
p_created_by,
p_invoice_voucher,
p_source_warehouse_id,
p_destination_warehouse_id,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
p_so_no,
p_so_date,
p_type,
0
);
END;
$procedure$
-- Procedure: run_scheduled_invoice
CREATE OR REPLACE PROCEDURE public.run_scheduled_invoice(IN p_invoice_header_id uuid, IN p_draft_invoice_header_id uuid, IN p_schedule_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_system_user_id uuid;
v_execution_log_id uuid := gen_random_uuid();
v_related_schedule_id uuid;
v_new_draft_invoice_id uuid;
v_source_is_live boolean := false;
v_source_invoice_id uuid;
src_header RECORD;
src_detail RECORD;
BEGIN
-- ✅ 1. Find "System" user
SELECT u.id
INTO v_system_user_id
FROM public.users AS u
WHERE u.is_deleted = false
AND lower(u.first_name) = 'system'
ORDER BY u.created_on_utc NULLS LAST, u.id
LIMIT 1;
IF v_system_user_id IS NULL THEN
RAISE NOTICE 'No user with first_name="System" found; proceeding with NULL triggered_by.';
END IF;
-- ✅ 2. Determine which ID to use as source
IF p_invoice_header_id IS NOT NULL THEN
v_source_invoice_id := p_invoice_header_id;
v_source_is_live := true;
ELSIF p_draft_invoice_header_id IS NOT NULL THEN
v_source_invoice_id := p_draft_invoice_header_id;
v_source_is_live := false;
ELSE
RAISE NOTICE 'Both invoice_header_id and draft_invoice_header_id are NULL; aborting.';
RETURN;
END IF;
-- ✅ 3. Get related schedule_id
SELECT s.id
INTO v_related_schedule_id
FROM public.recurring_sales_schedules AS s
WHERE s.is_deleted = false
AND (
(v_source_is_live AND s.invoice_header_id = v_source_invoice_id)
OR (NOT v_source_is_live AND s.draft_invoice_header_id = v_source_invoice_id)
)
ORDER BY s.starts_from DESC
LIMIT 1;
-- ✅ 4. Fetch source header from appropriate table
IF v_source_is_live THEN
SELECT * INTO src_header
FROM public.invoice_headers
WHERE id = v_source_invoice_id
AND is_deleted = false;
ELSE
SELECT * INTO src_header
FROM public.draft_invoice_headers
WHERE id = v_source_invoice_id
AND is_deleted = false;
END IF;
IF NOT FOUND THEN
INSERT INTO public.schedule_execution_logs (
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
) VALUES (
v_execution_log_id, v_related_schedule_id, (p_schedule_date::date), NOW(),
'Failure', 'Source invoice header not found in live or draft tables.', v_system_user_id, false
);
RAISE NOTICE 'Source invoice header % not found; aborting for date %.', v_source_invoice_id, (p_schedule_date::date);
RETURN;
END IF;
-- ✅ 5. Generate new draft invoice header
v_new_draft_invoice_id := gen_random_uuid();
CALL public.create_draft_invoice_header(
v_new_draft_invoice_id,
src_header.company_id,
src_header.customer_id,
src_header.debit_account_id,
src_header.credit_account_id,
(p_schedule_date::date),
((p_schedule_date::date) + COALESCE(src_header.payment_term, 10)),
src_header.payment_term,
src_header.taxable_amount,
src_header.cgst_amount,
src_header.sgst_amount,
src_header.igst_amount,
src_header.total_amount,
src_header.discount,
src_header.fees,
src_header.round_off,
src_header.currency_id,
src_header.note,
1,
v_system_user_id,
src_header.invoice_voucher,
src_header.source_warehouse_id,
src_header.destination_warehouse_id,
src_header.so_no,
(p_schedule_date::date),
src_header.type
);
-- ✅ 6. Clone all details
IF v_source_is_live THEN
FOR src_detail IN
SELECT * FROM public.invoice_details
WHERE invoice_header_id = v_source_invoice_id
LOOP
CALL public.create_draft_invoice_detail(
v_new_draft_invoice_id,
src_detail.price, src_detail.quantity, src_detail.discount, src_detail.fees,
src_detail.cgst_amount, src_detail.igst_amount, src_detail.sgst_amount,
src_detail.taxable_amount, src_detail.total_amount,
src_detail.serial_number, src_detail.product_id
);
END LOOP;
ELSE
FOR src_detail IN
SELECT * FROM public.draft_invoice_details
WHERE invoice_header_id = v_source_invoice_id
LOOP
CALL public.create_draft_invoice_detail(
v_new_draft_invoice_id,
src_detail.price, src_detail.quantity, src_detail.discount, src_detail.fees,
src_detail.cgst_amount, src_detail.igst_amount, src_detail.sgst_amount,
src_detail.taxable_amount, src_detail.total_amount,
src_detail.serial_number, src_detail.product_id
);
END LOOP;
END IF;
-- ✅ 7. Log success
INSERT INTO public.schedule_execution_logs (
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
) VALUES (
v_execution_log_id, v_related_schedule_id, (p_schedule_date::date), NOW(),
'Success', '', v_system_user_id, false
);
RAISE NOTICE 'Draft invoice % created from % (live=%), schedule_id %, on %.',
v_new_draft_invoice_id, v_source_invoice_id, v_source_is_live, v_related_schedule_id, (p_schedule_date::date);
EXCEPTION WHEN OTHERS THEN
INSERT INTO public.schedule_execution_logs (
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
) VALUES (
COALESCE(v_execution_log_id, gen_random_uuid()), v_related_schedule_id, (p_schedule_date::date), NOW(),
'Failure', SQLERRM, v_system_user_id, false
);
RAISE NOTICE 'Error while cloning invoice % on %: %',
v_source_invoice_id, (p_schedule_date::date), SQLERRM;
END;
$procedure$
-- Procedure: insert_draft_invoice
CREATE OR REPLACE PROCEDURE public.insert_draft_invoice(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_invoice_date timestamp without time zone, IN p_payment_term integer, IN p_invoice_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_so_no text, IN p_so_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_invoice_voucher text, IN p_created_by uuid, IN p_invoice_details jsonb, IN p_type integer, IN p_settled_amount numeric DEFAULT 0)
LANGUAGE plpgsql
AS $procedure$
DECLARE
detail RECORD;
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
BEGIN
-- Set default values for warehouses
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
-- Insert into the invoice header
CALL public.create_draft_invoice_header (
p_id,
p_company_id,
p_customer_id,
p_credit_account_id,
p_debit_account_id,
p_invoice_date::date,
p_due_date::date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_currency_id,
p_note,
p_invoice_status_id,
p_created_by,
p_invoice_voucher,
v_source_warehouse_id,
v_destination_warehouse_id,
p_so_no,
p_so_date::date,
p_type
);
RAISE NOTICE 'Invoice header inserted with ID: %', p_id;
-- Loop through the JSONB array of invoice details
FOR detail IN
SELECT * FROM jsonb_to_recordset(p_invoice_details) AS (
id uuid, product_id uuid, price numeric, quantity numeric,
fees numeric, discount numeric, taxable_amount numeric,
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
total_amount numeric, serial_number integer
)
LOOP
-- Insert into invoice details
INSERT INTO public.draft_invoice_details (
id, invoice_header_id, product_id, price, quantity, fees, discount,
taxable_amount, sgst_amount, cgst_amount, igst_amount, total_amount,
serial_number, is_deleted
) VALUES (
detail.id, p_id, detail.product_id, detail.price, detail.quantity, detail.fees,
detail.discount, detail.taxable_amount, detail.sgst_amount, detail.cgst_amount,
detail.igst_amount, detail.total_amount, detail.serial_number, false
);
RAISE NOTICE 'Invoice detail inserted with ID: %', detail.id;
END LOOP;
-- No explicit COMMIT here
RAISE NOTICE 'Transaction completed successfully';
EXCEPTION
-- Handle unique violation (duplicate key)
WHEN unique_violation THEN
RAISE NOTICE 'Duplicate entry: %, rolling back', SQLERRM;
ROLLBACK;
-- Handle foreign key violation
WHEN foreign_key_violation THEN
RAISE NOTICE 'Foreign key violation: %, rolling back', SQLERRM;
ROLLBACK;
-- Catch all other exceptions
WHEN OTHERS THEN
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
ROLLBACK;
END;
$procedure$
-- Procedure: create_schedule_invoices
CREATE OR REPLACE PROCEDURE public.create_schedule_invoices(IN p_company_id uuid, IN p_frequency_cycle text, IN p_starts_from date, IN p_end_date date, IN p_created_by uuid, IN p_invoice_header_id uuid DEFAULT NULL::uuid, IN p_draft_invoice_header_id uuid DEFAULT NULL::uuid, IN p_month_of_year integer DEFAULT NULL::integer, IN p_day_of_month integer DEFAULT NULL::integer, IN p_day_of_week integer DEFAULT NULL::integer, IN p_time_of_day interval DEFAULT '00:00:00'::interval)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_recurring_schedule_id uuid := gen_random_uuid(); -- New ID for recurring_sales_schedules
v_schedule_status text;
-- normalized copies (treat all-zero GUID as NULL)
v_invoice_header_id uuid := NULLIF(p_invoice_header_id, '00000000-0000-0000-0000-000000000000'::uuid);
v_draft_invoice_header_id uuid := NULLIF(p_draft_invoice_header_id, '00000000-0000-0000-0000-000000000000'::uuid);
v_day_of_week integer := COALESCE(p_day_of_week, 0);
v_day_of_month integer := COALESCE(p_day_of_month, 0);
v_month_of_year integer := COALESCE(p_month_of_year, 0);
v_time_of_day interval := COALESCE(p_time_of_day, '00:00:00'::interval);
BEGIN
----------------------------------------------------------------
-- Validate: exactly ONE of invoice_header_id / draft_invoice_header_id
----------------------------------------------------------------
IF (v_invoice_header_id IS NULL AND v_draft_invoice_header_id IS NULL) THEN
RAISE EXCEPTION 'Either invoice_header_id or draft_invoice_header_id must be provided.';
ELSIF (v_invoice_header_id IS NOT NULL AND v_draft_invoice_header_id IS NOT NULL) THEN
RAISE EXCEPTION 'Provide only one of invoice_header_id or draft_invoice_header_id, not both.';
END IF;
-- Determine the initial schedule status
IF p_starts_from > CURRENT_DATE THEN
v_schedule_status := 'inactive';
ELSE
v_schedule_status := 'active';
END IF;
-- Insert into the recurring_sales_schedules table
INSERT INTO public.recurring_sales_schedules (
id, invoice_header_id, draft_invoice_header_id, company_id, frequency_cycle, schedule_status, starts_from, end_date,
created_on_utc, created_by, is_deleted
)
VALUES (
v_recurring_schedule_id, v_invoice_header_id, v_draft_invoice_header_id, p_company_id, p_frequency_cycle, v_schedule_status,
p_starts_from, p_end_date, NOW(), p_created_by, false
);
-- Insert into the public.recurrence_schedule_details table based on p_frequency_cycle
IF p_frequency_cycle = 'Daily' THEN
INSERT INTO public.recurrence_schedule_details (
schedule_id , frequency_cycle, time_of_day, is_deleted
)
VALUES (
v_recurring_schedule_id, p_frequency_cycle, v_time_of_day, false
);
ELSIF p_frequency_cycle = 'Weekly' THEN
INSERT INTO public.recurrence_schedule_details (
schedule_id, frequency_cycle, day_of_week, time_of_day, is_deleted
)
VALUES (
v_recurring_schedule_id, p_frequency_cycle, v_day_of_week, v_time_of_day, false
);
ELSIF p_frequency_cycle = 'Monthly' THEN
INSERT INTO public.recurrence_schedule_details (
schedule_id, frequency_cycle, day_of_month, time_of_day, is_deleted
)
VALUES (
v_recurring_schedule_id, p_frequency_cycle, v_day_of_month, v_time_of_day, false
);
ELSIF p_frequency_cycle = 'Yearly' THEN
INSERT INTO public.recurrence_schedule_details (
schedule_id, frequency_cycle, month_of_year, day_of_month, time_of_day, is_deleted
)
VALUES (
v_recurring_schedule_id, p_frequency_cycle, v_month_of_year, v_day_of_month, v_time_of_day, false
);
ELSE
RAISE NOTICE 'Invalid frequency cycle: %', p_frequency_cycle;
END IF;
RAISE NOTICE 'Recurring schedule created successfully with ID: %, Status: %', v_recurring_schedule_id, v_schedule_status;
END;
$procedure$
-- Procedure: insert_invoice
CREATE OR REPLACE PROCEDURE public.insert_invoice(IN p_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_invoice_date timestamp without time zone, IN p_payment_term integer, IN p_invoice_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_so_no text, IN p_so_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_invoice_voucher text, IN p_created_by uuid, IN p_invoice_details jsonb, IN p_type integer, IN p_settled_amount numeric DEFAULT 0)
LANGUAGE plpgsql
AS $procedure$
DECLARE
detail RECORD;
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
BEGIN
-- Set default values for warehouses
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
-- Insert into the invoice header
CALL public.create_invoice_header (
p_id,
p_company_id,
p_customer_id,
p_credit_account_id,
p_debit_account_id,
p_invoice_date::date,
p_due_date::date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_currency_id,
p_note,
p_invoice_status_id,
p_created_by,
p_invoice_voucher,
v_source_warehouse_id,
v_destination_warehouse_id,
p_so_no,
p_so_date::date,
p_type
);
RAISE NOTICE 'Invoice header inserted with ID: %', p_id;
-- Loop through the JSONB array of invoice details
FOR detail IN
SELECT * FROM jsonb_to_recordset(p_invoice_details) AS (
id uuid, product_id uuid, price numeric, quantity numeric,
fees numeric, discount numeric, taxable_amount numeric,
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
total_amount numeric, serial_number integer
)
LOOP
-- Insert into invoice details
INSERT INTO public.invoice_details (
id, invoice_header_id, product_id, price, quantity, fees, discount,
taxable_amount, sgst_amount, cgst_amount, igst_amount, total_amount,
serial_number, is_deleted
) VALUES (
detail.id, p_id, detail.product_id, detail.price, detail.quantity, detail.fees,
detail.discount, detail.taxable_amount, detail.sgst_amount, detail.cgst_amount,
detail.igst_amount, detail.total_amount, detail.serial_number, false
);
RAISE NOTICE 'Invoice detail inserted with ID: %', detail.id;
END LOOP;
-- No explicit COMMIT here
RAISE NOTICE 'Transaction completed successfully';
EXCEPTION
-- Handle unique violation (duplicate key)
WHEN unique_violation THEN
RAISE NOTICE 'Duplicate entry: %, rolling back', SQLERRM;
ROLLBACK;
-- Handle foreign key violation
WHEN foreign_key_violation THEN
RAISE NOTICE 'Foreign key violation: %, rolling back', SQLERRM;
ROLLBACK;
-- Catch all other exceptions
WHEN OTHERS THEN
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
ROLLBACK;
END;
$procedure$
-- Procedure: insert_multiple_invoices_by_excel
CREATE OR REPLACE PROCEDURE public.insert_multiple_invoices_by_excel(IN p_invoices jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
invoice RECORD;
v_created_by uuid;
results TEXT[] := ARRAY[]::TEXT[];
record_status TEXT;
BEGIN
-- Optional: default created_by fallback
SELECT id INTO v_created_by
FROM users
WHERE first_name = 'System'
LIMIT 1;
FOR invoice IN
SELECT * FROM jsonb_to_recordset(p_invoices) AS (
invoice_id uuid,
company_id uuid,
customer_id uuid,
discount numeric,
currency_id integer,
invoice_date timestamp without time zone,
invoice_number text,
payment_term integer,
invoice_status_id integer,
due_date timestamp without time zone,
total_amount numeric,
taxable_amount numeric,
fees numeric,
sgst_amount numeric,
cgst_amount numeric,
igst_amount numeric,
note text,
round_off numeric,
debit_account_id uuid,
credit_account_id uuid,
so_no text,
so_date timestamp without time zone,
source_warehouse_id uuid,
destination_warehouse_id uuid,
invoice_voucher text,
created_by uuid,
lines jsonb,
type integer,
settled_amount numeric
)
LOOP
BEGIN
-- Duplicate? mark & skip
IF EXISTS (
SELECT 1
FROM public.invoice_headers
WHERE invoice_number = invoice.invoice_number
AND company_id = invoice.company_id
) THEN
record_status := 'duplicate';
results := array_append(results, invoice.invoice_number || ':' || record_status);
CONTINUE;
END IF;
-- Insert via your existing routine
CALL public.insert_invoice_by_excel(
invoice.invoice_id,
invoice.company_id,
invoice.customer_id,
invoice.discount,
invoice.currency_id,
invoice.invoice_date,
invoice.invoice_number,
invoice.payment_term,
invoice.invoice_status_id,
invoice.due_date,
invoice.total_amount,
invoice.taxable_amount,
invoice.fees,
invoice.sgst_amount,
invoice.cgst_amount,
invoice.igst_amount,
invoice.note,
invoice.round_off,
invoice.debit_account_id,
invoice.credit_account_id,
invoice.so_no,
invoice.so_date,
invoice.source_warehouse_id,
invoice.destination_warehouse_id,
invoice.invoice_voucher,
COALESCE(invoice.created_by, v_created_by),
invoice.lines::jsonb,
invoice.type,
invoice.settled_amount
);
record_status := 'success';
results := array_append(results, invoice.invoice_number || ':' || record_status);
EXCEPTION WHEN OTHERS THEN
record_status := 'failed';
results := array_append(results, invoice.invoice_number || ':' || record_status);
CONTINUE;
END;
END LOOP;
-- Emit overall status as before
IF array_position(results, '%:failed') IS NOT NULL THEN
RAISE NOTICE 'STATUS:error';
ELSIF array_position(results, '%:duplicate') IS NOT NULL THEN
RAISE NOTICE 'STATUS:duplicate';
ELSE
RAISE NOTICE 'STATUS:success';
END IF;
-- Emit per-record status (as a JSONB array for convenience)
RAISE NOTICE 'DETAILS:%', to_jsonb(results);
END;
$procedure$
-- Procedure: create_invoice_payment
-- SOURCE
CREATE OR REPLACE PROCEDURE public.create_invoice_payment(IN p_invoice_payment_header_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_received_date date, IN p_mode_of_payment text, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_reference text, IN p_received_amount numeric, IN p_grand_total_amount numeric, IN p_advance_amount numeric, IN p_description text, IN p_tds_amount numeric, IN p_created_by uuid, IN p_invoice_payment_details jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_total_paid_amount NUMERIC;
v_settled_amount NUMERIC;
v_invoice_status_id INT;
v_payment_status_id INT;
v_invoice_header_id UUID;
v_payment_amount NUMERIC;
v_tds_amount NUMERIC;
v_serial_number INT;
v_row JSONB;
v_payment_number VARCHAR;
v_invoice_ids UUID[] := '{}';
BEGIN
v_payment_number := get_new_payment_number(p_company_id, p_received_date);
INSERT INTO public.invoice_payment_headers (
id,
company_id,
customer_id,
received_date,
mode_of_payment,
credit_account_id,
debit_account_id,
reference,
received_amount,
description,
tds_amount,
advance_amount,
grand_total_amount,
payment_number,
created_by,
created_on_utc,
is_deleted
)
VALUES (
p_invoice_payment_header_id,
p_company_id,
p_customer_id,
p_received_date,
p_mode_of_payment,
p_credit_account_id,
p_debit_account_id,
p_reference,
p_received_amount,
p_description,
p_tds_amount,
p_advance_amount,
p_grand_total_amount,
v_payment_number,
p_created_by,
now(),
false
);
FOR v_row IN SELECT * FROM jsonb_array_elements(p_invoice_payment_details)
LOOP
v_invoice_header_id := (v_row->>'header_id')::UUID;
v_payment_amount := COALESCE((v_row->>'payment_amount')::NUMERIC, 0);
v_tds_amount := COALESCE((v_row->>'tds_amount')::NUMERIC, 0);
v_serial_number := (v_row->>'serial_number')::INT;
v_total_paid_amount := 0;
v_payment_status_id := 1;
v_invoice_status_id := 1;
INSERT INTO public.invoice_payment_details (
id,
invoice_payment_header_id,
invoice_header_id,
received_amount,
tds_amount,
serial_number,
created_by,
created_on_utc
)
VALUES (
(v_row->>'id')::UUID,
p_invoice_payment_header_id,
v_invoice_header_id,
v_payment_amount,
v_tds_amount,
v_serial_number,
p_created_by,
now()
);
SELECT settled_amount INTO v_settled_amount
FROM public.invoice_headers
WHERE id = v_invoice_header_id;
v_total_paid_amount := COALESCE(v_settled_amount, 0) + v_payment_amount + v_tds_amount;
IF (SELECT total_amount FROM public.invoice_headers WHERE id = v_invoice_header_id) > v_total_paid_amount THEN
v_invoice_status_id := 4; -- PARTIALLY_PAID
v_payment_status_id := 2; -- PartiallyPaid
ELSE
v_invoice_status_id := 5; -- PAID
v_payment_status_id := 3; -- FullyPaid
END IF;
UPDATE public.invoice_headers
SET settled_amount = v_total_paid_amount,
invoice_status_id = v_invoice_status_id,
payment_status_id = v_payment_status_id
WHERE id = v_invoice_header_id;
v_invoice_ids := array_append(v_invoice_ids, v_invoice_header_id);
END LOOP;
IF array_length(v_invoice_ids, 1) > 0 THEN
CALL public.update_invoice_next_status_for_invoice_header(
p_company_id,
v_invoice_ids,
p_created_by
);
END IF;
RAISE NOTICE 'Invoice payment inserted successfully';
END;
$procedure$
-- TARGET
CREATE OR REPLACE PROCEDURE public.create_invoice_payment(IN p_invoice_payment_header_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_received_date date, IN p_mode_of_payment text, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_reference text, IN p_received_amount numeric, IN p_grand_total_amount numeric, IN p_advance_amount numeric, IN p_description text, IN p_tds_amount numeric, IN p_created_by uuid, IN p_invoice_payment_details jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_total_paid_amount NUMERIC;
v_settled_amount NUMERIC;
v_invoice_status_id INT;
v_invoice_header_id UUID;
v_payment_amount NUMERIC;
v_tds_amount NUMERIC;
v_serial_number INT;
v_row JSONB;
v_payment_number VARCHAR;
v_invoice_ids UUID[] := '{}'; -- Initialize empty array
BEGIN
v_payment_number := get_new_payment_number(p_company_id, p_received_date);
-- Insert into invoice_payment_headers
INSERT INTO public.invoice_payment_headers (
id,
company_id,
customer_id,
received_date,
mode_of_payment,
credit_account_id,
debit_account_id,
reference,
received_amount,
description,
tds_amount,
advance_amount,
grand_total_amount,
payment_number,
created_by,
created_on_utc,
is_deleted
)
VALUES (
p_invoice_payment_header_id,
p_company_id,
p_customer_id,
p_received_date,
p_mode_of_payment,
p_credit_account_id,
p_debit_account_id,
p_reference,
p_received_amount,
p_description,
p_tds_amount,
p_advance_amount,
p_grand_total_amount,
v_payment_number,
p_created_by,
now(),
false
);
-- Process each detail row
FOR v_row IN SELECT * FROM jsonb_array_elements(p_invoice_payment_details)
LOOP
v_invoice_header_id := (v_row->>'header_id')::UUID;
v_payment_amount := (v_row->>'payment_amount')::NUMERIC;
v_tds_amount := (v_row->>'tds_amount')::NUMERIC;
v_serial_number := (v_row->>'serial_number')::INT;
v_total_paid_amount := 0;
INSERT INTO public.invoice_payment_details (
id,
invoice_payment_header_id,
invoice_header_id,
received_amount,
tds_amount,
serial_number,
created_by,
created_on_utc
)
VALUES (
(v_row->>'id')::UUID,
p_invoice_payment_header_id,
v_invoice_header_id,
v_payment_amount,
v_tds_amount,
v_serial_number,
p_created_by,
now()
);
-- Update settled amount and status
SELECT setteled_amount INTO v_settled_amount
FROM public.invoice_headers
WHERE id = v_invoice_header_id;
v_total_paid_amount := v_settled_amount + v_payment_amount + v_tds_amount;
IF (SELECT total_amount FROM public.invoice_headers WHERE id = v_invoice_header_id) > v_total_paid_amount THEN
v_invoice_status_id := 4; -- PARTIALLY_PAID
ELSE
v_invoice_status_id := 5; -- PAID
END IF;
UPDATE public.invoice_headers
SET setteled_amount = v_total_paid_amount,
invoice_status_id = v_invoice_status_id
WHERE id = v_invoice_header_id;
-- Collect invoice header id for next status procedure
v_invoice_ids := array_append(v_invoice_ids, v_invoice_header_id);
END LOOP;
-- Call next status procedure if any invoices were updated
IF array_length(v_invoice_ids, 1) > 0 THEN
CALL public.update_invoice_next_status_for_invoice_header(
p_company_id,
v_invoice_ids,
p_created_by
);
END IF;
RAISE NOTICE 'Invoice payment inserted successfully';
END;
$procedure$
-- Procedure: create_invoice_payment
CREATE OR REPLACE PROCEDURE public.create_invoice_payment(IN p_invoice_payment_header_id uuid, IN p_company_id uuid, IN p_customer_id uuid, IN p_received_date date, IN p_mode_of_payment text, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_reference text, IN p_received_amount numeric, IN p_grand_total_amount numeric, IN p_advance_amount numeric, IN p_description text, IN p_tds_amount numeric, IN p_created_by uuid, IN p_payment_status_id integer, IN p_invoice_payment_details jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_total_paid_amount NUMERIC;
v_settled_amount NUMERIC;
v_invoice_status_id INT;
v_payment_status_id INT;
v_invoice_header_id UUID;
v_payment_amount NUMERIC;
v_tds_amount NUMERIC;
v_serial_number INT;
v_row JSONB;
v_payment_number VARCHAR;
v_invoice_ids UUID[] := '{}';
BEGIN
-- Generate payment number
v_payment_number := get_new_payment_number(p_company_id, p_received_date);
-- Insert payment header
INSERT INTO public.invoice_payment_headers (
id,
company_id,
customer_id,
received_date,
mode_of_payment,
credit_account_id,
debit_account_id,
reference,
received_amount,
description,
tds_amount,
advance_amount,
grand_total_amount,
payment_number,
created_by,
created_on_utc,
is_deleted,
payment_status_id
)
VALUES (
p_invoice_payment_header_id,
p_company_id,
p_customer_id,
p_received_date,
p_mode_of_payment,
p_credit_account_id,
p_debit_account_id,
p_reference,
p_received_amount,
p_description,
p_tds_amount,
p_advance_amount,
p_grand_total_amount,
v_payment_number,
p_created_by,
now(),
false,
p_payment_status_id
);
-- Process invoice-wise payments
FOR v_row IN SELECT * FROM jsonb_array_elements(p_invoice_payment_details)
LOOP
v_invoice_header_id := (v_row->>'header_id')::UUID;
v_payment_amount := COALESCE((v_row->>'payment_amount')::NUMERIC, 0);
v_tds_amount := COALESCE((v_row->>'tds_amount')::NUMERIC, 0);
v_serial_number := (v_row->>'serial_number')::INT;
INSERT INTO public.invoice_payment_details (
id,
invoice_payment_header_id,
invoice_header_id,
received_amount,
tds_amount,
serial_number,
created_by,
created_on_utc
)
VALUES (
(v_row->>'id')::UUID,
p_invoice_payment_header_id,
v_invoice_header_id,
v_payment_amount,
v_tds_amount,
v_serial_number,
p_created_by,
now()
);
SELECT settled_amount
INTO v_settled_amount
FROM public.invoice_headers
WHERE id = v_invoice_header_id;
v_total_paid_amount :=
COALESCE(v_settled_amount, 0)
+ v_payment_amount
+ v_tds_amount;
IF (SELECT total_amount FROM public.invoice_headers WHERE id = v_invoice_header_id)
> v_total_paid_amount THEN
v_invoice_status_id := 4; -- Partially Paid
v_payment_status_id := 2; -- PartiallyPaid
ELSE
v_invoice_status_id := 5; -- Paid
v_payment_status_id := 3; -- FullyPaid
END IF;
UPDATE public.invoice_headers
SET
settled_amount = v_total_paid_amount,
invoice_status_id = v_invoice_status_id,
payment_status_id = v_payment_status_id
WHERE id = v_invoice_header_id;
v_invoice_ids := array_append(v_invoice_ids, v_invoice_header_id);
END LOOP;
-- Move invoice to next workflow status
IF array_length(v_invoice_ids, 1) > 0 THEN
CALL public.update_invoice_next_status_for_invoice_header(
p_company_id,
v_invoice_ids,
p_created_by
);
END IF;
RAISE NOTICE 'Invoice payment inserted successfully';
END;
$procedure$
-- Procedure: insert_delinquency_snapshot
CREATE OR REPLACE PROCEDURE public.insert_delinquency_snapshot(IN p_organization_id uuid, IN p_customer_id uuid, IN p_cycle_id uuid, IN p_overdue_amount numeric, IN p_oldest_due_date date, IN p_days_past_due integer, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- 🛑 Guard: one snapshot per cycle per day
IF EXISTS (
SELECT 1
FROM delinquency_snapshots ds
WHERE ds.cycle_id = p_cycle_id
AND ds.created_on_utc::date = CURRENT_DATE
AND ds.is_deleted = false
) THEN
RETURN;
END IF;
INSERT INTO delinquency_snapshots (
id,
organization_id,
customer_id,
cycle_id,
overdue_amount,
oldest_due_date,
days_past_due,
aging_bucket,
last_evaluated_on,
created_on_utc,
created_by,
is_deleted
)
VALUES (
gen_random_uuid(),
p_organization_id,
p_customer_id,
p_cycle_id,
p_overdue_amount,
p_oldest_due_date,
p_days_past_due,
get_aging_bucket(p_days_past_due),
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
p_created_by,
false
);
END;
$procedure$
-- View: v_run_defaulter_fdw
SELECT result
FROM run_defaulter_fdw() run_defaulter_fdw(result);