ekwo-os 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +873 -0
- package/dist/args.d.ts +57 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +134 -0
- package/dist/args.js.map +1 -0
- package/dist/assets/expected-objects.json +9432 -0
- package/dist/assets/migrations/20260911120000_core_companies.sql +260 -0
- package/dist/assets/migrations/20260911120100_accounts_journals.sql +211 -0
- package/dist/assets/migrations/20260911120200_contacts.sql +90 -0
- package/dist/assets/migrations/20260911120300_taxes.sql +146 -0
- package/dist/assets/migrations/20260911120400_entries.sql +338 -0
- package/dist/assets/migrations/20260911120500_documents.sql +279 -0
- package/dist/assets/migrations/20260911120600_payments_reconciliation.sql +251 -0
- package/dist/assets/migrations/20260911120700_bank.sql +178 -0
- package/dist/assets/migrations/20260911120800_analytics_attachments.sql +128 -0
- package/dist/assets/migrations/20260911120900_post_document.sql +279 -0
- package/dist/assets/migrations/20260911121000_reporting.sql +235 -0
- package/dist/assets/migrations/20260911121100_country_templates.sql +226 -0
- package/dist/assets/migrations/20260911121200_fec.sql +71 -0
- package/dist/assets/migrations/20260911130000_instance.sql +77 -0
- package/dist/assets/migrations/20260911130100_instance_members.sql +224 -0
- package/dist/assets/migrations/20260911140000_instance_admins.sql +112 -0
- package/dist/assets/migrations/20260911160000_tax_posting_templates_unique.sql +17 -0
- package/dist/assets/migrations/20260911170000_document_amount_paid.sql +82 -0
- package/dist/assets/migrations/20260911173000_post_payment.sql +122 -0
- package/dist/assets/migrations/20260911173100_sequence_counters_under_rls.sql +84 -0
- package/dist/assets/migrations/20260911183000_country_journal_defaults.sql +143 -0
- package/dist/assets/migrations/20260911193853_line_account_defaults.sql +274 -0
- package/dist/assets/migrations/20260911195054_products.sql +221 -0
- package/dist/assets/migrations/20260911210131_anon_surface.sql +85 -0
- package/dist/assets/migrations/20260912074712_country_packs.sql +318 -0
- package/dist/assets/migrations/20260912080311_report_code_and_region.sql +198 -0
- package/dist/assets/migrations/20260912081014_pack_certification_maintained.sql +31 -0
- package/dist/assets/migrations/20260912081015_pack_certification_backfill.sql +16 -0
- package/dist/assets/migrations/20260912090407_tax_report_boxes.sql +329 -0
- package/dist/assets/migrations/20260912091917_tax_on_base_value.sql +25 -0
- package/dist/assets/migrations/20260912091918_tax_engine_columns.sql +776 -0
- package/dist/assets/migrations/20260912094412_opening_and_closing.sql +759 -0
- package/dist/assets/migrations/20260912095825_charts_of_accounts.sql +326 -0
- package/dist/assets/migrations/20260912100412_financial_statements.sql +632 -0
- package/dist/assets/migrations/20260912104719_one_formula_evaluator.sql +209 -0
- package/dist/assets/migrations/20260912105720_entry_kind_appropriation.sql +26 -0
- package/dist/assets/migrations/20260912105721_appropriation_entry_kind.sql +444 -0
- package/dist/assets/migrations/20260912111751_document_rules.sql +274 -0
- package/dist/assets/migrations/20260912112132_cash_basis_vat_and_fx.sql +1189 -0
- package/dist/assets/migrations/20260913074512_modules.sql +493 -0
- package/dist/assets/migrations/20260913075903_asset_disposal_roles.sql +47 -0
- package/dist/assets/migrations/20260913083216_capabilities.sql +637 -0
- package/dist/assets/migrations/20260913083901_company_invitations.sql +262 -0
- package/dist/assets/migrations/20260913084402_user_preferences.sql +376 -0
- package/dist/assets/migrations/20260913084847_company_profile.sql +261 -0
- package/dist/assets/migrations/20260913085436_numbering_reads_the_pack.sql +235 -0
- package/dist/assets/migrations/20260913085932_api_keys.sql +303 -0
- package/dist/assets/migrations/20260913090216_fiscal_year_bounds.sql +159 -0
- package/dist/assets/migrations/20260913092527_entries_import.sql +219 -0
- package/dist/assets/migrations/20260913101536_null_safe_guards.sql +188 -0
- package/dist/assets/migrations/20260913102115_installer_is_named.sql +462 -0
- package/dist/assets/migrations/20260913102758_no_currency_default.sql +138 -0
- package/dist/assets/migrations/20260913103355_cash_basis_needs_a_box.sql +453 -0
- package/dist/assets/migrations/20260913104014_aged_balance_named_group.sql +79 -0
- package/dist/assets/migrations/20260913104232_foreign_key_indexes.sql +144 -0
- package/dist/assets/migrations/20260913105120_declared_no_reader.sql +41 -0
- package/dist/assets/migrations/20260913111407_pack_languages.sql +223 -0
- package/dist/assets/migrations/20260913112233_schema_comments_for_a_reader.sql +27 -0
- package/dist/assets/migrations/20260913114535_fec_opening_balances.sql +307 -0
- package/dist/assets/migrations/20260914103412_audit_log.sql +509 -0
- package/dist/assets/migrations/20260914111907_pack_upgrade.sql +428 -0
- package/dist/assets/migrations/20260914120500_rounding_reads_the_currency.sql +206 -0
- package/dist/assets/migrations/20260914121200_amounts_round_at_the_currency.sql +2403 -0
- package/dist/assets/migrations/20260914134325_schema_version_0_2_0.sql +36 -0
- package/dist/assets/migrations/20260914143915_accounts_in_use.sql +428 -0
- package/dist/assets/migrations/20260914144731_account_code_frozen.sql +99 -0
- package/dist/assets/migrations/20260914151207_schema_grants_its_own_rights.sql +284 -0
- package/dist/assets/migrations/20260914152840_pack_upgrade_records_its_own_line.sql +237 -0
- package/dist/assets/migrations/20260914163943_declaration_periodicity.sql +349 -0
- package/dist/assets/migrations/20260915094000_foreign_services_received.sql +37 -0
- package/dist/assets/migrations/20260915094500_mentions_read_a_foreign_service.sql +72 -0
- package/dist/assets/migrations/20260915153000_document_shares.sql +568 -0
- package/dist/assets/migrations/20260915160000_ec_sales_list.sql +181 -0
- package/dist/assets/migrations/20260915161842_pack_sources.sql +56 -0
- package/dist/assets/migrations/20260915170500_schema_version_0_3_0.sql +45 -0
- package/dist/assets/migrations/20260915174500_a_vat_category_is_a_code.sql +177 -0
- package/dist/assets/migrations/20260915180000_document_rule_references.sql +85 -0
- package/dist/assets/migrations/20260915181000_territories.sql +342 -0
- package/dist/assets/migrations/20260915181500_ec_sales_list_reads_the_territories.sql +222 -0
- package/dist/assets/migrations/20260915182000_intracom_triangular.sql +53 -0
- package/dist/assets/migrations/20260915182500_a_triangular_supply_says_reverse_charge.sql +99 -0
- package/dist/assets/migrations/20260915191200_a_document_knows_its_language.sql +633 -0
- package/dist/assets/migrations/20260915195000_a_price_that_holds_its_tax.sql +649 -0
- package/dist/assets/migrations/20260915200000_a_posting_names_its_boxes.sql +1004 -0
- package/dist/assets/migrations/20260916094500_a_company_files_more_than_one_declaration.sql +795 -0
- package/dist/assets/migrations/20260916103000_a_ledger_line_names_its_posting.sql +902 -0
- package/dist/assets/migrations/20260916123000_a_vocabulary_for_a_tax_that_is_not_a_vat.sql +144 -0
- package/dist/assets/migrations/20260916124000_a_box_can_be_a_rate_of_a_box.sql +525 -0
- package/dist/assets/migrations/20260916125000_a_tax_follows_the_territory.sql +1318 -0
- package/dist/assets/migrations/20260916126000_a_tax_point_and_its_exception.sql +1211 -0
- package/dist/assets/migrations/20260917090000_a_counterparty_that_learns.sql +561 -0
- package/dist/assets/migrations/20260917120000_what_the_money_pays.sql +545 -0
- package/dist/assets/migrations/20260917150000_a_filing_is_a_row.sql +488 -0
- package/dist/assets/migrations/20260917170000_when_it_is_due.sql +194 -0
- package/dist/assets/migrations/20260917180000_a_box_is_a_number_and_a_kind.sql +213 -0
- package/dist/assets/migrations/20260917190000_what_a_declaration_owes.sql +486 -0
- package/dist/assets/migrations/20260917200000_what_moved_after_it_went.sql +227 -0
- package/dist/assets/migrations/20260918090000_a_form_names_its_file.sql +27 -0
- package/dist/assets/migrations/20260918110000_a_deposit_and_what_came_back.sql +286 -0
- package/dist/assets/migrations/20260918113741_a_fourth_preset_is_named.sql +9 -0
- package/dist/assets/migrations/20260918113807_a_client_reads_and_hands_over.sql +96 -0
- package/dist/assets/migrations/20260918114322_two_definer_functions_check_their_caller.sql +75 -0
- package/dist/assets/migrations/20260918130000_every_company_somebody_keeps.sql +177 -0
- package/dist/assets/migrations/20260918140000_a_session_nobody_prepared.sql +40 -0
- package/dist/assets/migrations/20260918141107_a_line_keeps_the_tax_it_was_posted_with.sql +256 -0
- package/dist/assets/migrations/20260918141342_a_company_has_an_electronic_address.sql +66 -0
- package/dist/assets/migrations/20260918141605_an_invoice_reads_whole_from_the_views.sql +313 -0
- package/dist/assets/migrations/20260918141627_a_policy_asks_once.sql +132 -0
- package/dist/assets/migrations/20260918143352_a_suggestion_reads_the_words_once.sql +204 -0
- package/dist/assets/migrations/20260918143417_a_reference_of_the_caller_and_a_rehearsal.sql +115 -0
- package/dist/assets/migrations/20260918150712_a_company_leaves_with_its_books.sql +1119 -0
- package/dist/assets/migrations/20260918150931_a_statement_is_imported_once.sql +654 -0
- package/dist/assets/migrations/20260918161204_a_posted_document_does_not_move.sql +437 -0
- package/dist/assets/migrations/20260918161538_a_posted_entry_does_not_move.sql +252 -0
- package/dist/assets/migrations/20260918171946_an_entry_is_posted_by_post_entry.sql +289 -0
- package/dist/assets/migrations/20260918174312_schema_version_0_4_0.sql +53 -0
- package/dist/assets/modules/assets/module.json +13 -0
- package/dist/assets/modules/assets/supabase/migrations/20260913081447_assets.sql +1190 -0
- package/dist/assets/modules/assets/supabase/migrations/20260913104234_foreign_key_indexes.sql +47 -0
- package/dist/assets/modules/assets/supabase/migrations/20260913104500_assets_capabilities.sql +145 -0
- package/dist/assets/modules/assets/supabase/migrations/20260914122100_assets_round_at_the_currency.sql +564 -0
- package/dist/assets/modules/assets/supabase/migrations/20260914145012_assets_accounts_in_use.sql +37 -0
- package/dist/assets/modules/assets/supabase/migrations/20260914151530_assets_grants_its_own_rights.sql +63 -0
- package/dist/assets/modules/assets/supabase/migrations/20260918113912_assets_client_reads.sql +20 -0
- package/dist/assets/modules/assets/supabase/migrations/20260918150801_assets_leave_with_the_company.sql +41 -0
- package/dist/assets/modules/budgets/module.json +12 -0
- package/dist/assets/modules/budgets/supabase/migrations/20260913083012_budgets.sql +210 -0
- package/dist/assets/modules/budgets/supabase/migrations/20260913104233_foreign_key_indexes.sql +37 -0
- package/dist/assets/modules/budgets/supabase/migrations/20260913104501_budgets_capabilities.sql +72 -0
- package/dist/assets/modules/budgets/supabase/migrations/20260914122400_budgets_round_at_the_currency.sql +68 -0
- package/dist/assets/modules/budgets/supabase/migrations/20260914145048_budgets_accounts_in_use.sql +30 -0
- package/dist/assets/modules/budgets/supabase/migrations/20260914151812_budgets_grants_its_own_rights.sql +31 -0
- package/dist/assets/modules/budgets/supabase/migrations/20260918113954_budgets_client_reads.sql +20 -0
- package/dist/assets/modules/budgets/supabase/migrations/20260918150823_budgets_leave_with_the_company.sql +37 -0
- package/dist/assets/modules/schema/module.1.json +69 -0
- package/dist/assets/seed/00_currencies.sql +15 -0
- package/dist/assets/seed/00_territories.sql +280 -0
- package/dist/assets/seed/05_framework_generic.sql +110 -0
- package/dist/assets/seed/10_pack_be.sql +1284 -0
- package/dist/assets/seed/11_pack_fr.sql +1112 -0
- package/dist/assets/seed/12_pack_lu.sql +2484 -0
- package/dist/assets/seed/13_pack_ee.sql +733 -0
- package/dist/assets/seed/14_pack_gb.sql +796 -0
- package/dist/assets/seed/15_pack_us.sql +751 -0
- package/dist/assets/seed/90_demo_company.sql +368 -0
- package/dist/assets/seed/modules/assets/10_pack_be.sql +58 -0
- package/dist/assets/seed/modules/assets/11_pack_fr.sql +57 -0
- package/dist/assets/seed/modules/assets/14_pack_gb.sql +58 -0
- package/dist/assets/seed/modules/assets/15_pack_us.sql +60 -0
- package/dist/auth.d.ts +59 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +134 -0
- package/dist/auth.js.map +1 -0
- package/dist/bin.d.ts +6 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +8 -0
- package/dist/bin.js.map +1 -0
- package/dist/books.d.ts +60 -0
- package/dist/books.d.ts.map +1 -0
- package/dist/books.js +168 -0
- package/dist/books.js.map +1 -0
- package/dist/bootstrap.d.ts +236 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +459 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/bundle.d.ts +27 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +47 -0
- package/dist/bundle.js.map +1 -0
- package/dist/checklist.d.ts +35 -0
- package/dist/checklist.d.ts.map +1 -0
- package/dist/checklist.js +64 -0
- package/dist/checklist.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +318 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/company.d.ts +59 -0
- package/dist/commands/company.d.ts.map +1 -0
- package/dist/commands/company.js +287 -0
- package/dist/commands/company.js.map +1 -0
- package/dist/commands/contact.d.ts +9 -0
- package/dist/commands/contact.d.ts.map +1 -0
- package/dist/commands/contact.js +82 -0
- package/dist/commands/contact.js.map +1 -0
- package/dist/commands/demo.d.ts +19 -0
- package/dist/commands/demo.d.ts.map +1 -0
- package/dist/commands/demo.js +72 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +68 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/document.d.ts +17 -0
- package/dist/commands/document.d.ts.map +1 -0
- package/dist/commands/document.js +217 -0
- package/dist/commands/document.js.map +1 -0
- package/dist/commands/init.d.ts +31 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +535 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +24 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +128 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/migrate.d.ts +30 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +115 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/module.d.ts +32 -0
- package/dist/commands/module.d.ts.map +1 -0
- package/dist/commands/module.js +285 -0
- package/dist/commands/module.js.map +1 -0
- package/dist/commands/pack.d.ts +22 -0
- package/dist/commands/pack.d.ts.map +1 -0
- package/dist/commands/pack.js +481 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/payment.d.ts +15 -0
- package/dist/commands/payment.d.ts.map +1 -0
- package/dist/commands/payment.js +97 -0
- package/dist/commands/payment.js.map +1 -0
- package/dist/commands/register.d.ts +20 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +134 -0
- package/dist/commands/register.js.map +1 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +134 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/whoami.d.ts +21 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +133 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/company.d.ts +16 -0
- package/dist/company.d.ts.map +1 -0
- package/dist/company.js +26 -0
- package/dist/company.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +50 -0
- package/dist/config.js.map +1 -0
- package/dist/connection.d.ts +79 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +116 -0
- package/dist/connection.js.map +1 -0
- package/dist/context.d.ts +47 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +93 -0
- package/dist/context.js.map +1 -0
- package/dist/doctor.d.ts +45 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +448 -0
- package/dist/doctor.js.map +1 -0
- package/dist/grants.d.ts +88 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +174 -0
- package/dist/grants.js.map +1 -0
- package/dist/identity.d.ts +54 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +132 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/inventory.d.ts +155 -0
- package/dist/inventory.d.ts.map +1 -0
- package/dist/inventory.js +303 -0
- package/dist/inventory.js.map +1 -0
- package/dist/migrations.d.ts +76 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +226 -0
- package/dist/migrations.js.map +1 -0
- package/dist/module/read.d.ts +91 -0
- package/dist/module/read.d.ts.map +1 -0
- package/dist/module/read.js +172 -0
- package/dist/module/read.js.map +1 -0
- package/dist/output.d.ts +110 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +173 -0
- package/dist/output.js.map +1 -0
- package/dist/pack/certification.d.ts +27 -0
- package/dist/pack/certification.d.ts.map +1 -0
- package/dist/pack/certification.js +38 -0
- package/dist/pack/certification.js.map +1 -0
- package/dist/pack/compile.d.ts +75 -0
- package/dist/pack/compile.d.ts.map +1 -0
- package/dist/pack/compile.js +801 -0
- package/dist/pack/compile.js.map +1 -0
- package/dist/pack/filing.d.ts +48 -0
- package/dist/pack/filing.d.ts.map +1 -0
- package/dist/pack/filing.js +56 -0
- package/dist/pack/filing.js.map +1 -0
- package/dist/pack/read.d.ts +766 -0
- package/dist/pack/read.d.ts.map +1 -0
- package/dist/pack/read.js +2336 -0
- package/dist/pack/read.js.map +1 -0
- package/dist/pack/schema.d.ts +26 -0
- package/dist/pack/schema.d.ts.map +1 -0
- package/dist/pack/schema.js +181 -0
- package/dist/pack/schema.js.map +1 -0
- package/dist/pack/territories.d.ts +98 -0
- package/dist/pack/territories.d.ts.map +1 -0
- package/dist/pack/territories.js +0 -0
- package/dist/pack/territories.js.map +1 -0
- package/dist/pack/upgrade.d.ts +86 -0
- package/dist/pack/upgrade.d.ts.map +1 -0
- package/dist/pack/upgrade.js +76 -0
- package/dist/pack/upgrade.js.map +1 -0
- package/dist/pack/vat-codes.d.ts +273 -0
- package/dist/pack/vat-codes.d.ts.map +1 -0
- package/dist/pack/vat-codes.js +513 -0
- package/dist/pack/vat-codes.js.map +1 -0
- package/dist/profiles.d.ts +67 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +187 -0
- package/dist/profiles.js.map +1 -0
- package/dist/prompt.d.ts +40 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +120 -0
- package/dist/prompt.js.map +1 -0
- package/dist/registry.d.ts +84 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +104 -0
- package/dist/registry.js.map +1 -0
- package/dist/rest.d.ts +81 -0
- package/dist/rest.d.ts.map +1 -0
- package/dist/rest.js +245 -0
- package/dist/rest.js.map +1 -0
- package/dist/schema.d.ts +15 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +15 -0
- package/dist/schema.js.map +1 -0
- package/dist/seeds.d.ts +27 -0
- package/dist/seeds.d.ts.map +1 -0
- package/dist/seeds.js +47 -0
- package/dist/seeds.js.map +1 -0
- package/dist/session.d.ts +39 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +92 -0
- package/dist/session.js.map +1 -0
- package/dist/sql.d.ts +48 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +112 -0
- package/dist/sql.js.map +1 -0
- package/dist/status.d.ts +94 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +120 -0
- package/dist/status.js.map +1 -0
- package/dist/ui.d.ts +55 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +114 -0
- package/dist/ui.js.map +1 -0
- package/package.json +29 -0
- package/schema/output.1.json +613 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
-- Ekwo OS — a financial statement is data, and one of them fits any chart.
|
|
2
|
+
--
|
|
3
|
+
-- The schema could produce a trial balance and a ledger, and nothing that an
|
|
4
|
+
-- accountant files. A balance sheet was a query somebody would have written
|
|
5
|
+
-- in the application, once per country, which is how a country gets back into
|
|
6
|
+
-- the core through the front door.
|
|
7
|
+
--
|
|
8
|
+
-- Three tables and one function, filled by a pack:
|
|
9
|
+
--
|
|
10
|
+
-- `statement_templates` one statement of one framework — the Belgian
|
|
11
|
+
-- abbreviated scheme, the French liasse, the
|
|
12
|
+
-- generic one by account type.
|
|
13
|
+
-- `statement_line_templates` one line of it, with the plus/minus lists a
|
|
14
|
+
-- total is computed from.
|
|
15
|
+
-- `statement_line_rules` how accounts reach a line: by code range, by
|
|
16
|
+
-- code prefix, by account type, or one code.
|
|
17
|
+
--
|
|
18
|
+
-- **Mapping by code range is presentation, and presentation is allowed.** The
|
|
19
|
+
-- rule that forbids choosing an account *to post to* by prefix is about
|
|
20
|
+
-- posting. The NBB scheme, the liasse and every XBRL taxonomy map by ranges of
|
|
21
|
+
-- the legal chart — in Belgium the rubric code *is* the range, `40/41` being
|
|
22
|
+
-- accounts 40 and 41 — and refusing that would mean hand-listing four hundred
|
|
23
|
+
-- codes per country.
|
|
24
|
+
--
|
|
25
|
+
-- **The generic framework is what the eighteen account types buy.** A
|
|
26
|
+
-- statement whose `country` is null and whose rules are all `account_type`
|
|
27
|
+
-- gives a readable balance sheet on any chart, including the code-less charts
|
|
28
|
+
-- of the United Kingdom and the United States, and it is the fallback for a
|
|
29
|
+
-- company whose chart declares no statement of its own.
|
|
30
|
+
--
|
|
31
|
+
-- **`country` is nullable, so the key is the code alone.** A nullable column
|
|
32
|
+
-- cannot carry a primary key, and a statement code is unique across packs the
|
|
33
|
+
-- way a declaration form code is unique inside a country — `BE-BNB-ABBR-BS`,
|
|
34
|
+
-- `FR-2050`, `IFRS-SME-BS`. `chart_code` null means every chart of the
|
|
35
|
+
-- country; a statement that only fits one chart names it.
|
|
36
|
+
--
|
|
37
|
+
-- **A closing entry is not what a period earned.** `close_fiscal_year()` books
|
|
38
|
+
-- the mirror image of every income and expense account so the next year starts
|
|
39
|
+
-- at nil, and marks the entry `kind = 'closing'`. An income statement leaves
|
|
40
|
+
-- those out — a closed year would otherwise read as a result of zero — and a
|
|
41
|
+
-- balance sheet keeps them, because that entry is what carries the result onto
|
|
42
|
+
-- the line the balance sheet shows it on. An allocation section keeps them
|
|
43
|
+
-- too, and there is a limit there worth knowing: the appropriation entry and
|
|
44
|
+
-- the entry that zeroes the appropriation accounts are both `closing`, so the
|
|
45
|
+
-- two net out and a Belgian allocation section reads nil after a close. Making
|
|
46
|
+
-- it readable needs a kind that tells them apart, which is a change to
|
|
47
|
+
-- `close_fiscal_year()`, not to this file.
|
|
48
|
+
--
|
|
49
|
+
-- **Not copied into a company**, for the reason a declaration form is not: an
|
|
50
|
+
-- operator does not get to redefine what the Banque nationale prints. They are
|
|
51
|
+
-- reference data `financial_statement()` reads directly, which is also why
|
|
52
|
+
-- they carry no `company_id`.
|
|
53
|
+
--
|
|
54
|
+
-- **One evaluator, called twice.** A declaration form and a financial statement
|
|
55
|
+
-- derive their totals the same way — a list to add, a list to subtract — and
|
|
56
|
+
-- the two differences between them are parameters, not engines: a return omits
|
|
57
|
+
-- a box that comes to nothing where a statement prints its whole frame
|
|
58
|
+
-- (`p_keep_zero`), and a statement multiplies a line by the sign the scheme
|
|
59
|
+
-- reads it with (`factor`). `evaluate_totals()` below is that engine;
|
|
60
|
+
-- `vat_return()` is rewritten onto it in the migration that follows this one,
|
|
61
|
+
-- its own file because it was published before this.
|
|
62
|
+
|
|
63
|
+
-- ---------------------------------------------------------------------------
|
|
64
|
+
-- The statements
|
|
65
|
+
-- ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
create table if not exists statement_templates (
|
|
68
|
+
code text primary key,
|
|
69
|
+
country char(2),
|
|
70
|
+
chart_code text,
|
|
71
|
+
name text not null,
|
|
72
|
+
name_i18n jsonb not null default '{}'::jsonb,
|
|
73
|
+
kind text not null,
|
|
74
|
+
framework text,
|
|
75
|
+
valid_from date not null default date '1970-01-01',
|
|
76
|
+
valid_to date,
|
|
77
|
+
legal_reference text,
|
|
78
|
+
constraint statement_templates_country_format check (country is null or country ~ '^[A-Z]{2}$'),
|
|
79
|
+
constraint statement_templates_kind check (
|
|
80
|
+
kind in ('balance_sheet', 'income_statement', 'cash_flow', 'allocation')
|
|
81
|
+
),
|
|
82
|
+
constraint statement_templates_chart_needs_country check (chart_code is null or country is not null),
|
|
83
|
+
constraint statement_templates_validity check (valid_to is null or valid_to >= valid_from)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
comment on table statement_templates is
|
|
87
|
+
'Financial statements per framework, from packs/<cc>/statements.json and packs/generic/. Reference data: never copied into a company.';
|
|
88
|
+
comment on column statement_templates.country is
|
|
89
|
+
'Null on the generic framework, which reports by account type and fits any chart of any country.';
|
|
90
|
+
comment on column statement_templates.chart_code is
|
|
91
|
+
'Null means every chart of the country. Filled when a statement only makes sense on one — a nonprofit scheme on a nonprofit chart.';
|
|
92
|
+
comment on column statement_templates.kind is
|
|
93
|
+
'balance_sheet reads balances cumulative to the end of the period; income_statement and allocation read the movements of the period; cash_flow is declared and not yet produced.';
|
|
94
|
+
comment on column statement_templates.code is
|
|
95
|
+
'Immutable once published. A new version of a scheme is a new code with its own validity, the way a new VAT rate is a new tax code.';
|
|
96
|
+
|
|
97
|
+
create index if not exists statement_templates_country_idx
|
|
98
|
+
on statement_templates (country, chart_code, kind);
|
|
99
|
+
|
|
100
|
+
-- ---------------------------------------------------------------------------
|
|
101
|
+
-- The lines
|
|
102
|
+
-- ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
create table if not exists statement_line_templates (
|
|
105
|
+
statement_code text not null references statement_templates(code) on delete cascade,
|
|
106
|
+
code text not null,
|
|
107
|
+
parent_code text,
|
|
108
|
+
name text not null,
|
|
109
|
+
name_i18n jsonb not null default '{}'::jsonb,
|
|
110
|
+
sequence integer not null default 10,
|
|
111
|
+
sign smallint not null default 1,
|
|
112
|
+
is_total boolean not null default false,
|
|
113
|
+
plus_lines text[] not null default '{}'::text[],
|
|
114
|
+
minus_lines text[] not null default '{}'::text[],
|
|
115
|
+
xbrl_element text,
|
|
116
|
+
legal_reference text,
|
|
117
|
+
primary key (statement_code, code),
|
|
118
|
+
constraint statement_line_templates_sign check (sign in (1, -1)),
|
|
119
|
+
-- A line is summed from the ledger or computed from other lines, never both.
|
|
120
|
+
constraint statement_line_templates_formula_is_a_total check (
|
|
121
|
+
is_total or (plus_lines = '{}'::text[] and minus_lines = '{}'::text[])
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
comment on table statement_line_templates is
|
|
126
|
+
'The lines of a statement, in the order it prints them, and the plus/minus lists a total is computed from.';
|
|
127
|
+
comment on column statement_line_templates.sign is
|
|
128
|
+
'Multiplies the debit-minus-credit balance so the line reads the way the scheme prints it: 1 on an asset or an expense, -1 on a liability, equity or income line.';
|
|
129
|
+
comment on column statement_line_templates.plus_lines is
|
|
130
|
+
'Lines added into this total, by their code. Evaluated in `sequence` order, so a total may only name one computed before it.';
|
|
131
|
+
comment on column statement_line_templates.xbrl_element is
|
|
132
|
+
'What an XBRL filing writes for this line. The NBB CBSO taxonomy is dimensional, so the value is a fact key — a metric and its dimension members, `met:am1|bas:m2` — and not an element name. Null where nothing is verified.';
|
|
133
|
+
comment on column statement_line_templates.parent_code is
|
|
134
|
+
'The line this one details, for a renderer that indents. Structure only: a parent that is a total says so with its plus list.';
|
|
135
|
+
|
|
136
|
+
-- ---------------------------------------------------------------------------
|
|
137
|
+
-- The rules that bring accounts to a line
|
|
138
|
+
-- ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
create table if not exists statement_line_rules (
|
|
141
|
+
statement_code text not null,
|
|
142
|
+
line_code text not null,
|
|
143
|
+
sequence integer not null default 10,
|
|
144
|
+
rule_kind text not null,
|
|
145
|
+
code_from text,
|
|
146
|
+
code_to text,
|
|
147
|
+
account_type account_type,
|
|
148
|
+
balance_side text not null default 'any',
|
|
149
|
+
primary key (statement_code, line_code, sequence),
|
|
150
|
+
foreign key (statement_code, line_code)
|
|
151
|
+
references statement_line_templates (statement_code, code) on delete cascade,
|
|
152
|
+
constraint statement_line_rules_kind check (
|
|
153
|
+
rule_kind in ('account_code', 'code_range', 'code_prefix', 'account_type')
|
|
154
|
+
),
|
|
155
|
+
constraint statement_line_rules_side check (balance_side in ('debit', 'credit', 'any')),
|
|
156
|
+
constraint statement_line_rules_arguments check (
|
|
157
|
+
case rule_kind
|
|
158
|
+
when 'account_type' then account_type is not null and code_from is null and code_to is null
|
|
159
|
+
when 'code_range' then code_from is not null and code_to is not null and account_type is null
|
|
160
|
+
else code_from is not null and code_to is null and account_type is null
|
|
161
|
+
end
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
comment on table statement_line_rules is
|
|
166
|
+
'How an account of a company reaches a line. Presentation maps by range of the legal chart; choosing an account to post to by prefix stays forbidden, and is a different question.';
|
|
167
|
+
comment on column statement_line_rules.rule_kind is
|
|
168
|
+
'account_code names one code; code_range and code_prefix compare the head of the code, so `40`..`41` takes 400000 and 411000 and stops at 42; account_type is what the generic framework is made of.';
|
|
169
|
+
comment on column statement_line_rules.balance_side is
|
|
170
|
+
'Which side of the account this line takes. `any` takes it whatever it holds; `debit` and `credit` split one account between two lines — a suspense account is a receivable when it is in debit and a payable when it is in credit.';
|
|
171
|
+
comment on column statement_line_rules.sequence is
|
|
172
|
+
'Order inside the line, and part of the key. Between two rules that both catch an account, the narrower one wins first, then this.';
|
|
173
|
+
|
|
174
|
+
create index if not exists statement_line_rules_line_idx
|
|
175
|
+
on statement_line_rules (statement_code, line_code);
|
|
176
|
+
|
|
177
|
+
-- ---------------------------------------------------------------------------
|
|
178
|
+
-- statement_account_matches — which line each account of a company falls on
|
|
179
|
+
--
|
|
180
|
+
-- One place decides it, so `financial_statement()` and `unmapped_accounts()`
|
|
181
|
+
-- can never disagree about what is on the statement and what is missing from
|
|
182
|
+
-- it. Priority: an exact code beats a range or a prefix, which beat an account
|
|
183
|
+
-- type; between two ranges the narrower head wins.
|
|
184
|
+
-- ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
create or replace function statement_account_matches(
|
|
187
|
+
p_company_id uuid,
|
|
188
|
+
p_statement_code text,
|
|
189
|
+
p_from date,
|
|
190
|
+
p_to date
|
|
191
|
+
)
|
|
192
|
+
returns table (
|
|
193
|
+
account_id uuid,
|
|
194
|
+
account_code text,
|
|
195
|
+
account_name text,
|
|
196
|
+
account_type account_type,
|
|
197
|
+
balance numeric,
|
|
198
|
+
line_code text
|
|
199
|
+
)
|
|
200
|
+
language sql
|
|
201
|
+
stable
|
|
202
|
+
as $$
|
|
203
|
+
with statement as (
|
|
204
|
+
select s.code, s.kind from statement_templates s where s.code = p_statement_code
|
|
205
|
+
),
|
|
206
|
+
balances as (
|
|
207
|
+
select a.id, a.code, a.name, a.account_type,
|
|
208
|
+
round(coalesce(sum(l.balance), 0), 2) as balance
|
|
209
|
+
from accounts a
|
|
210
|
+
join entry_lines l on l.account_id = a.id
|
|
211
|
+
join entries e on e.id = l.entry_id
|
|
212
|
+
cross join statement st
|
|
213
|
+
where a.company_id = p_company_id
|
|
214
|
+
and e.state = 'posted'
|
|
215
|
+
and case when st.kind = 'balance_sheet'
|
|
216
|
+
then e.entry_date <= p_to
|
|
217
|
+
else e.entry_date between p_from and p_to
|
|
218
|
+
end
|
|
219
|
+
-- An income statement is what the period earned, and the entry that
|
|
220
|
+
-- closes a year is not that: it books the mirror image of every income
|
|
221
|
+
-- and expense account so they start the next year at nil. Left in, a
|
|
222
|
+
-- closed year reads as a result of zero. A balance sheet keeps it, and
|
|
223
|
+
-- must: that entry is what moves the result out of the income
|
|
224
|
+
-- statement and onto the line the balance sheet shows it on.
|
|
225
|
+
and (st.kind <> 'income_statement' or e.kind <> 'closing')
|
|
226
|
+
group by a.id, a.code, a.name, a.account_type
|
|
227
|
+
),
|
|
228
|
+
ranked as (
|
|
229
|
+
select b.id, b.code, b.name, b.account_type, b.balance, r.line_code,
|
|
230
|
+
row_number() over (
|
|
231
|
+
partition by b.id
|
|
232
|
+
order by case r.rule_kind
|
|
233
|
+
when 'account_code' then 1
|
|
234
|
+
when 'code_range' then 2
|
|
235
|
+
when 'code_prefix' then 2
|
|
236
|
+
else 3
|
|
237
|
+
end,
|
|
238
|
+
length(coalesce(r.code_from, '')) desc,
|
|
239
|
+
r.sequence, r.line_code
|
|
240
|
+
) as rank
|
|
241
|
+
from balances b
|
|
242
|
+
join statement_line_rules r on r.statement_code = p_statement_code
|
|
243
|
+
where case r.rule_kind
|
|
244
|
+
when 'account_code' then b.code = r.code_from
|
|
245
|
+
when 'code_prefix' then left(b.code, length(r.code_from)) = r.code_from
|
|
246
|
+
when 'code_range' then left(b.code, length(r.code_from)) >= r.code_from
|
|
247
|
+
and left(b.code, length(r.code_to)) <= r.code_to
|
|
248
|
+
else b.account_type = r.account_type
|
|
249
|
+
end
|
|
250
|
+
and case r.balance_side
|
|
251
|
+
when 'debit' then b.balance > 0
|
|
252
|
+
when 'credit' then b.balance < 0
|
|
253
|
+
else true
|
|
254
|
+
end
|
|
255
|
+
)
|
|
256
|
+
select b.id, b.code, b.name, b.account_type, b.balance, r.line_code
|
|
257
|
+
from balances b
|
|
258
|
+
left join ranked r on r.id = b.id and r.rank = 1
|
|
259
|
+
order by b.code;
|
|
260
|
+
$$;
|
|
261
|
+
|
|
262
|
+
comment on function statement_account_matches(uuid, text, date, date) is
|
|
263
|
+
'Every account of a company with a balance in the period, and the statement line it falls on — null when no rule catches it. The single decision financial_statement() and unmapped_accounts() both read.';
|
|
264
|
+
|
|
265
|
+
-- ---------------------------------------------------------------------------
|
|
266
|
+
-- evaluate_totals — the one place a plus/minus formula is worked out
|
|
267
|
+
--
|
|
268
|
+
-- `p_values` what is already known, as `{ "<key>": <amount> }`.
|
|
269
|
+
-- `p_formulas` the totals to derive, as an array of
|
|
270
|
+
-- `{ key, plus[], minus[], floor_zero, factor, sequence }`.
|
|
271
|
+
-- `p_keep_zero` whether a total that comes to nothing is in the answer. It is
|
|
272
|
+
-- always in the working set either way, so a later total that
|
|
273
|
+
-- names it reads a zero and not a gap.
|
|
274
|
+
--
|
|
275
|
+
-- **A reference is resolved the way a declaration form needs it**, which costs
|
|
276
|
+
-- a statement nothing: `08:tax` names one key exactly, a bare `08` sums every
|
|
277
|
+
-- key whose head is `08` — the French CA3 carries a base and a tax on one
|
|
278
|
+
-- line, and a statement whose keys hold no separator gets a plain equality out
|
|
279
|
+
-- of the same rule.
|
|
280
|
+
--
|
|
281
|
+
-- **The totals are evaluated in the order they depend on each other**, not the
|
|
282
|
+
-- order they are declared: a balance sheet prints a subtotal above the lines it
|
|
283
|
+
-- adds up, and a declaration names a total computed before it. A reference that
|
|
284
|
+
-- names no formula is a value — present or nil, it is known already — so a
|
|
285
|
+
-- total over ledger boxes is ready on the first pass. A pass that settles
|
|
286
|
+
-- nothing is a cycle, and says which totals are in it.
|
|
287
|
+
-- ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
create or replace function evaluate_totals(
|
|
290
|
+
p_values jsonb,
|
|
291
|
+
p_formulas jsonb,
|
|
292
|
+
p_keep_zero boolean default false
|
|
293
|
+
)
|
|
294
|
+
returns jsonb
|
|
295
|
+
language plpgsql
|
|
296
|
+
immutable
|
|
297
|
+
as $$
|
|
298
|
+
declare
|
|
299
|
+
v_values jsonb := coalesce(p_values, '{}'::jsonb);
|
|
300
|
+
v_out jsonb := '{}'::jsonb;
|
|
301
|
+
v_done jsonb := '{}'::jsonb;
|
|
302
|
+
v_all jsonb := coalesce(p_formulas, '[]'::jsonb);
|
|
303
|
+
v_left integer;
|
|
304
|
+
v_settled integer;
|
|
305
|
+
v_ready boolean;
|
|
306
|
+
v_amount numeric;
|
|
307
|
+
v_part numeric;
|
|
308
|
+
v_refs text[];
|
|
309
|
+
v_ref text;
|
|
310
|
+
v_key text;
|
|
311
|
+
v_stuck text;
|
|
312
|
+
f jsonb;
|
|
313
|
+
begin
|
|
314
|
+
select count(*) into v_left from jsonb_array_elements(v_all);
|
|
315
|
+
|
|
316
|
+
while v_left > 0 loop
|
|
317
|
+
v_settled := 0;
|
|
318
|
+
|
|
319
|
+
for f in
|
|
320
|
+
select t.x from jsonb_array_elements(v_all) with ordinality as t(x, ord)
|
|
321
|
+
order by coalesce((t.x ->> 'sequence')::integer, 0), t.ord
|
|
322
|
+
loop
|
|
323
|
+
v_key := f ->> 'key';
|
|
324
|
+
if v_done ? v_key then
|
|
325
|
+
continue;
|
|
326
|
+
end if;
|
|
327
|
+
|
|
328
|
+
select coalesce(array_agg(e.value), '{}'::text[]) into v_refs
|
|
329
|
+
from (
|
|
330
|
+
select jsonb_array_elements_text(coalesce(f -> 'plus', '[]'::jsonb)) as value
|
|
331
|
+
union all
|
|
332
|
+
select jsonb_array_elements_text(coalesce(f -> 'minus', '[]'::jsonb))
|
|
333
|
+
) as e;
|
|
334
|
+
|
|
335
|
+
-- Ready when every reference that names another formula has been worked
|
|
336
|
+
-- out already.
|
|
337
|
+
v_ready := true;
|
|
338
|
+
foreach v_ref in array v_refs loop
|
|
339
|
+
if exists (
|
|
340
|
+
select 1 from jsonb_array_elements(v_all) as g(x)
|
|
341
|
+
where (g.x ->> 'key') <> v_key
|
|
342
|
+
and not (v_done ? (g.x ->> 'key'))
|
|
343
|
+
and case when strpos(v_ref, ':') > 0
|
|
344
|
+
then (g.x ->> 'key') = replace(v_ref, ':', '|')
|
|
345
|
+
else split_part((g.x ->> 'key'), '|', 1) = v_ref
|
|
346
|
+
end
|
|
347
|
+
) then
|
|
348
|
+
v_ready := false;
|
|
349
|
+
end if;
|
|
350
|
+
end loop;
|
|
351
|
+
if not v_ready then
|
|
352
|
+
continue;
|
|
353
|
+
end if;
|
|
354
|
+
|
|
355
|
+
v_amount := 0;
|
|
356
|
+
for v_ref in
|
|
357
|
+
select jsonb_array_elements_text(coalesce(f -> 'plus', '[]'::jsonb))
|
|
358
|
+
loop
|
|
359
|
+
select coalesce(sum(e.value::numeric), 0) into v_part
|
|
360
|
+
from jsonb_each_text(v_values) as e(key, value)
|
|
361
|
+
where case when strpos(v_ref, ':') > 0
|
|
362
|
+
then e.key = replace(v_ref, ':', '|')
|
|
363
|
+
else split_part(e.key, '|', 1) = v_ref
|
|
364
|
+
end;
|
|
365
|
+
v_amount := v_amount + v_part;
|
|
366
|
+
end loop;
|
|
367
|
+
for v_ref in
|
|
368
|
+
select jsonb_array_elements_text(coalesce(f -> 'minus', '[]'::jsonb))
|
|
369
|
+
loop
|
|
370
|
+
select coalesce(sum(e.value::numeric), 0) into v_part
|
|
371
|
+
from jsonb_each_text(v_values) as e(key, value)
|
|
372
|
+
where case when strpos(v_ref, ':') > 0
|
|
373
|
+
then e.key = replace(v_ref, ':', '|')
|
|
374
|
+
else split_part(e.key, '|', 1) = v_ref
|
|
375
|
+
end;
|
|
376
|
+
v_amount := v_amount - v_part;
|
|
377
|
+
end loop;
|
|
378
|
+
|
|
379
|
+
-- The floor belongs to the pair it splits — the Belgian 71 and 72, the
|
|
380
|
+
-- French 25 and 28 — so it applies before the sign the caller reads the
|
|
381
|
+
-- line with.
|
|
382
|
+
if coalesce((f ->> 'floor_zero')::boolean, false) then
|
|
383
|
+
v_amount := greatest(v_amount, 0);
|
|
384
|
+
end if;
|
|
385
|
+
v_amount := round(v_amount * coalesce((f ->> 'factor')::numeric, 1), 2);
|
|
386
|
+
|
|
387
|
+
v_values := v_values || jsonb_build_object(v_key, v_amount);
|
|
388
|
+
v_done := v_done || jsonb_build_object(v_key, true);
|
|
389
|
+
if p_keep_zero or v_amount <> 0 then
|
|
390
|
+
v_out := v_out || jsonb_build_object(v_key, v_amount);
|
|
391
|
+
end if;
|
|
392
|
+
v_settled := v_settled + 1;
|
|
393
|
+
v_left := v_left - 1;
|
|
394
|
+
end loop;
|
|
395
|
+
|
|
396
|
+
if v_settled = 0 then
|
|
397
|
+
select string_agg(t.x ->> 'key', ', ' order by t.x ->> 'key') into v_stuck
|
|
398
|
+
from jsonb_array_elements(v_all) as t(x)
|
|
399
|
+
where not (v_done ? (t.x ->> 'key'));
|
|
400
|
+
raise exception 'formula_cycle: these totals depend on each other and on nothing else: %', v_stuck;
|
|
401
|
+
end if;
|
|
402
|
+
end loop;
|
|
403
|
+
|
|
404
|
+
return v_out;
|
|
405
|
+
end;
|
|
406
|
+
$$;
|
|
407
|
+
|
|
408
|
+
comment on function evaluate_totals(jsonb, jsonb, boolean) is
|
|
409
|
+
'Works out the plus/minus totals of a declaration form or of a financial statement, in the order they depend on each other. The one place that calculation lives: vat_return() and financial_statement() both call it.';
|
|
410
|
+
|
|
411
|
+
-- ---------------------------------------------------------------------------
|
|
412
|
+
-- financial_statement
|
|
413
|
+
-- ---------------------------------------------------------------------------
|
|
414
|
+
|
|
415
|
+
create or replace function financial_statement(
|
|
416
|
+
p_company_id uuid,
|
|
417
|
+
p_statement_code text,
|
|
418
|
+
p_from date,
|
|
419
|
+
p_to date
|
|
420
|
+
)
|
|
421
|
+
returns table (
|
|
422
|
+
line_code text,
|
|
423
|
+
parent_code text,
|
|
424
|
+
name text,
|
|
425
|
+
sequence integer,
|
|
426
|
+
is_total boolean,
|
|
427
|
+
amount numeric,
|
|
428
|
+
xbrl_element text
|
|
429
|
+
)
|
|
430
|
+
language plpgsql
|
|
431
|
+
stable
|
|
432
|
+
as $$
|
|
433
|
+
declare
|
|
434
|
+
-- line code -> amount: the lines summed from the ledger, then the totals
|
|
435
|
+
-- `evaluate_totals()` derives from them.
|
|
436
|
+
v_values jsonb := '{}'::jsonb;
|
|
437
|
+
v_formulas jsonb := '[]'::jsonb;
|
|
438
|
+
v_rows jsonb := '[]'::jsonb;
|
|
439
|
+
r record;
|
|
440
|
+
begin
|
|
441
|
+
if not exists (select 1 from companies c where c.id = p_company_id) then
|
|
442
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
443
|
+
end if;
|
|
444
|
+
if not exists (select 1 from statement_templates s where s.code = p_statement_code) then
|
|
445
|
+
raise exception 'unknown_statement: % is not a statement of this installation', p_statement_code;
|
|
446
|
+
end if;
|
|
447
|
+
|
|
448
|
+
-- 1. The lines summed from the ledger. All of them first, whatever their
|
|
449
|
+
-- place in the scheme: a scheme prints a subtotal above the lines it adds
|
|
450
|
+
-- up — every balance sheet does — and only a total that names another
|
|
451
|
+
-- total depends on an order, which the evaluator keeps.
|
|
452
|
+
select coalesce(jsonb_object_agg(l.code, round(coalesce(s.balance, 0) * l.sign, 2)), '{}'::jsonb)
|
|
453
|
+
into v_values
|
|
454
|
+
from statement_line_templates l
|
|
455
|
+
left join (
|
|
456
|
+
select m.line_code, round(sum(m.balance), 2) as balance
|
|
457
|
+
from statement_account_matches(p_company_id, p_statement_code, p_from, p_to) m
|
|
458
|
+
where m.line_code is not null
|
|
459
|
+
group by m.line_code
|
|
460
|
+
) s on s.line_code = l.code
|
|
461
|
+
where l.statement_code = p_statement_code
|
|
462
|
+
and not l.is_total;
|
|
463
|
+
|
|
464
|
+
-- 2. The totals, worked out by the one evaluator the declaration forms use.
|
|
465
|
+
-- A statement prints its whole frame, so every total comes back, and the
|
|
466
|
+
-- sign it reads the line with is the factor.
|
|
467
|
+
select coalesce(jsonb_agg(jsonb_build_object(
|
|
468
|
+
'key', l.code, 'plus', to_jsonb(l.plus_lines), 'minus', to_jsonb(l.minus_lines),
|
|
469
|
+
'factor', l.sign, 'sequence', l.sequence
|
|
470
|
+
) order by l.sequence, l.code), '[]'::jsonb)
|
|
471
|
+
into v_formulas
|
|
472
|
+
from statement_line_templates l
|
|
473
|
+
where l.statement_code = p_statement_code
|
|
474
|
+
and l.is_total;
|
|
475
|
+
|
|
476
|
+
v_values := v_values || evaluate_totals(v_values, v_formulas, true);
|
|
477
|
+
|
|
478
|
+
-- 3. The frame, in the order it is printed. Every line is returned, nil
|
|
479
|
+
-- included: a statement is read top to bottom and tied out, where a
|
|
480
|
+
-- declaration prints what it has — which is why `vat_return()` drops a
|
|
481
|
+
-- nil box and this does not.
|
|
482
|
+
for r in
|
|
483
|
+
select l.code, l.parent_code, l.name, l.sequence, l.is_total, l.xbrl_element
|
|
484
|
+
from statement_line_templates l
|
|
485
|
+
where l.statement_code = p_statement_code
|
|
486
|
+
order by l.sequence, l.code
|
|
487
|
+
loop
|
|
488
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
489
|
+
'line_code', r.code, 'parent_code', r.parent_code, 'name', r.name,
|
|
490
|
+
'sequence', r.sequence, 'is_total', r.is_total,
|
|
491
|
+
'amount', coalesce((v_values ->> r.code)::numeric, 0),
|
|
492
|
+
'xbrl_element', r.xbrl_element));
|
|
493
|
+
end loop;
|
|
494
|
+
|
|
495
|
+
return query
|
|
496
|
+
select (x ->> 'line_code')::text,
|
|
497
|
+
(x ->> 'parent_code')::text,
|
|
498
|
+
(x ->> 'name')::text,
|
|
499
|
+
(x ->> 'sequence')::integer,
|
|
500
|
+
(x ->> 'is_total')::boolean,
|
|
501
|
+
(x ->> 'amount')::numeric,
|
|
502
|
+
(x ->> 'xbrl_element')::text
|
|
503
|
+
from jsonb_array_elements(v_rows) as x;
|
|
504
|
+
end;
|
|
505
|
+
$$;
|
|
506
|
+
|
|
507
|
+
comment on function financial_statement(uuid, text, date, date) is
|
|
508
|
+
'One financial statement of a company for a period: each line summed from the accounts its rules catch, then the totals evaluated in the order the scheme declares them. No country rule lives in this function.';
|
|
509
|
+
|
|
510
|
+
-- ---------------------------------------------------------------------------
|
|
511
|
+
-- unmapped_accounts — what the statement would silently leave out
|
|
512
|
+
-- ---------------------------------------------------------------------------
|
|
513
|
+
|
|
514
|
+
create or replace function unmapped_accounts(
|
|
515
|
+
p_company_id uuid,
|
|
516
|
+
p_statement_code text,
|
|
517
|
+
p_from date,
|
|
518
|
+
p_to date
|
|
519
|
+
)
|
|
520
|
+
returns table (
|
|
521
|
+
account_id uuid,
|
|
522
|
+
account_code text,
|
|
523
|
+
account_name text,
|
|
524
|
+
account_type account_type,
|
|
525
|
+
balance numeric
|
|
526
|
+
)
|
|
527
|
+
language sql
|
|
528
|
+
stable
|
|
529
|
+
as $$
|
|
530
|
+
select m.account_id, m.account_code, m.account_name, m.account_type, m.balance
|
|
531
|
+
from statement_account_matches(p_company_id, p_statement_code, p_from, p_to) m
|
|
532
|
+
join accounts a on a.id = m.account_id
|
|
533
|
+
cross join (select s.kind from statement_templates s where s.code = p_statement_code) st
|
|
534
|
+
where m.line_code is null
|
|
535
|
+
and m.balance <> 0
|
|
536
|
+
-- Only what this statement is answerable for. A revenue account is not
|
|
537
|
+
-- missing from a balance sheet, and a statement that claims no
|
|
538
|
+
-- completeness — an allocation section, a cash flow — reports nothing.
|
|
539
|
+
and case st.kind
|
|
540
|
+
when 'balance_sheet' then a.internal_group in ('asset', 'liability', 'equity')
|
|
541
|
+
when 'income_statement' then a.internal_group in ('income', 'expense')
|
|
542
|
+
else false
|
|
543
|
+
end
|
|
544
|
+
order by m.account_code;
|
|
545
|
+
$$;
|
|
546
|
+
|
|
547
|
+
comment on function unmapped_accounts(uuid, text, date, date) is
|
|
548
|
+
'Accounts this statement is answerable for that carry a balance and that no rule of it catches. Empty is what makes the statement tie out; a row is an account somebody opened outside the pack.';
|
|
549
|
+
|
|
550
|
+
-- ---------------------------------------------------------------------------
|
|
551
|
+
-- available_statements — what a company can ask for
|
|
552
|
+
-- ---------------------------------------------------------------------------
|
|
553
|
+
|
|
554
|
+
create or replace function available_statements(p_company_id uuid, p_at date default null)
|
|
555
|
+
returns table (
|
|
556
|
+
code text,
|
|
557
|
+
name text,
|
|
558
|
+
kind text,
|
|
559
|
+
framework text,
|
|
560
|
+
country char(2),
|
|
561
|
+
chart_code text,
|
|
562
|
+
is_default boolean
|
|
563
|
+
)
|
|
564
|
+
language sql
|
|
565
|
+
stable
|
|
566
|
+
as $$
|
|
567
|
+
with company as (
|
|
568
|
+
select c.id, c.fiscal_country,
|
|
569
|
+
coalesce(
|
|
570
|
+
(select p.chart_code from company_packs p
|
|
571
|
+
where p.company_id = c.id and p.country = c.fiscal_country),
|
|
572
|
+
(select ch.code from chart_templates ch
|
|
573
|
+
where ch.country = c.fiscal_country and ch.is_default)
|
|
574
|
+
) as chart_code
|
|
575
|
+
from companies c where c.id = p_company_id
|
|
576
|
+
)
|
|
577
|
+
select s.code, s.name, s.kind, s.framework, s.country, s.chart_code,
|
|
578
|
+
coalesce(s.code = any (ch.statements), false)
|
|
579
|
+
from statement_templates s
|
|
580
|
+
cross join company co
|
|
581
|
+
left join chart_templates ch
|
|
582
|
+
on ch.country = co.fiscal_country and ch.code = co.chart_code
|
|
583
|
+
where (s.country is null or s.country = co.fiscal_country)
|
|
584
|
+
and (s.chart_code is null or s.chart_code = co.chart_code)
|
|
585
|
+
and s.valid_from <= coalesce(p_at, current_date)
|
|
586
|
+
and (s.valid_to is null or s.valid_to >= coalesce(p_at, current_date))
|
|
587
|
+
order by s.country nulls last, s.kind, s.code;
|
|
588
|
+
$$;
|
|
589
|
+
|
|
590
|
+
comment on function available_statements(uuid, date) is
|
|
591
|
+
'Statements a company may ask for: those of its country and chart, plus the generic framework. `is_default` marks the ones its chart declares.';
|
|
592
|
+
|
|
593
|
+
-- ---------------------------------------------------------------------------
|
|
594
|
+
-- Row level security
|
|
595
|
+
--
|
|
596
|
+
-- A scheme is reference data, like a chart of accounts and a declaration
|
|
597
|
+
-- form: any signed-in user may read it, and nobody writes it but the
|
|
598
|
+
-- generated seed, which runs as the owner. No write policy at all.
|
|
599
|
+
-- ---------------------------------------------------------------------------
|
|
600
|
+
|
|
601
|
+
alter table statement_templates enable row level security;
|
|
602
|
+
alter table statement_line_templates enable row level security;
|
|
603
|
+
alter table statement_line_rules enable row level security;
|
|
604
|
+
|
|
605
|
+
create policy statement_templates_select on statement_templates
|
|
606
|
+
for select using (auth.uid() is not null);
|
|
607
|
+
create policy statement_line_templates_select on statement_line_templates
|
|
608
|
+
for select using (auth.uid() is not null);
|
|
609
|
+
create policy statement_line_rules_select on statement_line_rules
|
|
610
|
+
for select using (auth.uid() is not null);
|
|
611
|
+
|
|
612
|
+
comment on policy statement_templates_select on statement_templates is
|
|
613
|
+
'Reference data, readable by any signed-in user. No write policy: a statement comes from a pack.';
|
|
614
|
+
|
|
615
|
+
-- ---------------------------------------------------------------------------
|
|
616
|
+
-- One comment corrected
|
|
617
|
+
--
|
|
618
|
+
-- `20260912074712` said `statement_hint` would be "read by
|
|
619
|
+
-- financial_statement() (P0-4)". It is not: the rules of a statement decide
|
|
620
|
+
-- which line an account falls on, which is a property of the scheme and not of
|
|
621
|
+
-- the account, and a hint on the account would be a second place to keep in
|
|
622
|
+
-- step. The column stays — a chart may carry the intent, and a contributor
|
|
623
|
+
-- writing a pack has somewhere to put it — and its comment now says so.
|
|
624
|
+
-- ---------------------------------------------------------------------------
|
|
625
|
+
|
|
626
|
+
comment on column account_templates.statement_hint is
|
|
627
|
+
'Free note: the statement line this account is meant for. Read by nothing — the rules of a statement decide — and kept so a chart can carry the intent.';
|
|
628
|
+
comment on column accounts.statement_hint is
|
|
629
|
+
'Free note: the statement line this account is meant for. Read by nothing — the rules of a statement decide — and kept so a chart can carry the intent.';
|
|
630
|
+
|
|
631
|
+
-- Rule 6 of supabase/migrations/README.md.
|
|
632
|
+
revoke execute on all functions in schema public from public;
|