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,1004 @@
|
|
|
1
|
+
-- Ekwo OS — one posting, printed in as many boxes as the form asks for.
|
|
2
|
+
--
|
|
3
|
+
-- A tax carries one `base` posting per kind of document. That rule is right
|
|
4
|
+
-- and it stays: a taxable amount has one definition, and every further place
|
|
5
|
+
-- the form shows it is a `total` adding the boxes the postings already wrote.
|
|
6
|
+
-- What the rule could not express is a form that prints one amount in boxes
|
|
7
|
+
-- **no total can derive from one another**, and two of the packs in this
|
|
8
|
+
-- repository meet it:
|
|
9
|
+
--
|
|
10
|
+
-- Form KMD (Estonia) reports an intra-Community acquisition of goods in box
|
|
11
|
+
-- 6.1, in box 6 around it and in box 1 around that. Box 6 is not the sum
|
|
12
|
+
-- of the boxes printed under it — the form prints only 6.1, and the rest
|
|
13
|
+
-- of 6 is services received, which the form never prints on its own — so
|
|
14
|
+
-- the pack had to invent a leaf box for the part nobody prints and total
|
|
15
|
+
-- the two. Six such boxes on a form of thirty.
|
|
16
|
+
-- The VAT Return (United Kingdom) asks for the value of a service received
|
|
17
|
+
-- from a supplier established abroad in box 6, which is outputs, and in
|
|
18
|
+
-- box 7, which is inputs. Neither box contains the other, and the pack
|
|
19
|
+
-- had to invent three leaf boxes on a form of nine.
|
|
20
|
+
--
|
|
21
|
+
-- Both are the same gap, recorded in `docs/international.md` under two names,
|
|
22
|
+
-- and both are closed here: a posting names **a list** of boxes, and
|
|
23
|
+
-- `vat_return()` sums it into each one.
|
|
24
|
+
--
|
|
25
|
+
-- **The shape is an array, not a table of its own.** `declaration_boxes
|
|
26
|
+
-- text[]` beside `declaration_box`, on the template and on the company's copy,
|
|
27
|
+
-- with the first of the list equal to the column. Three reasons, and
|
|
28
|
+
-- `docs/decisions.md` carries them at length: the return reads the list with
|
|
29
|
+
-- one `unnest` where a junction is a join and a second order to keep; a
|
|
30
|
+
-- posting has no identity of its own — `pack upgrade` already replaces every
|
|
31
|
+
-- posting of a tax as one fact — so the rows of a junction would carry a key
|
|
32
|
+
-- nothing names; and a junction is a table, which is a policy, a grant and an
|
|
33
|
+
-- inventory entry for a list that is almost always one element long.
|
|
34
|
+
--
|
|
35
|
+
-- **`declaration_box` stays and stays first.** It is what a posting is known
|
|
36
|
+
-- by, what `vat_return()` reads a line back on, and what every reader that
|
|
37
|
+
-- knows nothing of this change still sees. A check constraint holds the two
|
|
38
|
+
-- in step, and a trigger fills the list from the column for a writer that
|
|
39
|
+
-- knows only the column — so nothing that writes a posting today has to
|
|
40
|
+
-- change, and a pack that names one box compiles to exactly what it did.
|
|
41
|
+
--
|
|
42
|
+
-- **What does not change**: `evaluate_totals()`, which never knew a box from
|
|
43
|
+
-- a statement line; `post_document()`, because a posting still writes one
|
|
44
|
+
-- ledger line and that line still carries one box — the expansion belongs to
|
|
45
|
+
-- the return, not to the ledger; and `ec_sales_list()`, which reads treatments
|
|
46
|
+
-- and never a declaration box.
|
|
47
|
+
|
|
48
|
+
-- ---------------------------------------------------------------------------
|
|
49
|
+
-- The column, on the template and on the company's copy
|
|
50
|
+
-- ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
alter table tax_posting_templates
|
|
53
|
+
add column if not exists declaration_boxes text[];
|
|
54
|
+
|
|
55
|
+
alter table tax_postings
|
|
56
|
+
add column if not exists declaration_boxes text[];
|
|
57
|
+
|
|
58
|
+
update tax_posting_templates
|
|
59
|
+
set declaration_boxes = array[declaration_box]
|
|
60
|
+
where declaration_box is not null
|
|
61
|
+
and declaration_boxes is null;
|
|
62
|
+
|
|
63
|
+
update tax_postings
|
|
64
|
+
set declaration_boxes = array[declaration_box]
|
|
65
|
+
where declaration_box is not null
|
|
66
|
+
and declaration_boxes is null;
|
|
67
|
+
|
|
68
|
+
comment on column tax_posting_templates.declaration_boxes is
|
|
69
|
+
'Every box this one amount is printed in, from packs/<cc>/taxes.json. Almost always the single box declaration_box names, which is the first of the list; several where the form prints one figure in boxes that are not sums of one another. A box that is a sum stays a total and is never named here.';
|
|
70
|
+
|
|
71
|
+
comment on column tax_postings.declaration_boxes is
|
|
72
|
+
'Every box this one amount is printed in, copied from the template. The first is declaration_box, which is what the posting is known by; vat_return() sums the line into each box of the list.';
|
|
73
|
+
|
|
74
|
+
-- The two are one fact written twice, so they are held in step. The list may
|
|
75
|
+
-- not be empty, it starts at the box the posting is known by, and it is null
|
|
76
|
+
-- exactly where that box is — a posting that reports nowhere reports nowhere
|
|
77
|
+
-- in every column.
|
|
78
|
+
--
|
|
79
|
+
-- Duplicates inside the list are not refused here. `array[..] = array[..]`
|
|
80
|
+
-- cannot say it without a subquery, which a check constraint may not hold,
|
|
81
|
+
-- and a duplicate changes no figure — `unnest` would sum the same line into
|
|
82
|
+
-- the same box twice, which is a defect in the pack rather than in a row.
|
|
83
|
+
-- `ekwo pack check` refuses it at the one moment it is worth saying, which is
|
|
84
|
+
-- when somebody is writing the pack.
|
|
85
|
+
|
|
86
|
+
alter table tax_posting_templates
|
|
87
|
+
add constraint tax_posting_templates_boxes_start_at_the_box check (
|
|
88
|
+
case when declaration_box is null
|
|
89
|
+
then declaration_boxes is null
|
|
90
|
+
else declaration_boxes is not null
|
|
91
|
+
and cardinality(declaration_boxes) >= 1
|
|
92
|
+
and declaration_boxes[1] = declaration_box
|
|
93
|
+
end
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
alter table tax_postings
|
|
97
|
+
add constraint tax_postings_boxes_start_at_the_box check (
|
|
98
|
+
case when declaration_box is null
|
|
99
|
+
then declaration_boxes is null
|
|
100
|
+
else declaration_boxes is not null
|
|
101
|
+
and cardinality(declaration_boxes) >= 1
|
|
102
|
+
and declaration_boxes[1] = declaration_box
|
|
103
|
+
end
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
-- ---------------------------------------------------------------------------
|
|
107
|
+
-- A writer that knows one column is handed the other
|
|
108
|
+
-- ---------------------------------------------------------------------------
|
|
109
|
+
--
|
|
110
|
+
-- Everything that wrote a posting before this migration names `declaration_box`
|
|
111
|
+
-- and nothing else: the seeds of an older release, a company's own tax typed
|
|
112
|
+
-- into the application, a test fixture. Rather than make every one of them
|
|
113
|
+
-- learn a second column, the row fills the half it was not given.
|
|
114
|
+
--
|
|
115
|
+
-- "Not given" is read from what moved, never from what is null, and that is
|
|
116
|
+
-- the whole subtlety here. A writer that clears the box means the posting
|
|
117
|
+
-- reports nowhere, and a trigger that read `declaration_box is null` as "say
|
|
118
|
+
-- nothing" would put the box straight back from the list — which is a write
|
|
119
|
+
-- that does not take, the worst kind. So: the box moved and the list did not,
|
|
120
|
+
-- the list follows; the list moved and the box did not, the box follows; both
|
|
121
|
+
-- moved, the writer knows what it is doing and the check constraint judges it.
|
|
122
|
+
|
|
123
|
+
create or replace function tax_posting_boxes_agree()
|
|
124
|
+
returns trigger
|
|
125
|
+
language plpgsql
|
|
126
|
+
as $$
|
|
127
|
+
declare
|
|
128
|
+
-- An insert is an update from a row where both were null, which is the same
|
|
129
|
+
-- rule written once instead of twice.
|
|
130
|
+
v_was_box text := case when tg_op = 'UPDATE' then old.declaration_box else null end;
|
|
131
|
+
v_was_boxes text[] := case when tg_op = 'UPDATE' then old.declaration_boxes else null end;
|
|
132
|
+
v_box_moved boolean := new.declaration_box is distinct from v_was_box;
|
|
133
|
+
v_list_moved boolean := new.declaration_boxes is distinct from v_was_boxes;
|
|
134
|
+
begin
|
|
135
|
+
if v_box_moved and not v_list_moved then
|
|
136
|
+
new.declaration_boxes := case when new.declaration_box is null
|
|
137
|
+
then null
|
|
138
|
+
else array[new.declaration_box] end;
|
|
139
|
+
elsif v_list_moved and not v_box_moved then
|
|
140
|
+
new.declaration_box := case when new.declaration_boxes is null
|
|
141
|
+
or cardinality(new.declaration_boxes) = 0
|
|
142
|
+
then null
|
|
143
|
+
else new.declaration_boxes[1] end;
|
|
144
|
+
end if;
|
|
145
|
+
return new;
|
|
146
|
+
end;
|
|
147
|
+
$$;
|
|
148
|
+
|
|
149
|
+
comment on function tax_posting_boxes_agree() is
|
|
150
|
+
'Keeps declaration_box and declaration_boxes in step on a tax posting: a writer that moves one is handed the other. A writer that moves both is left alone and judged by the check constraint, and a writer that clears the box clears the list with it.';
|
|
151
|
+
|
|
152
|
+
create trigger tax_posting_templates_boxes_agree
|
|
153
|
+
before insert or update on tax_posting_templates
|
|
154
|
+
for each row execute function tax_posting_boxes_agree();
|
|
155
|
+
|
|
156
|
+
create trigger tax_postings_boxes_agree
|
|
157
|
+
before insert or update on tax_postings
|
|
158
|
+
for each row execute function tax_posting_boxes_agree();
|
|
159
|
+
|
|
160
|
+
-- A trigger function runs as the statement that fired it and is called by
|
|
161
|
+
-- nobody directly, so it is open to nobody.
|
|
162
|
+
revoke execute on function tax_posting_boxes_agree() from public, anon;
|
|
163
|
+
|
|
164
|
+
-- ---------------------------------------------------------------------------
|
|
165
|
+
-- Installing a pack copies the list
|
|
166
|
+
-- ---------------------------------------------------------------------------
|
|
167
|
+
--
|
|
168
|
+
-- Replaced whole rather than patched, because a function is replaced whole in
|
|
169
|
+
-- PostgreSQL. Everything but the insert into `tax_postings` is `20260914143915`
|
|
170
|
+
-- unchanged.
|
|
171
|
+
|
|
172
|
+
create or replace function install_country_template(
|
|
173
|
+
p_company_id uuid,
|
|
174
|
+
p_country char(2),
|
|
175
|
+
p_language char(2) default null,
|
|
176
|
+
p_chart_code text default null
|
|
177
|
+
)
|
|
178
|
+
returns void
|
|
179
|
+
language plpgsql
|
|
180
|
+
as $$
|
|
181
|
+
declare
|
|
182
|
+
v_defaults country_defaults%rowtype;
|
|
183
|
+
r record;
|
|
184
|
+
v_tax_id uuid;
|
|
185
|
+
v_language char(2);
|
|
186
|
+
v_version text;
|
|
187
|
+
v_chart text;
|
|
188
|
+
v_charts text;
|
|
189
|
+
begin
|
|
190
|
+
if not exists (select 1 from companies where id = p_company_id) then
|
|
191
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
192
|
+
end if;
|
|
193
|
+
|
|
194
|
+
-- Which chart. The caller names one, or the pack's default stands. A named
|
|
195
|
+
-- chart that does not exist is an error that lists what does: guessing here
|
|
196
|
+
-- would install the wrong plan of accounts and nobody would notice for a
|
|
197
|
+
-- year.
|
|
198
|
+
if p_chart_code is null then
|
|
199
|
+
select c.code into v_chart
|
|
200
|
+
from chart_templates c
|
|
201
|
+
where c.country = p_country and c.is_default;
|
|
202
|
+
if v_chart is null then
|
|
203
|
+
-- No pack for this country, or a pack that names no default chart.
|
|
204
|
+
raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
|
|
205
|
+
end if;
|
|
206
|
+
else
|
|
207
|
+
select c.code into v_chart
|
|
208
|
+
from chart_templates c
|
|
209
|
+
where c.country = p_country and c.code = p_chart_code;
|
|
210
|
+
if v_chart is null then
|
|
211
|
+
select string_agg(c.code, ', ' order by c.code) into v_charts
|
|
212
|
+
from chart_templates c where c.country = p_country;
|
|
213
|
+
raise exception 'unknown_chart: % has no chart %. It has: %',
|
|
214
|
+
p_country, p_chart_code, coalesce(v_charts, 'none');
|
|
215
|
+
end if;
|
|
216
|
+
end if;
|
|
217
|
+
|
|
218
|
+
if not exists (
|
|
219
|
+
select 1 from account_templates
|
|
220
|
+
where country = p_country and chart_code = v_chart
|
|
221
|
+
) then
|
|
222
|
+
raise exception 'unknown_country_template: chart % of % holds no account', v_chart, p_country;
|
|
223
|
+
end if;
|
|
224
|
+
|
|
225
|
+
-- The language of the copy: what the caller asked for, else what the
|
|
226
|
+
-- company keeps its books in.
|
|
227
|
+
select coalesce(p_language, c.language) into v_language
|
|
228
|
+
from companies c where c.id = p_company_id;
|
|
229
|
+
|
|
230
|
+
-- 1. Accounts, without the hierarchy.
|
|
231
|
+
insert into accounts (company_id, code, name, name_i18n, statement_hint,
|
|
232
|
+
account_type, reconcilable)
|
|
233
|
+
select p_company_id, t.code,
|
|
234
|
+
label_for(t.name, t.name_i18n, array[v_language]),
|
|
235
|
+
t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
|
|
236
|
+
from account_templates t
|
|
237
|
+
where t.country = p_country
|
|
238
|
+
and t.chart_code = v_chart
|
|
239
|
+
on conflict (company_id, code) do nothing;
|
|
240
|
+
|
|
241
|
+
-- 2. Hierarchy, now that every code exists.
|
|
242
|
+
update accounts a
|
|
243
|
+
set parent_id = p.id
|
|
244
|
+
from account_templates t
|
|
245
|
+
join accounts p on p.company_id = p_company_id and p.code = t.parent_code
|
|
246
|
+
where a.company_id = p_company_id
|
|
247
|
+
and a.code = t.code
|
|
248
|
+
and t.country = p_country
|
|
249
|
+
and t.chart_code = v_chart
|
|
250
|
+
and t.parent_code is not null
|
|
251
|
+
and a.parent_id is null;
|
|
252
|
+
|
|
253
|
+
-- 3. Journals. Common to every chart of the country.
|
|
254
|
+
insert into journals (company_id, code, name, name_i18n, journal_type)
|
|
255
|
+
select p_company_id, t.code,
|
|
256
|
+
label_for(t.name, t.name_i18n, array[v_language]),
|
|
257
|
+
t.name_i18n, t.journal_type
|
|
258
|
+
from journal_templates t
|
|
259
|
+
where t.country = p_country
|
|
260
|
+
on conflict (company_id, code) do nothing;
|
|
261
|
+
|
|
262
|
+
-- 4. Taxes and their postings. Also common: a chart changes how a company
|
|
263
|
+
-- presents itself, not what it owes.
|
|
264
|
+
for r in
|
|
265
|
+
select * from tax_templates where country = p_country order by sequence, code
|
|
266
|
+
loop
|
|
267
|
+
v_tax_id := null;
|
|
268
|
+
insert into taxes (company_id, code, name, name_i18n, description, amount_type, amount,
|
|
269
|
+
applies_to, treatment, country, valid_from, valid_to,
|
|
270
|
+
legal_reference, vat_category, exemption_code, sequence,
|
|
271
|
+
tax_kind, recoverable, jurisdiction, price_include,
|
|
272
|
+
cash_basis, cash_basis_transition_account_id)
|
|
273
|
+
values (p_company_id, r.code,
|
|
274
|
+
label_for(r.name, r.name_i18n, array[v_language]),
|
|
275
|
+
r.name_i18n, r.description, r.amount_type, r.amount,
|
|
276
|
+
r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
|
|
277
|
+
r.legal_reference, r.vat_category, r.exemption_code, r.sequence,
|
|
278
|
+
r.tax_kind, r.recoverable, r.jurisdiction, r.price_include,
|
|
279
|
+
r.cash_basis, account_id_by_code(p_company_id, r.cash_basis_transition_account_code))
|
|
280
|
+
on conflict (company_id, code) do nothing
|
|
281
|
+
returning id into v_tax_id;
|
|
282
|
+
|
|
283
|
+
if v_tax_id is null then
|
|
284
|
+
continue;
|
|
285
|
+
end if;
|
|
286
|
+
|
|
287
|
+
insert into tax_postings (tax_id, company_id, document_kind, posting_type,
|
|
288
|
+
factor_percent, account_id, declaration_box,
|
|
289
|
+
declaration_boxes, box_factor_percent, report_code,
|
|
290
|
+
sequence)
|
|
291
|
+
select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
|
|
292
|
+
tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
|
|
293
|
+
tp.declaration_box, tp.declaration_boxes, tp.box_factor_percent,
|
|
294
|
+
tp.report_code, tp.sequence
|
|
295
|
+
from tax_posting_templates tp
|
|
296
|
+
where tp.tax_template_id = r.id
|
|
297
|
+
order by tp.sequence;
|
|
298
|
+
end loop;
|
|
299
|
+
|
|
300
|
+
-- 5. Roles.
|
|
301
|
+
select * into v_defaults from country_defaults where country = p_country;
|
|
302
|
+
if found then
|
|
303
|
+
update companies c
|
|
304
|
+
set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
|
|
305
|
+
payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
|
|
306
|
+
suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
|
|
307
|
+
rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
|
|
308
|
+
retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
|
|
309
|
+
default_sales_account_id = coalesce(c.default_sales_account_id, account_id_by_code(p_company_id, v_defaults.sales_account_code)),
|
|
310
|
+
default_purchase_account_id = coalesce(c.default_purchase_account_id, account_id_by_code(p_company_id, v_defaults.purchase_account_code)),
|
|
311
|
+
sales_journal_id = coalesce(c.sales_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.sales_journal_code)),
|
|
312
|
+
purchase_journal_id = coalesce(c.purchase_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.purchase_journal_code)),
|
|
313
|
+
miscellaneous_journal_id = coalesce(c.miscellaneous_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.misc_journal_code))
|
|
314
|
+
where c.id = p_company_id;
|
|
315
|
+
|
|
316
|
+
-- 6. The financial journals point at their account.
|
|
317
|
+
update journals j
|
|
318
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
|
|
319
|
+
where j.company_id = p_company_id
|
|
320
|
+
and j.journal_type = 'bank'
|
|
321
|
+
and j.default_account_id is null
|
|
322
|
+
and v_defaults.bank_account_code is not null;
|
|
323
|
+
|
|
324
|
+
update journals j
|
|
325
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
|
|
326
|
+
where j.company_id = p_company_id
|
|
327
|
+
and j.journal_type = 'cash'
|
|
328
|
+
and j.default_account_id is null
|
|
329
|
+
and v_defaults.cash_account_code is not null;
|
|
330
|
+
end if;
|
|
331
|
+
|
|
332
|
+
-- 7. What was copied, from which version, and on which chart.
|
|
333
|
+
v_version := coalesce((select version from country_packs where country = p_country), '1.0.0');
|
|
334
|
+
|
|
335
|
+
insert into company_packs (company_id, country, version, chart_code)
|
|
336
|
+
values (p_company_id, p_country, v_version, v_chart)
|
|
337
|
+
on conflict (company_id, country) do update
|
|
338
|
+
set version = excluded.version,
|
|
339
|
+
chart_code = excluded.chart_code,
|
|
340
|
+
upgraded_at = now()
|
|
341
|
+
where company_packs.version is distinct from excluded.version
|
|
342
|
+
or company_packs.chart_code is distinct from excluded.chart_code;
|
|
343
|
+
|
|
344
|
+
-- 8. The working chart. Everything wired above is pinned, so a company opens
|
|
345
|
+
-- on the accounts its country model actually points at rather than on the
|
|
346
|
+
-- whole transcription.
|
|
347
|
+
perform pin_referenced_accounts(p_company_id);
|
|
348
|
+
end;
|
|
349
|
+
$$;
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
comment on function install_country_template(uuid, char, char, text) is
|
|
353
|
+
'Copies one chart of a country pack into a company in one language, with the country''s journals and taxes, wires the default roles, pins the accounts it wired, and records the pack version and the chart in company_packs. A tax posting is copied with every declaration box it prints in.';
|
|
354
|
+
|
|
355
|
+
revoke execute on function install_country_template(uuid, char, char, text) from public, anon;
|
|
356
|
+
grant execute on function install_country_template(uuid, char, char, text) to authenticated, service_role;
|
|
357
|
+
|
|
358
|
+
-- ---------------------------------------------------------------------------
|
|
359
|
+
-- The upgrade compares the list and carries it
|
|
360
|
+
-- ---------------------------------------------------------------------------
|
|
361
|
+
--
|
|
362
|
+
-- A tax that starts printing its base in a second box is a difference like any
|
|
363
|
+
-- other, and one a company has to be told about: without the list in the diff,
|
|
364
|
+
-- an upgrade would report "nothing to do" and the return would stay short by a
|
|
365
|
+
-- box. The postings of a tax are compared as one fact, so the list joins the
|
|
366
|
+
-- object the diff already builds. Everything else in both functions is
|
|
367
|
+
-- `20260914111907` and `20260914152840` unchanged.
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
create or replace function pack_upgrade_diff(
|
|
371
|
+
p_company_id uuid,
|
|
372
|
+
p_country char(2) default null
|
|
373
|
+
)
|
|
374
|
+
returns table (
|
|
375
|
+
object text,
|
|
376
|
+
key text,
|
|
377
|
+
change text,
|
|
378
|
+
rule pack_change_rule,
|
|
379
|
+
detail jsonb
|
|
380
|
+
)
|
|
381
|
+
language plpgsql
|
|
382
|
+
stable
|
|
383
|
+
as $$
|
|
384
|
+
declare
|
|
385
|
+
v_country char(2);
|
|
386
|
+
v_chart text;
|
|
387
|
+
begin
|
|
388
|
+
select coalesce(p_country, c.country) into v_country
|
|
389
|
+
from companies c where c.id = p_company_id;
|
|
390
|
+
if v_country is null then
|
|
391
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
392
|
+
end if;
|
|
393
|
+
|
|
394
|
+
select cp.chart_code into v_chart
|
|
395
|
+
from company_packs cp
|
|
396
|
+
where cp.company_id = p_company_id and cp.country = v_country;
|
|
397
|
+
if v_chart is null then
|
|
398
|
+
raise exception 'no_pack_installed: company % holds no % pack, so there is nothing to upgrade from', p_company_id, v_country;
|
|
399
|
+
end if;
|
|
400
|
+
|
|
401
|
+
-- Accounts -----------------------------------------------------------------
|
|
402
|
+
return query
|
|
403
|
+
select 'account', t.code, 'missing', 'addition'::pack_change_rule,
|
|
404
|
+
jsonb_build_object('pack', jsonb_build_object('name', t.name, 'account_type', t.account_type))
|
|
405
|
+
from account_templates t
|
|
406
|
+
where t.country = v_country and t.chart_code = v_chart
|
|
407
|
+
and not exists (select 1 from accounts a where a.company_id = p_company_id and a.code = t.code);
|
|
408
|
+
|
|
409
|
+
return query
|
|
410
|
+
select 'account', a.code, 'differs', 'review'::pack_change_rule,
|
|
411
|
+
jsonb_build_object(
|
|
412
|
+
'pack', jsonb_build_object('name', t.name, 'account_type', t.account_type),
|
|
413
|
+
'company', jsonb_build_object('name', a.name, 'account_type', a.account_type))
|
|
414
|
+
from accounts a
|
|
415
|
+
join account_templates t
|
|
416
|
+
on t.country = v_country and t.chart_code = v_chart and t.code = a.code
|
|
417
|
+
where a.company_id = p_company_id
|
|
418
|
+
and (label_for(t.name, t.name_i18n, preferred_languages(p_company_id)) is distinct from a.name
|
|
419
|
+
or t.account_type is distinct from a.account_type);
|
|
420
|
+
|
|
421
|
+
return query
|
|
422
|
+
select 'account', a.code, 'company_only', 'review'::pack_change_rule,
|
|
423
|
+
jsonb_build_object('company', jsonb_build_object('name', a.name, 'account_type', a.account_type))
|
|
424
|
+
from accounts a
|
|
425
|
+
where a.company_id = p_company_id
|
|
426
|
+
and not exists (select 1 from account_templates t
|
|
427
|
+
where t.country = v_country and t.chart_code = v_chart and t.code = a.code);
|
|
428
|
+
|
|
429
|
+
-- Journals -----------------------------------------------------------------
|
|
430
|
+
return query
|
|
431
|
+
select 'journal', t.code, 'missing', 'addition'::pack_change_rule,
|
|
432
|
+
jsonb_build_object('pack', jsonb_build_object('name', t.name, 'journal_type', t.journal_type))
|
|
433
|
+
from journal_templates t
|
|
434
|
+
where t.country = v_country
|
|
435
|
+
and not exists (select 1 from journals j where j.company_id = p_company_id and j.code = t.code);
|
|
436
|
+
|
|
437
|
+
return query
|
|
438
|
+
select 'journal', j.code, 'differs', 'review'::pack_change_rule,
|
|
439
|
+
jsonb_build_object(
|
|
440
|
+
'pack', jsonb_build_object('name', t.name, 'journal_type', t.journal_type),
|
|
441
|
+
'company', jsonb_build_object('name', j.name, 'journal_type', j.journal_type))
|
|
442
|
+
from journals j
|
|
443
|
+
join journal_templates t on t.country = v_country and t.code = j.code
|
|
444
|
+
where j.company_id = p_company_id
|
|
445
|
+
and (t.name is distinct from j.name or t.journal_type is distinct from j.journal_type);
|
|
446
|
+
|
|
447
|
+
-- Taxes --------------------------------------------------------------------
|
|
448
|
+
return query
|
|
449
|
+
select 'tax', t.code, 'missing', 'addition'::pack_change_rule,
|
|
450
|
+
jsonb_build_object('pack', jsonb_build_object(
|
|
451
|
+
'name', t.name, 'amount', t.amount, 'amount_type', t.amount_type,
|
|
452
|
+
'valid_from', t.valid_from, 'valid_to', t.valid_to))
|
|
453
|
+
from tax_templates t
|
|
454
|
+
where t.country = v_country
|
|
455
|
+
and not exists (select 1 from taxes x where x.company_id = p_company_id and x.code = t.code);
|
|
456
|
+
|
|
457
|
+
-- A validity the pack has closed and the company still holds open, or holds
|
|
458
|
+
-- open longer. This is a rate change arriving: the old tax stops on the day
|
|
459
|
+
-- the pack says, and the new rate is an addition above.
|
|
460
|
+
return query
|
|
461
|
+
select 'tax', x.code, 'valid_to', 'closure'::pack_change_rule,
|
|
462
|
+
jsonb_build_object('pack', jsonb_build_object('valid_to', t.valid_to),
|
|
463
|
+
'company', jsonb_build_object('valid_to', x.valid_to))
|
|
464
|
+
from taxes x
|
|
465
|
+
join tax_templates t on t.country = v_country and t.code = x.code
|
|
466
|
+
where x.company_id = p_company_id
|
|
467
|
+
and t.valid_to is not null
|
|
468
|
+
and (x.valid_to is null or x.valid_to > t.valid_to);
|
|
469
|
+
|
|
470
|
+
return query
|
|
471
|
+
select 'tax', x.code, 'differs', 'review'::pack_change_rule,
|
|
472
|
+
jsonb_build_object(
|
|
473
|
+
'pack', jsonb_build_object('name', t.name, 'amount', t.amount,
|
|
474
|
+
'amount_type', t.amount_type, 'treatment', t.treatment,
|
|
475
|
+
'applies_to', t.applies_to, 'tax_kind', t.tax_kind,
|
|
476
|
+
'recoverable', t.recoverable, 'valid_from', t.valid_from),
|
|
477
|
+
'company', jsonb_build_object('name', x.name, 'amount', x.amount,
|
|
478
|
+
'amount_type', x.amount_type, 'treatment', x.treatment,
|
|
479
|
+
'applies_to', x.applies_to, 'tax_kind', x.tax_kind,
|
|
480
|
+
'recoverable', x.recoverable, 'valid_from', x.valid_from))
|
|
481
|
+
from taxes x
|
|
482
|
+
join tax_templates t on t.country = v_country and t.code = x.code
|
|
483
|
+
where x.company_id = p_company_id
|
|
484
|
+
and (label_for(t.name, null, preferred_languages(p_company_id)) is distinct from x.name
|
|
485
|
+
or t.amount is distinct from x.amount
|
|
486
|
+
or t.amount_type is distinct from x.amount_type
|
|
487
|
+
or t.treatment is distinct from x.treatment
|
|
488
|
+
or t.applies_to is distinct from x.applies_to
|
|
489
|
+
or t.tax_kind is distinct from x.tax_kind
|
|
490
|
+
or t.recoverable is distinct from x.recoverable
|
|
491
|
+
or t.valid_from is distinct from x.valid_from);
|
|
492
|
+
|
|
493
|
+
return query
|
|
494
|
+
select 'tax', x.code, 'company_only', 'review'::pack_change_rule,
|
|
495
|
+
jsonb_build_object('company', jsonb_build_object('name', x.name, 'amount', x.amount))
|
|
496
|
+
from taxes x
|
|
497
|
+
where x.company_id = p_company_id
|
|
498
|
+
and x.country = v_country
|
|
499
|
+
and not exists (select 1 from tax_templates t where t.country = v_country and t.code = x.code);
|
|
500
|
+
|
|
501
|
+
-- What a tax books. Compared as a whole per tax rather than line by line: a
|
|
502
|
+
-- posting has no identity of its own, and "this tax now books its
|
|
503
|
+
-- non-deductible share somewhere else" is one fact, not four.
|
|
504
|
+
return query
|
|
505
|
+
with pack as (
|
|
506
|
+
select t.code,
|
|
507
|
+
jsonb_agg(jsonb_build_object('document_kind', tp.document_kind,
|
|
508
|
+
'posting_type', tp.posting_type,
|
|
509
|
+
'factor_percent', tp.factor_percent,
|
|
510
|
+
'account_code', tp.account_code,
|
|
511
|
+
'declaration_box', tp.declaration_box,
|
|
512
|
+
'declaration_boxes', to_jsonb(tp.declaration_boxes),
|
|
513
|
+
'box_factor_percent', tp.box_factor_percent,
|
|
514
|
+
'report_code', tp.report_code)
|
|
515
|
+
order by tp.document_kind, tp.posting_type, tp.sequence) as postings
|
|
516
|
+
from tax_templates t
|
|
517
|
+
join tax_posting_templates tp on tp.tax_template_id = t.id
|
|
518
|
+
where t.country = v_country
|
|
519
|
+
group by t.code
|
|
520
|
+
), held as (
|
|
521
|
+
select x.code,
|
|
522
|
+
jsonb_agg(jsonb_build_object('document_kind', p.document_kind,
|
|
523
|
+
'posting_type', p.posting_type,
|
|
524
|
+
'factor_percent', p.factor_percent,
|
|
525
|
+
'account_code', a.code,
|
|
526
|
+
'declaration_box', p.declaration_box,
|
|
527
|
+
'declaration_boxes', to_jsonb(p.declaration_boxes),
|
|
528
|
+
'box_factor_percent', p.box_factor_percent,
|
|
529
|
+
'report_code', p.report_code)
|
|
530
|
+
order by p.document_kind, p.posting_type, p.sequence) as postings
|
|
531
|
+
from taxes x
|
|
532
|
+
join tax_postings p on p.tax_id = x.id
|
|
533
|
+
left join accounts a on a.id = p.account_id
|
|
534
|
+
where x.company_id = p_company_id
|
|
535
|
+
group by x.code
|
|
536
|
+
)
|
|
537
|
+
select 'tax_posting', pack.code, 'differs', 'review'::pack_change_rule,
|
|
538
|
+
jsonb_build_object('pack', pack.postings, 'company', held.postings)
|
|
539
|
+
from pack
|
|
540
|
+
join held on held.code = pack.code
|
|
541
|
+
where pack.postings is distinct from held.postings;
|
|
542
|
+
end;
|
|
543
|
+
$$;
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
comment on function pack_upgrade_diff(uuid, char) is
|
|
547
|
+
'What separates a company from the country pack this installation now holds, by natural key, each difference carrying the rule that decides what an upgrade does with it. What a tax books is compared as one object, every declaration box of every posting included.';
|
|
548
|
+
|
|
549
|
+
revoke execute on function pack_upgrade_diff(uuid, char) from public, anon;
|
|
550
|
+
grant execute on function pack_upgrade_diff(uuid, char) to authenticated, service_role;
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
create or replace function pack_upgrade(
|
|
554
|
+
p_company_id uuid,
|
|
555
|
+
p_country char(2) default null,
|
|
556
|
+
p_apply boolean default false
|
|
557
|
+
)
|
|
558
|
+
returns jsonb
|
|
559
|
+
language plpgsql
|
|
560
|
+
security definer
|
|
561
|
+
set search_path = public, pg_temp
|
|
562
|
+
as $$
|
|
563
|
+
declare
|
|
564
|
+
v_country char(2);
|
|
565
|
+
v_chart text;
|
|
566
|
+
v_from text;
|
|
567
|
+
v_to text;
|
|
568
|
+
v_applied jsonb := '[]'::jsonb;
|
|
569
|
+
v_listed jsonb := '[]'::jsonb;
|
|
570
|
+
v_refused jsonb := '[]'::jsonb;
|
|
571
|
+
d record;
|
|
572
|
+
v_tax_id uuid;
|
|
573
|
+
v_template tax_templates%rowtype;
|
|
574
|
+
begin
|
|
575
|
+
select coalesce(p_country, c.country) into v_country
|
|
576
|
+
from companies c where c.id = p_company_id;
|
|
577
|
+
if v_country is null then
|
|
578
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
579
|
+
end if;
|
|
580
|
+
|
|
581
|
+
if auth.uid() is not null and not has_capability(p_company_id, 'company.write') then
|
|
582
|
+
raise exception 'not_allowed: upgrading a country pack needs company.write';
|
|
583
|
+
end if;
|
|
584
|
+
|
|
585
|
+
select cp.version, cp.chart_code into v_from, v_chart
|
|
586
|
+
from company_packs cp
|
|
587
|
+
where cp.company_id = p_company_id and cp.country = v_country;
|
|
588
|
+
if v_chart is null then
|
|
589
|
+
raise exception 'no_pack_installed: company % holds no % pack, so there is nothing to upgrade from', p_company_id, v_country;
|
|
590
|
+
end if;
|
|
591
|
+
|
|
592
|
+
select version into v_to from country_packs where country = v_country;
|
|
593
|
+
if v_to is null then
|
|
594
|
+
raise exception 'no_pack_loaded: this installation holds no % pack. Apply the seeds of this release first.', v_country;
|
|
595
|
+
end if;
|
|
596
|
+
|
|
597
|
+
for d in select * from pack_upgrade_diff(p_company_id, v_country) loop
|
|
598
|
+
-- Never, whatever the caller asked. An upgrade adds and closes; it does
|
|
599
|
+
-- not take a row out of a company's books.
|
|
600
|
+
if d.change = 'company_only' then
|
|
601
|
+
v_refused := v_refused || jsonb_build_object('object', d.object, 'key', d.key,
|
|
602
|
+
'change', d.change, 'rule', d.rule,
|
|
603
|
+
'detail', d.detail);
|
|
604
|
+
continue;
|
|
605
|
+
end if;
|
|
606
|
+
|
|
607
|
+
if d.rule = 'review' and not p_apply then
|
|
608
|
+
v_listed := v_listed || jsonb_build_object('object', d.object, 'key', d.key,
|
|
609
|
+
'change', d.change, 'rule', d.rule,
|
|
610
|
+
'detail', d.detail);
|
|
611
|
+
continue;
|
|
612
|
+
end if;
|
|
613
|
+
|
|
614
|
+
if d.object = 'account' and d.change = 'missing' then
|
|
615
|
+
insert into accounts (company_id, code, name, name_i18n, statement_hint, account_type, reconcilable)
|
|
616
|
+
select p_company_id, t.code, label_for(t.name, t.name_i18n, preferred_languages(p_company_id)),
|
|
617
|
+
t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
|
|
618
|
+
from account_templates t
|
|
619
|
+
where t.country = v_country and t.chart_code = v_chart and t.code = d.key
|
|
620
|
+
on conflict (company_id, code) do nothing;
|
|
621
|
+
|
|
622
|
+
elsif d.object = 'account' and d.change = 'differs' then
|
|
623
|
+
update accounts a
|
|
624
|
+
set name = label_for(t.name, t.name_i18n, preferred_languages(p_company_id)),
|
|
625
|
+
name_i18n = t.name_i18n,
|
|
626
|
+
account_type = t.account_type
|
|
627
|
+
from account_templates t
|
|
628
|
+
where t.country = v_country and t.chart_code = v_chart and t.code = a.code
|
|
629
|
+
and a.company_id = p_company_id and a.code = d.key;
|
|
630
|
+
|
|
631
|
+
elsif d.object = 'journal' and d.change = 'missing' then
|
|
632
|
+
insert into journals (company_id, code, name, journal_type)
|
|
633
|
+
select p_company_id, t.code, t.name, t.journal_type
|
|
634
|
+
from journal_templates t
|
|
635
|
+
where t.country = v_country and t.code = d.key
|
|
636
|
+
on conflict (company_id, code) do nothing;
|
|
637
|
+
|
|
638
|
+
elsif d.object = 'journal' and d.change = 'differs' then
|
|
639
|
+
update journals j
|
|
640
|
+
set name = t.name, journal_type = t.journal_type
|
|
641
|
+
from journal_templates t
|
|
642
|
+
where t.country = v_country and t.code = j.code
|
|
643
|
+
and j.company_id = p_company_id and j.code = d.key;
|
|
644
|
+
|
|
645
|
+
elsif d.object = 'tax' and d.change = 'missing' then
|
|
646
|
+
select * into v_template from tax_templates where country = v_country and code = d.key;
|
|
647
|
+
insert into taxes (company_id, code, name, description, amount_type, amount,
|
|
648
|
+
applies_to, treatment, country, valid_from, valid_to,
|
|
649
|
+
legal_reference, vat_category, exemption_code, sequence,
|
|
650
|
+
tax_kind, recoverable, jurisdiction, price_include,
|
|
651
|
+
cash_basis, cash_basis_transition_account_id)
|
|
652
|
+
values (p_company_id, v_template.code, v_template.name, v_template.description,
|
|
653
|
+
v_template.amount_type, v_template.amount, v_template.applies_to,
|
|
654
|
+
v_template.treatment, v_country, v_template.valid_from, v_template.valid_to,
|
|
655
|
+
v_template.legal_reference, v_template.vat_category, v_template.exemption_code,
|
|
656
|
+
v_template.sequence, v_template.tax_kind, v_template.recoverable,
|
|
657
|
+
v_template.jurisdiction, v_template.price_include, v_template.cash_basis,
|
|
658
|
+
account_id_by_code(p_company_id, v_template.cash_basis_transition_account_code))
|
|
659
|
+
on conflict (company_id, code) do nothing
|
|
660
|
+
returning id into v_tax_id;
|
|
661
|
+
|
|
662
|
+
if v_tax_id is not null then
|
|
663
|
+
insert into tax_postings (tax_id, company_id, document_kind, posting_type,
|
|
664
|
+
factor_percent, account_id, declaration_box,
|
|
665
|
+
declaration_boxes, box_factor_percent, report_code,
|
|
666
|
+
sequence)
|
|
667
|
+
select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
|
|
668
|
+
tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
|
|
669
|
+
tp.declaration_box, tp.declaration_boxes, tp.box_factor_percent,
|
|
670
|
+
tp.report_code, tp.sequence
|
|
671
|
+
from tax_posting_templates tp
|
|
672
|
+
where tp.tax_template_id = v_template.id
|
|
673
|
+
order by tp.sequence;
|
|
674
|
+
end if;
|
|
675
|
+
|
|
676
|
+
elsif d.object = 'tax' and d.change = 'valid_to' then
|
|
677
|
+
update taxes x
|
|
678
|
+
set valid_to = t.valid_to
|
|
679
|
+
from tax_templates t
|
|
680
|
+
where t.country = v_country and t.code = x.code
|
|
681
|
+
and x.company_id = p_company_id and x.code = d.key;
|
|
682
|
+
|
|
683
|
+
elsif d.object = 'tax' and d.change = 'differs' then
|
|
684
|
+
update taxes x
|
|
685
|
+
set name = t.name, description = t.description, amount = t.amount,
|
|
686
|
+
amount_type = t.amount_type, applies_to = t.applies_to,
|
|
687
|
+
treatment = t.treatment, valid_from = t.valid_from,
|
|
688
|
+
legal_reference = t.legal_reference, vat_category = t.vat_category,
|
|
689
|
+
exemption_code = t.exemption_code, tax_kind = t.tax_kind,
|
|
690
|
+
recoverable = t.recoverable, jurisdiction = t.jurisdiction,
|
|
691
|
+
price_include = t.price_include, cash_basis = t.cash_basis
|
|
692
|
+
from tax_templates t
|
|
693
|
+
where t.country = v_country and t.code = x.code
|
|
694
|
+
and x.company_id = p_company_id and x.code = d.key;
|
|
695
|
+
|
|
696
|
+
elsif d.object = 'tax_posting' and d.change = 'differs' then
|
|
697
|
+
select id into v_tax_id from taxes where company_id = p_company_id and code = d.key;
|
|
698
|
+
delete from tax_postings where tax_id = v_tax_id;
|
|
699
|
+
insert into tax_postings (tax_id, company_id, document_kind, posting_type,
|
|
700
|
+
factor_percent, account_id, declaration_box,
|
|
701
|
+
declaration_boxes, box_factor_percent, report_code,
|
|
702
|
+
sequence)
|
|
703
|
+
select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
|
|
704
|
+
tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
|
|
705
|
+
tp.declaration_box, tp.declaration_boxes, tp.box_factor_percent,
|
|
706
|
+
tp.report_code, tp.sequence
|
|
707
|
+
from tax_posting_templates tp
|
|
708
|
+
join tax_templates t on t.id = tp.tax_template_id
|
|
709
|
+
where t.country = v_country and t.code = d.key
|
|
710
|
+
order by tp.sequence;
|
|
711
|
+
|
|
712
|
+
else
|
|
713
|
+
-- A difference this function does not know how to apply is listed
|
|
714
|
+
-- rather than guessed at. Raising here would make a new kind of
|
|
715
|
+
-- difference break every upgrade.
|
|
716
|
+
v_listed := v_listed || jsonb_build_object('object', d.object, 'key', d.key,
|
|
717
|
+
'change', d.change, 'rule', d.rule,
|
|
718
|
+
'detail', d.detail);
|
|
719
|
+
continue;
|
|
720
|
+
end if;
|
|
721
|
+
|
|
722
|
+
v_applied := v_applied || jsonb_build_object('object', d.object, 'key', d.key,
|
|
723
|
+
'change', d.change, 'rule', d.rule,
|
|
724
|
+
'detail', d.detail);
|
|
725
|
+
end loop;
|
|
726
|
+
|
|
727
|
+
-- The version moves only when nothing is left waiting. A company that still
|
|
728
|
+
-- holds a difference it has not decided on has not finished upgrading, and
|
|
729
|
+
-- saying otherwise would hide the difference at the next run.
|
|
730
|
+
if jsonb_array_length(v_listed) = 0 then
|
|
731
|
+
update company_packs
|
|
732
|
+
set version = v_to, upgraded_at = now()
|
|
733
|
+
where company_id = p_company_id and country = v_country;
|
|
734
|
+
end if;
|
|
735
|
+
|
|
736
|
+
perform audit_record(
|
|
737
|
+
p_company_id, 'company_packs', null, v_country, 'update', 'pack_upgraded',
|
|
738
|
+
jsonb_build_object('version', v_from),
|
|
739
|
+
jsonb_build_object('version', case when jsonb_array_length(v_listed) = 0 then v_to else v_from end,
|
|
740
|
+
'pack_version', v_to,
|
|
741
|
+
'chart_code', v_chart,
|
|
742
|
+
'applied', v_applied,
|
|
743
|
+
'listed', v_listed,
|
|
744
|
+
'never_applied', v_refused));
|
|
745
|
+
|
|
746
|
+
return jsonb_build_object(
|
|
747
|
+
'company_id', p_company_id,
|
|
748
|
+
'country', v_country,
|
|
749
|
+
'chart_code', v_chart,
|
|
750
|
+
'from_version', v_from,
|
|
751
|
+
'to_version', v_to,
|
|
752
|
+
'version_moved', jsonb_array_length(v_listed) = 0,
|
|
753
|
+
'applied', v_applied,
|
|
754
|
+
'listed', v_listed,
|
|
755
|
+
'never_applied', v_refused);
|
|
756
|
+
end;
|
|
757
|
+
$$;
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
revoke execute on function pack_upgrade(uuid, char, boolean) from public, anon;
|
|
761
|
+
grant execute on function pack_upgrade(uuid, char, boolean) to authenticated, service_role;
|
|
762
|
+
|
|
763
|
+
-- ---------------------------------------------------------------------------
|
|
764
|
+
-- The return sums a line into every box its posting names
|
|
765
|
+
-- ---------------------------------------------------------------------------
|
|
766
|
+
--
|
|
767
|
+
-- One `unnest`, and the rest of `20260914163943` unchanged. The amount is the
|
|
768
|
+
-- same in each box: `box_factor_percent` is the share of the amount the form
|
|
769
|
+
-- expects, and a form that prints one figure in three places prints the same
|
|
770
|
+
-- figure in all three. A form that wants a fraction of it in one of them is
|
|
771
|
+
-- printing a different figure, which is a second posting or a total.
|
|
772
|
+
--
|
|
773
|
+
-- A parent that *is* a sum stays a total and is untouched: a posting that
|
|
774
|
+
-- named both a box and the total above it would be counted twice, which is
|
|
775
|
+
-- why `ekwo pack check` refuses a posting that names a total at all.
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
create or replace function vat_return(
|
|
779
|
+
p_company_id uuid,
|
|
780
|
+
p_from date,
|
|
781
|
+
p_to date,
|
|
782
|
+
p_report_code text default null
|
|
783
|
+
)
|
|
784
|
+
returns table (
|
|
785
|
+
box text,
|
|
786
|
+
kind text,
|
|
787
|
+
amount numeric,
|
|
788
|
+
computed boolean,
|
|
789
|
+
name text,
|
|
790
|
+
sequence integer,
|
|
791
|
+
hidden boolean,
|
|
792
|
+
report_code text
|
|
793
|
+
)
|
|
794
|
+
language plpgsql
|
|
795
|
+
stable
|
|
796
|
+
as $$
|
|
797
|
+
declare
|
|
798
|
+
-- 'box|kind' -> amount, for every box summed from the ledger, then the
|
|
799
|
+
-- totals `evaluate_totals()` derives from them. The key carries the kind
|
|
800
|
+
-- because the French CA3 puts a base and a tax on line 08 and a formula has
|
|
801
|
+
-- to be able to name one of them.
|
|
802
|
+
v_values jsonb := '{}'::jsonb;
|
|
803
|
+
v_formulas jsonb := '[]'::jsonb;
|
|
804
|
+
v_totals jsonb := '{}'::jsonb;
|
|
805
|
+
v_rows jsonb := '[]'::jsonb;
|
|
806
|
+
v_country char(2);
|
|
807
|
+
v_report text;
|
|
808
|
+
v_in char(2);
|
|
809
|
+
v_count integer;
|
|
810
|
+
v_codes text;
|
|
811
|
+
-- How often this company files, what the form accepts, and what the two
|
|
812
|
+
-- dates asked for actually are.
|
|
813
|
+
v_files declaration_period;
|
|
814
|
+
v_accepts declaration_period[];
|
|
815
|
+
v_asked declaration_period;
|
|
816
|
+
-- A declaration figure is not a ledger figure, but it is written in the
|
|
817
|
+
-- same currency and with the same decimals.
|
|
818
|
+
v_round money_rounding;
|
|
819
|
+
r record;
|
|
820
|
+
begin
|
|
821
|
+
select c.fiscal_country, c.vat_period into v_country, v_files
|
|
822
|
+
from companies c where c.id = p_company_id;
|
|
823
|
+
if not found then
|
|
824
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
825
|
+
end if;
|
|
826
|
+
v_round := rounding_of(p_company_id);
|
|
827
|
+
|
|
828
|
+
-- Which form. The caller names one, or the country files exactly one on
|
|
829
|
+
-- that date. Two and no name is a question only the caller can answer — a
|
|
830
|
+
-- Canadian company files the federal return and the Québec one at once — so
|
|
831
|
+
-- this asks instead of guessing.
|
|
832
|
+
if p_report_code is not null then
|
|
833
|
+
select t.country, t.code into v_in, v_report
|
|
834
|
+
from tax_report_templates t
|
|
835
|
+
where t.code = p_report_code
|
|
836
|
+
and t.valid_from <= p_to
|
|
837
|
+
and (t.valid_to is null or t.valid_to >= p_to)
|
|
838
|
+
order by t.country
|
|
839
|
+
limit 1;
|
|
840
|
+
if v_report is null then
|
|
841
|
+
raise exception 'unknown_tax_report: % is not a declaration form in force on %',
|
|
842
|
+
p_report_code, p_to;
|
|
843
|
+
end if;
|
|
844
|
+
else
|
|
845
|
+
select count(*), min(t.code), string_agg(t.code, ', ' order by t.code)
|
|
846
|
+
into v_count, v_report, v_codes
|
|
847
|
+
from tax_report_templates t
|
|
848
|
+
where t.country = v_country
|
|
849
|
+
and t.is_periodic_return
|
|
850
|
+
and t.valid_from <= p_to
|
|
851
|
+
and (t.valid_to is null or t.valid_to >= p_to);
|
|
852
|
+
if v_count > 1 then
|
|
853
|
+
raise exception 'ambiguous_tax_report: % files several declarations on % (%); name one',
|
|
854
|
+
v_country, p_to, v_codes;
|
|
855
|
+
end if;
|
|
856
|
+
v_in := v_country;
|
|
857
|
+
if v_count = 0 then
|
|
858
|
+
v_report := null; -- no pack for this country: the ledger boxes, and no total.
|
|
859
|
+
end if;
|
|
860
|
+
end if;
|
|
861
|
+
|
|
862
|
+
-- The period asked for against the one this company files on. Four things
|
|
863
|
+
-- have to hold before this refuses, and the fourth is what keeps it out of
|
|
864
|
+
-- everybody's way: the company has recorded a cadence, the form is one it
|
|
865
|
+
-- files on that cadence, the dates are themselves a whole cadence of that
|
|
866
|
+
-- form, and the two are not the same. A fortnight, a half-year, the annual
|
|
867
|
+
-- recapitulative form a quarterly filer also files — none of those is a
|
|
868
|
+
-- filing on the wrong cadence, and none of them is refused.
|
|
869
|
+
if v_files is not null and v_report is not null then
|
|
870
|
+
select t.periods into v_accepts
|
|
871
|
+
from tax_report_templates t
|
|
872
|
+
where t.country = v_in and t.code = v_report;
|
|
873
|
+
v_asked := declaration_period_of(p_from, p_to);
|
|
874
|
+
if v_asked is not null
|
|
875
|
+
and v_files = any(v_accepts)
|
|
876
|
+
and v_asked = any(v_accepts)
|
|
877
|
+
and v_asked <> v_files then
|
|
878
|
+
raise exception
|
|
879
|
+
'wrong_declaration_period: this company files % returns on %; % to % is a %',
|
|
880
|
+
v_files, v_report, p_from, p_to, v_asked;
|
|
881
|
+
end if;
|
|
882
|
+
end if;
|
|
883
|
+
|
|
884
|
+
-- 1. What the tax postings wrote on the ledger, in every box each of them
|
|
885
|
+
-- names. A line carries the box it is known by; the posting behind it
|
|
886
|
+
-- carries the whole list, which is one box for all but the handful of
|
|
887
|
+
-- forms that print a figure twice. No country rule here either: the
|
|
888
|
+
-- expansion is `unnest`, and which boxes there are is the pack's answer.
|
|
889
|
+
for r in
|
|
890
|
+
with lines as (
|
|
891
|
+
select l.declaration_box as lbox,
|
|
892
|
+
case when l.tax_line then 'tax' else 'base' end as lkind,
|
|
893
|
+
l.tax_id as ltax,
|
|
894
|
+
l.box_amount as lamount
|
|
895
|
+
from entry_lines l
|
|
896
|
+
join entries e on e.id = l.entry_id
|
|
897
|
+
where l.company_id = p_company_id
|
|
898
|
+
and e.state = 'posted'
|
|
899
|
+
and e.entry_date between p_from and p_to
|
|
900
|
+
and l.declaration_box is not null
|
|
901
|
+
),
|
|
902
|
+
-- The posting that wrote the line: which form it is on, and every box it
|
|
903
|
+
-- prints in. A line with no posting behind it — an entry keyed by hand,
|
|
904
|
+
-- a tax a company wrote itself — is on this form and in the one box it
|
|
905
|
+
-- names, which is what the left join leaves.
|
|
906
|
+
sourced as (
|
|
907
|
+
select ln.lbox, ln.lkind, ln.lamount,
|
|
908
|
+
coalesce(p.boxes, array[ln.lbox]) as lboxes
|
|
909
|
+
from lines ln
|
|
910
|
+
left join lateral (
|
|
911
|
+
select min(tp.report_code) as report_code,
|
|
912
|
+
array_agg(distinct b.box) as boxes
|
|
913
|
+
from tax_postings tp
|
|
914
|
+
cross join lateral unnest(tp.declaration_boxes) as b(box)
|
|
915
|
+
where tp.tax_id = ln.ltax
|
|
916
|
+
and tp.declaration_box = ln.lbox
|
|
917
|
+
and tp.posting_type = ln.lkind::tax_posting_type
|
|
918
|
+
) p on true
|
|
919
|
+
-- A box number belongs to one form. A line whose posting names
|
|
920
|
+
-- another form is not on this declaration; one that names none is
|
|
921
|
+
-- the single-return case every European company is in.
|
|
922
|
+
where v_report is null or coalesce(p.report_code, v_report) = v_report
|
|
923
|
+
),
|
|
924
|
+
ledger as (
|
|
925
|
+
select x.box as lbox, s.lkind,
|
|
926
|
+
round_amount(sum(s.lamount), v_round) as lamount
|
|
927
|
+
from sourced s
|
|
928
|
+
cross join lateral unnest(s.lboxes) as x(box)
|
|
929
|
+
group by 1, 2
|
|
930
|
+
having round_amount(sum(s.lamount), v_round) <> 0
|
|
931
|
+
)
|
|
932
|
+
select g.lbox, g.lkind, g.lamount, b.name as lname,
|
|
933
|
+
b.sequence as lsequence, coalesce(b.hidden, false) as lhidden
|
|
934
|
+
from ledger g
|
|
935
|
+
left join tax_report_box_templates b
|
|
936
|
+
on b.country = v_in and b.report_code = v_report
|
|
937
|
+
and b.box = g.lbox and b.kind = g.lkind
|
|
938
|
+
order by coalesce(b.sequence, 2147483647), g.lbox, g.lkind
|
|
939
|
+
loop
|
|
940
|
+
v_values := v_values || jsonb_build_object(r.lbox || '|' || r.lkind, r.lamount);
|
|
941
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
942
|
+
'box', r.lbox, 'kind', r.lkind, 'amount', r.lamount, 'computed', false,
|
|
943
|
+
'name', r.lname, 'sequence', r.lsequence, 'hidden', r.lhidden,
|
|
944
|
+
'report_code', v_report));
|
|
945
|
+
end loop;
|
|
946
|
+
|
|
947
|
+
-- 2. The totals of the form, through the evaluator the statements use. A
|
|
948
|
+
-- return prints what it has, so a nil total is left out of the answer —
|
|
949
|
+
-- and kept in the working set, so a later total that names it reads a
|
|
950
|
+
-- zero rather than a gap.
|
|
951
|
+
if v_report is not null then
|
|
952
|
+
select coalesce(jsonb_agg(jsonb_build_object(
|
|
953
|
+
'key', b.box || '|total', 'plus', to_jsonb(b.plus_boxes),
|
|
954
|
+
'minus', to_jsonb(b.minus_boxes), 'floor_zero', b.floor_zero,
|
|
955
|
+
'sequence', b.sequence
|
|
956
|
+
) order by b.sequence, b.box), '[]'::jsonb)
|
|
957
|
+
into v_formulas
|
|
958
|
+
from tax_report_box_templates b
|
|
959
|
+
where b.country = v_in
|
|
960
|
+
and b.report_code = v_report
|
|
961
|
+
and b.kind = 'total'
|
|
962
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
963
|
+
and (b.valid_to is null or b.valid_to >= p_to);
|
|
964
|
+
|
|
965
|
+
v_totals := evaluate_totals(v_values, v_formulas, v_round, false);
|
|
966
|
+
|
|
967
|
+
for r in
|
|
968
|
+
select b.box as tbox, b.name as tname, b.sequence as tsequence, b.hidden as thidden
|
|
969
|
+
from tax_report_box_templates b
|
|
970
|
+
where b.country = v_in
|
|
971
|
+
and b.report_code = v_report
|
|
972
|
+
and b.kind = 'total'
|
|
973
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
974
|
+
and (b.valid_to is null or b.valid_to >= p_to)
|
|
975
|
+
and v_totals ? (b.box || '|total')
|
|
976
|
+
order by b.sequence, b.box
|
|
977
|
+
loop
|
|
978
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
979
|
+
'box', r.tbox, 'kind', 'total',
|
|
980
|
+
'amount', (v_totals ->> (r.tbox || '|total'))::numeric, 'computed', true,
|
|
981
|
+
'name', r.tname, 'sequence', r.tsequence, 'hidden', r.thidden,
|
|
982
|
+
'report_code', v_report));
|
|
983
|
+
end loop;
|
|
984
|
+
end if;
|
|
985
|
+
|
|
986
|
+
return query
|
|
987
|
+
select (x ->> 'box')::text,
|
|
988
|
+
(x ->> 'kind')::text,
|
|
989
|
+
(x ->> 'amount')::numeric,
|
|
990
|
+
(x ->> 'computed')::boolean,
|
|
991
|
+
(x ->> 'name')::text,
|
|
992
|
+
(x ->> 'sequence')::integer,
|
|
993
|
+
(x ->> 'hidden')::boolean,
|
|
994
|
+
(x ->> 'report_code')::text
|
|
995
|
+
from jsonb_array_elements(v_rows) as x;
|
|
996
|
+
end;
|
|
997
|
+
$$;
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
comment on function vat_return(uuid, date, date, text) is
|
|
1001
|
+
'Declaration boxes for a period: summed from the ledger into every box the posting behind each line names, then the totals of the country''s form worked out by evaluate_totals(), the same evaluator financial_statement() uses. Refuses a period the company does not file on, when it has recorded one. No country rule lives in this function.';
|
|
1002
|
+
|
|
1003
|
+
revoke execute on function vat_return(uuid, date, date, text) from public, anon;
|
|
1004
|
+
grant execute on function vat_return(uuid, date, date, text) to authenticated, service_role;
|