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,428 @@
|
|
|
1
|
+
-- Ekwo OS — moving a company from one version of a country pack to the next.
|
|
2
|
+
--
|
|
3
|
+
-- A pack is versioned and a company records the version it copied, but until
|
|
4
|
+
-- now nothing could say what separated the two. `ekwo status` printed the
|
|
5
|
+
-- warning — "this company copied BE 1.0.0, this installation holds 1.4.0" —
|
|
6
|
+
-- and stopped there, because a chart of accounts is not a file to overwrite:
|
|
7
|
+
-- the company has been booking on those accounts for a year.
|
|
8
|
+
--
|
|
9
|
+
-- The difference is computed by natural key — an account code, a journal
|
|
10
|
+
-- code, a tax code — and every difference falls into one of three rules:
|
|
11
|
+
--
|
|
12
|
+
-- addition the pack has something the company does not. Copied in. A new
|
|
13
|
+
-- account or a new tax takes nothing away and breaks nothing.
|
|
14
|
+
-- closure the pack has closed the validity of a tax the company still
|
|
15
|
+
-- holds open. Applied. A rate that changes is a new tax plus a
|
|
16
|
+
-- `valid_to` on the old one, never an edit, so this is how a rate
|
|
17
|
+
-- change reaches a company: the old one stops, the new one is an
|
|
18
|
+
-- addition.
|
|
19
|
+
-- review everything else — a label that differs, a rate that differs on
|
|
20
|
+
-- the same code, a posting that books somewhere else, a row the
|
|
21
|
+
-- company holds and the pack does not. Listed, never applied
|
|
22
|
+
-- without the caller saying so.
|
|
23
|
+
--
|
|
24
|
+
-- The third rule is the point of the whole thing. Silently overwriting a
|
|
25
|
+
-- company's chart of accounts from a pack is how an upgrade destroys a year
|
|
26
|
+
-- of bookkeeping, and there is no way to be sure from here which of the two is
|
|
27
|
+
-- right: an operator may have renamed an account deliberately. So it is shown
|
|
28
|
+
-- and it waits.
|
|
29
|
+
--
|
|
30
|
+
-- One row the caller never gets applied whatever they ask: a row the company
|
|
31
|
+
-- holds and the pack does not. Nothing is deleted from a company's books by an
|
|
32
|
+
-- upgrade — an account may carry entries, a tax may be on a posted document —
|
|
33
|
+
-- and a pack that retires a code retires it, which is a `valid_to`, not a
|
|
34
|
+
-- deletion.
|
|
35
|
+
|
|
36
|
+
create type pack_change_rule as enum ('addition', 'closure', 'review');
|
|
37
|
+
|
|
38
|
+
comment on type pack_change_rule is
|
|
39
|
+
'What an upgrade does with a difference: copy it in, close a validity, or show it and wait.';
|
|
40
|
+
|
|
41
|
+
-- ---------------------------------------------------------------------------
|
|
42
|
+
-- The difference
|
|
43
|
+
-- ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
create or replace function pack_upgrade_diff(
|
|
46
|
+
p_company_id uuid,
|
|
47
|
+
p_country char(2) default null
|
|
48
|
+
)
|
|
49
|
+
returns table (
|
|
50
|
+
object text,
|
|
51
|
+
key text,
|
|
52
|
+
change text,
|
|
53
|
+
rule pack_change_rule,
|
|
54
|
+
detail jsonb
|
|
55
|
+
)
|
|
56
|
+
language plpgsql
|
|
57
|
+
stable
|
|
58
|
+
as $$
|
|
59
|
+
declare
|
|
60
|
+
v_country char(2);
|
|
61
|
+
v_chart text;
|
|
62
|
+
begin
|
|
63
|
+
select coalesce(p_country, c.country) into v_country
|
|
64
|
+
from companies c where c.id = p_company_id;
|
|
65
|
+
if v_country is null then
|
|
66
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
67
|
+
end if;
|
|
68
|
+
|
|
69
|
+
select cp.chart_code into v_chart
|
|
70
|
+
from company_packs cp
|
|
71
|
+
where cp.company_id = p_company_id and cp.country = v_country;
|
|
72
|
+
if v_chart is null then
|
|
73
|
+
raise exception 'no_pack_installed: company % holds no % pack, so there is nothing to upgrade from', p_company_id, v_country;
|
|
74
|
+
end if;
|
|
75
|
+
|
|
76
|
+
-- Accounts -----------------------------------------------------------------
|
|
77
|
+
return query
|
|
78
|
+
select 'account', t.code, 'missing', 'addition'::pack_change_rule,
|
|
79
|
+
jsonb_build_object('pack', jsonb_build_object('name', t.name, 'account_type', t.account_type))
|
|
80
|
+
from account_templates t
|
|
81
|
+
where t.country = v_country and t.chart_code = v_chart
|
|
82
|
+
and not exists (select 1 from accounts a where a.company_id = p_company_id and a.code = t.code);
|
|
83
|
+
|
|
84
|
+
return query
|
|
85
|
+
select 'account', a.code, 'differs', 'review'::pack_change_rule,
|
|
86
|
+
jsonb_build_object(
|
|
87
|
+
'pack', jsonb_build_object('name', t.name, 'account_type', t.account_type),
|
|
88
|
+
'company', jsonb_build_object('name', a.name, 'account_type', a.account_type))
|
|
89
|
+
from accounts a
|
|
90
|
+
join account_templates t
|
|
91
|
+
on t.country = v_country and t.chart_code = v_chart and t.code = a.code
|
|
92
|
+
where a.company_id = p_company_id
|
|
93
|
+
and (label_for(t.name, t.name_i18n, preferred_languages(p_company_id)) is distinct from a.name
|
|
94
|
+
or t.account_type is distinct from a.account_type);
|
|
95
|
+
|
|
96
|
+
return query
|
|
97
|
+
select 'account', a.code, 'company_only', 'review'::pack_change_rule,
|
|
98
|
+
jsonb_build_object('company', jsonb_build_object('name', a.name, 'account_type', a.account_type))
|
|
99
|
+
from accounts a
|
|
100
|
+
where a.company_id = p_company_id
|
|
101
|
+
and not exists (select 1 from account_templates t
|
|
102
|
+
where t.country = v_country and t.chart_code = v_chart and t.code = a.code);
|
|
103
|
+
|
|
104
|
+
-- Journals -----------------------------------------------------------------
|
|
105
|
+
return query
|
|
106
|
+
select 'journal', t.code, 'missing', 'addition'::pack_change_rule,
|
|
107
|
+
jsonb_build_object('pack', jsonb_build_object('name', t.name, 'journal_type', t.journal_type))
|
|
108
|
+
from journal_templates t
|
|
109
|
+
where t.country = v_country
|
|
110
|
+
and not exists (select 1 from journals j where j.company_id = p_company_id and j.code = t.code);
|
|
111
|
+
|
|
112
|
+
return query
|
|
113
|
+
select 'journal', j.code, 'differs', 'review'::pack_change_rule,
|
|
114
|
+
jsonb_build_object(
|
|
115
|
+
'pack', jsonb_build_object('name', t.name, 'journal_type', t.journal_type),
|
|
116
|
+
'company', jsonb_build_object('name', j.name, 'journal_type', j.journal_type))
|
|
117
|
+
from journals j
|
|
118
|
+
join journal_templates t on t.country = v_country and t.code = j.code
|
|
119
|
+
where j.company_id = p_company_id
|
|
120
|
+
and (t.name is distinct from j.name or t.journal_type is distinct from j.journal_type);
|
|
121
|
+
|
|
122
|
+
-- Taxes --------------------------------------------------------------------
|
|
123
|
+
return query
|
|
124
|
+
select 'tax', t.code, 'missing', 'addition'::pack_change_rule,
|
|
125
|
+
jsonb_build_object('pack', jsonb_build_object(
|
|
126
|
+
'name', t.name, 'amount', t.amount, 'amount_type', t.amount_type,
|
|
127
|
+
'valid_from', t.valid_from, 'valid_to', t.valid_to))
|
|
128
|
+
from tax_templates t
|
|
129
|
+
where t.country = v_country
|
|
130
|
+
and not exists (select 1 from taxes x where x.company_id = p_company_id and x.code = t.code);
|
|
131
|
+
|
|
132
|
+
-- A validity the pack has closed and the company still holds open, or holds
|
|
133
|
+
-- open longer. This is a rate change arriving: the old tax stops on the day
|
|
134
|
+
-- the pack says, and the new rate is an addition above.
|
|
135
|
+
return query
|
|
136
|
+
select 'tax', x.code, 'valid_to', 'closure'::pack_change_rule,
|
|
137
|
+
jsonb_build_object('pack', jsonb_build_object('valid_to', t.valid_to),
|
|
138
|
+
'company', jsonb_build_object('valid_to', x.valid_to))
|
|
139
|
+
from taxes x
|
|
140
|
+
join tax_templates t on t.country = v_country and t.code = x.code
|
|
141
|
+
where x.company_id = p_company_id
|
|
142
|
+
and t.valid_to is not null
|
|
143
|
+
and (x.valid_to is null or x.valid_to > t.valid_to);
|
|
144
|
+
|
|
145
|
+
return query
|
|
146
|
+
select 'tax', x.code, 'differs', 'review'::pack_change_rule,
|
|
147
|
+
jsonb_build_object(
|
|
148
|
+
'pack', jsonb_build_object('name', t.name, 'amount', t.amount,
|
|
149
|
+
'amount_type', t.amount_type, 'treatment', t.treatment,
|
|
150
|
+
'applies_to', t.applies_to, 'tax_kind', t.tax_kind,
|
|
151
|
+
'recoverable', t.recoverable, 'valid_from', t.valid_from),
|
|
152
|
+
'company', jsonb_build_object('name', x.name, 'amount', x.amount,
|
|
153
|
+
'amount_type', x.amount_type, 'treatment', x.treatment,
|
|
154
|
+
'applies_to', x.applies_to, 'tax_kind', x.tax_kind,
|
|
155
|
+
'recoverable', x.recoverable, 'valid_from', x.valid_from))
|
|
156
|
+
from taxes x
|
|
157
|
+
join tax_templates t on t.country = v_country and t.code = x.code
|
|
158
|
+
where x.company_id = p_company_id
|
|
159
|
+
and (label_for(t.name, null, preferred_languages(p_company_id)) is distinct from x.name
|
|
160
|
+
or t.amount is distinct from x.amount
|
|
161
|
+
or t.amount_type is distinct from x.amount_type
|
|
162
|
+
or t.treatment is distinct from x.treatment
|
|
163
|
+
or t.applies_to is distinct from x.applies_to
|
|
164
|
+
or t.tax_kind is distinct from x.tax_kind
|
|
165
|
+
or t.recoverable is distinct from x.recoverable
|
|
166
|
+
or t.valid_from is distinct from x.valid_from);
|
|
167
|
+
|
|
168
|
+
return query
|
|
169
|
+
select 'tax', x.code, 'company_only', 'review'::pack_change_rule,
|
|
170
|
+
jsonb_build_object('company', jsonb_build_object('name', x.name, 'amount', x.amount))
|
|
171
|
+
from taxes x
|
|
172
|
+
where x.company_id = p_company_id
|
|
173
|
+
and x.country = v_country
|
|
174
|
+
and not exists (select 1 from tax_templates t where t.country = v_country and t.code = x.code);
|
|
175
|
+
|
|
176
|
+
-- What a tax books. Compared as a whole per tax rather than line by line: a
|
|
177
|
+
-- posting has no identity of its own, and "this tax now books its
|
|
178
|
+
-- non-deductible share somewhere else" is one fact, not four.
|
|
179
|
+
return query
|
|
180
|
+
with pack as (
|
|
181
|
+
select t.code,
|
|
182
|
+
jsonb_agg(jsonb_build_object('document_kind', tp.document_kind,
|
|
183
|
+
'posting_type', tp.posting_type,
|
|
184
|
+
'factor_percent', tp.factor_percent,
|
|
185
|
+
'account_code', tp.account_code,
|
|
186
|
+
'declaration_box', tp.declaration_box,
|
|
187
|
+
'box_factor_percent', tp.box_factor_percent,
|
|
188
|
+
'report_code', tp.report_code)
|
|
189
|
+
order by tp.document_kind, tp.posting_type, tp.sequence) as postings
|
|
190
|
+
from tax_templates t
|
|
191
|
+
join tax_posting_templates tp on tp.tax_template_id = t.id
|
|
192
|
+
where t.country = v_country
|
|
193
|
+
group by t.code
|
|
194
|
+
), held as (
|
|
195
|
+
select x.code,
|
|
196
|
+
jsonb_agg(jsonb_build_object('document_kind', p.document_kind,
|
|
197
|
+
'posting_type', p.posting_type,
|
|
198
|
+
'factor_percent', p.factor_percent,
|
|
199
|
+
'account_code', a.code,
|
|
200
|
+
'declaration_box', p.declaration_box,
|
|
201
|
+
'box_factor_percent', p.box_factor_percent,
|
|
202
|
+
'report_code', p.report_code)
|
|
203
|
+
order by p.document_kind, p.posting_type, p.sequence) as postings
|
|
204
|
+
from taxes x
|
|
205
|
+
join tax_postings p on p.tax_id = x.id
|
|
206
|
+
left join accounts a on a.id = p.account_id
|
|
207
|
+
where x.company_id = p_company_id
|
|
208
|
+
group by x.code
|
|
209
|
+
)
|
|
210
|
+
select 'tax_posting', pack.code, 'differs', 'review'::pack_change_rule,
|
|
211
|
+
jsonb_build_object('pack', pack.postings, 'company', held.postings)
|
|
212
|
+
from pack
|
|
213
|
+
join held on held.code = pack.code
|
|
214
|
+
where pack.postings is distinct from held.postings;
|
|
215
|
+
end;
|
|
216
|
+
$$;
|
|
217
|
+
|
|
218
|
+
comment on function pack_upgrade_diff(uuid, char) is
|
|
219
|
+
'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.';
|
|
220
|
+
|
|
221
|
+
-- ---------------------------------------------------------------------------
|
|
222
|
+
-- The upgrade
|
|
223
|
+
-- ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
create or replace function pack_upgrade(
|
|
226
|
+
p_company_id uuid,
|
|
227
|
+
p_country char(2) default null,
|
|
228
|
+
p_apply boolean default false
|
|
229
|
+
)
|
|
230
|
+
returns jsonb
|
|
231
|
+
language plpgsql
|
|
232
|
+
as $$
|
|
233
|
+
declare
|
|
234
|
+
v_country char(2);
|
|
235
|
+
v_chart text;
|
|
236
|
+
v_from text;
|
|
237
|
+
v_to text;
|
|
238
|
+
v_applied jsonb := '[]'::jsonb;
|
|
239
|
+
v_listed jsonb := '[]'::jsonb;
|
|
240
|
+
v_refused jsonb := '[]'::jsonb;
|
|
241
|
+
d record;
|
|
242
|
+
v_tax_id uuid;
|
|
243
|
+
v_template tax_templates%rowtype;
|
|
244
|
+
begin
|
|
245
|
+
select coalesce(p_country, c.country) into v_country
|
|
246
|
+
from companies c where c.id = p_company_id;
|
|
247
|
+
if v_country is null then
|
|
248
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
249
|
+
end if;
|
|
250
|
+
|
|
251
|
+
if auth.uid() is not null and not has_capability(p_company_id, 'company.write') then
|
|
252
|
+
raise exception 'not_allowed: upgrading a country pack needs company.write';
|
|
253
|
+
end if;
|
|
254
|
+
|
|
255
|
+
select cp.version, cp.chart_code into v_from, v_chart
|
|
256
|
+
from company_packs cp
|
|
257
|
+
where cp.company_id = p_company_id and cp.country = v_country;
|
|
258
|
+
if v_chart is null then
|
|
259
|
+
raise exception 'no_pack_installed: company % holds no % pack, so there is nothing to upgrade from', p_company_id, v_country;
|
|
260
|
+
end if;
|
|
261
|
+
|
|
262
|
+
select version into v_to from country_packs where country = v_country;
|
|
263
|
+
if v_to is null then
|
|
264
|
+
raise exception 'no_pack_loaded: this installation holds no % pack. Apply the seeds of this release first.', v_country;
|
|
265
|
+
end if;
|
|
266
|
+
|
|
267
|
+
for d in select * from pack_upgrade_diff(p_company_id, v_country) loop
|
|
268
|
+
-- Never, whatever the caller asked. An upgrade adds and closes; it does
|
|
269
|
+
-- not take a row out of a company's books.
|
|
270
|
+
if d.change = 'company_only' then
|
|
271
|
+
v_refused := v_refused || jsonb_build_object('object', d.object, 'key', d.key,
|
|
272
|
+
'change', d.change, 'rule', d.rule,
|
|
273
|
+
'detail', d.detail);
|
|
274
|
+
continue;
|
|
275
|
+
end if;
|
|
276
|
+
|
|
277
|
+
if d.rule = 'review' and not p_apply then
|
|
278
|
+
v_listed := v_listed || jsonb_build_object('object', d.object, 'key', d.key,
|
|
279
|
+
'change', d.change, 'rule', d.rule,
|
|
280
|
+
'detail', d.detail);
|
|
281
|
+
continue;
|
|
282
|
+
end if;
|
|
283
|
+
|
|
284
|
+
if d.object = 'account' and d.change = 'missing' then
|
|
285
|
+
insert into accounts (company_id, code, name, name_i18n, statement_hint, account_type, reconcilable)
|
|
286
|
+
select p_company_id, t.code, label_for(t.name, t.name_i18n, preferred_languages(p_company_id)),
|
|
287
|
+
t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
|
|
288
|
+
from account_templates t
|
|
289
|
+
where t.country = v_country and t.chart_code = v_chart and t.code = d.key
|
|
290
|
+
on conflict (company_id, code) do nothing;
|
|
291
|
+
|
|
292
|
+
elsif d.object = 'account' and d.change = 'differs' then
|
|
293
|
+
update accounts a
|
|
294
|
+
set name = label_for(t.name, t.name_i18n, preferred_languages(p_company_id)),
|
|
295
|
+
name_i18n = t.name_i18n,
|
|
296
|
+
account_type = t.account_type
|
|
297
|
+
from account_templates t
|
|
298
|
+
where t.country = v_country and t.chart_code = v_chart and t.code = a.code
|
|
299
|
+
and a.company_id = p_company_id and a.code = d.key;
|
|
300
|
+
|
|
301
|
+
elsif d.object = 'journal' and d.change = 'missing' then
|
|
302
|
+
insert into journals (company_id, code, name, journal_type)
|
|
303
|
+
select p_company_id, t.code, t.name, t.journal_type
|
|
304
|
+
from journal_templates t
|
|
305
|
+
where t.country = v_country and t.code = d.key
|
|
306
|
+
on conflict (company_id, code) do nothing;
|
|
307
|
+
|
|
308
|
+
elsif d.object = 'journal' and d.change = 'differs' then
|
|
309
|
+
update journals j
|
|
310
|
+
set name = t.name, journal_type = t.journal_type
|
|
311
|
+
from journal_templates t
|
|
312
|
+
where t.country = v_country and t.code = j.code
|
|
313
|
+
and j.company_id = p_company_id and j.code = d.key;
|
|
314
|
+
|
|
315
|
+
elsif d.object = 'tax' and d.change = 'missing' then
|
|
316
|
+
select * into v_template from tax_templates where country = v_country and code = d.key;
|
|
317
|
+
insert into taxes (company_id, code, name, description, amount_type, amount,
|
|
318
|
+
applies_to, treatment, country, valid_from, valid_to,
|
|
319
|
+
legal_reference, vat_category, exemption_code, sequence,
|
|
320
|
+
tax_kind, recoverable, jurisdiction, price_include,
|
|
321
|
+
cash_basis, cash_basis_transition_account_id)
|
|
322
|
+
values (p_company_id, v_template.code, v_template.name, v_template.description,
|
|
323
|
+
v_template.amount_type, v_template.amount, v_template.applies_to,
|
|
324
|
+
v_template.treatment, v_country, v_template.valid_from, v_template.valid_to,
|
|
325
|
+
v_template.legal_reference, v_template.vat_category, v_template.exemption_code,
|
|
326
|
+
v_template.sequence, v_template.tax_kind, v_template.recoverable,
|
|
327
|
+
v_template.jurisdiction, v_template.price_include, v_template.cash_basis,
|
|
328
|
+
account_id_by_code(p_company_id, v_template.cash_basis_transition_account_code))
|
|
329
|
+
on conflict (company_id, code) do nothing
|
|
330
|
+
returning id into v_tax_id;
|
|
331
|
+
|
|
332
|
+
if v_tax_id is not null then
|
|
333
|
+
insert into tax_postings (tax_id, company_id, document_kind, posting_type,
|
|
334
|
+
factor_percent, account_id, declaration_box,
|
|
335
|
+
box_factor_percent, report_code, sequence)
|
|
336
|
+
select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
|
|
337
|
+
tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
|
|
338
|
+
tp.declaration_box, tp.box_factor_percent, tp.report_code, tp.sequence
|
|
339
|
+
from tax_posting_templates tp
|
|
340
|
+
where tp.tax_template_id = v_template.id
|
|
341
|
+
order by tp.sequence;
|
|
342
|
+
end if;
|
|
343
|
+
|
|
344
|
+
elsif d.object = 'tax' and d.change = 'valid_to' then
|
|
345
|
+
update taxes x
|
|
346
|
+
set valid_to = t.valid_to
|
|
347
|
+
from tax_templates t
|
|
348
|
+
where t.country = v_country and t.code = x.code
|
|
349
|
+
and x.company_id = p_company_id and x.code = d.key;
|
|
350
|
+
|
|
351
|
+
elsif d.object = 'tax' and d.change = 'differs' then
|
|
352
|
+
update taxes x
|
|
353
|
+
set name = t.name, description = t.description, amount = t.amount,
|
|
354
|
+
amount_type = t.amount_type, applies_to = t.applies_to,
|
|
355
|
+
treatment = t.treatment, valid_from = t.valid_from,
|
|
356
|
+
legal_reference = t.legal_reference, vat_category = t.vat_category,
|
|
357
|
+
exemption_code = t.exemption_code, tax_kind = t.tax_kind,
|
|
358
|
+
recoverable = t.recoverable, jurisdiction = t.jurisdiction,
|
|
359
|
+
price_include = t.price_include, cash_basis = t.cash_basis
|
|
360
|
+
from tax_templates t
|
|
361
|
+
where t.country = v_country and t.code = x.code
|
|
362
|
+
and x.company_id = p_company_id and x.code = d.key;
|
|
363
|
+
|
|
364
|
+
elsif d.object = 'tax_posting' and d.change = 'differs' then
|
|
365
|
+
select id into v_tax_id from taxes where company_id = p_company_id and code = d.key;
|
|
366
|
+
delete from tax_postings where tax_id = v_tax_id;
|
|
367
|
+
insert into tax_postings (tax_id, company_id, document_kind, posting_type,
|
|
368
|
+
factor_percent, account_id, declaration_box,
|
|
369
|
+
box_factor_percent, report_code, sequence)
|
|
370
|
+
select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
|
|
371
|
+
tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
|
|
372
|
+
tp.declaration_box, tp.box_factor_percent, tp.report_code, tp.sequence
|
|
373
|
+
from tax_posting_templates tp
|
|
374
|
+
join tax_templates t on t.id = tp.tax_template_id
|
|
375
|
+
where t.country = v_country and t.code = d.key
|
|
376
|
+
order by tp.sequence;
|
|
377
|
+
|
|
378
|
+
else
|
|
379
|
+
-- A difference this function does not know how to apply is listed
|
|
380
|
+
-- rather than guessed at. Raising here would make a new kind of
|
|
381
|
+
-- difference break every upgrade.
|
|
382
|
+
v_listed := v_listed || jsonb_build_object('object', d.object, 'key', d.key,
|
|
383
|
+
'change', d.change, 'rule', d.rule,
|
|
384
|
+
'detail', d.detail);
|
|
385
|
+
continue;
|
|
386
|
+
end if;
|
|
387
|
+
|
|
388
|
+
v_applied := v_applied || jsonb_build_object('object', d.object, 'key', d.key,
|
|
389
|
+
'change', d.change, 'rule', d.rule,
|
|
390
|
+
'detail', d.detail);
|
|
391
|
+
end loop;
|
|
392
|
+
|
|
393
|
+
-- The version moves only when nothing is left waiting. A company that still
|
|
394
|
+
-- holds a difference it has not decided on has not finished upgrading, and
|
|
395
|
+
-- saying otherwise would hide the difference at the next run.
|
|
396
|
+
if jsonb_array_length(v_listed) = 0 then
|
|
397
|
+
update company_packs
|
|
398
|
+
set version = v_to, upgraded_at = now()
|
|
399
|
+
where company_id = p_company_id and country = v_country;
|
|
400
|
+
end if;
|
|
401
|
+
|
|
402
|
+
perform audit_record(
|
|
403
|
+
p_company_id, 'company_packs', null, v_country, 'update', 'pack_upgraded',
|
|
404
|
+
jsonb_build_object('version', v_from),
|
|
405
|
+
jsonb_build_object('version', case when jsonb_array_length(v_listed) = 0 then v_to else v_from end,
|
|
406
|
+
'pack_version', v_to,
|
|
407
|
+
'chart_code', v_chart,
|
|
408
|
+
'applied', v_applied,
|
|
409
|
+
'listed', v_listed,
|
|
410
|
+
'never_applied', v_refused));
|
|
411
|
+
|
|
412
|
+
return jsonb_build_object(
|
|
413
|
+
'company_id', p_company_id,
|
|
414
|
+
'country', v_country,
|
|
415
|
+
'chart_code', v_chart,
|
|
416
|
+
'from_version', v_from,
|
|
417
|
+
'to_version', v_to,
|
|
418
|
+
'version_moved', jsonb_array_length(v_listed) = 0,
|
|
419
|
+
'applied', v_applied,
|
|
420
|
+
'listed', v_listed,
|
|
421
|
+
'never_applied', v_refused);
|
|
422
|
+
end;
|
|
423
|
+
$$;
|
|
424
|
+
|
|
425
|
+
comment on function pack_upgrade(uuid, char, boolean) is
|
|
426
|
+
'Moves a company to the country pack version this installation holds: additions copied in, closed validities applied, everything else listed and left alone unless the caller asks for it. Records what it did in the audit trail. The recorded version moves only when nothing is left waiting.';
|
|
427
|
+
|
|
428
|
+
revoke execute on all functions in schema public from public;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
-- Ekwo OS — an amount is rounded at the decimals of its currency, by the
|
|
2
|
+
-- method of its country.
|
|
3
|
+
--
|
|
4
|
+
-- Two columns had been filled by every pack and read by nothing:
|
|
5
|
+
-- `currencies.decimal_places`, which says the yen has none and the dinar has
|
|
6
|
+
-- three, and `country_defaults.rounding_method`, which says how a country
|
|
7
|
+
-- turns a half into a figure. Everything in the schema rounded with
|
|
8
|
+
-- `round(x, 2)` instead — fifty-one times, in eighteen functions, a view and
|
|
9
|
+
-- a generated column. Two decimals is right for every currency the packs
|
|
10
|
+
-- carry and wrong for about a quarter of the world's; and "half up" was not
|
|
11
|
+
-- a decision anywhere, it was what `round()` happens to do.
|
|
12
|
+
--
|
|
13
|
+
-- What a hard-coded two costs is not theoretical. A yen invoice of 1 234,5
|
|
14
|
+
-- would be booked at 1 234,50 and settled at 1 235, and the cent that does
|
|
15
|
+
-- not exist in that currency would sit on a suspense account for ever. The
|
|
16
|
+
-- rule is the same one the three copies of `rounding.ts` already carry: half
|
|
17
|
+
-- up on the absolute value, at the currency's decimals, so that a credit note
|
|
18
|
+
-- is its invoice with the sign flipped.
|
|
19
|
+
--
|
|
20
|
+
-- This migration adds the vocabulary; the next one makes the schema speak it.
|
|
21
|
+
--
|
|
22
|
+
-- * `money_rounding` is the pair that answers "how is this amount
|
|
23
|
+
-- written": the decimals of a currency and the method of a country.
|
|
24
|
+
-- They travel together because neither is an answer on its own.
|
|
25
|
+
-- * `round_amount(amount, rounding)` is the arithmetic, and the only place
|
|
26
|
+
-- in the whole schema where a rounding method is named. It looks nothing
|
|
27
|
+
-- up, which is what lets it be immutable and used in a view.
|
|
28
|
+
-- * `rounding_of(company, currency)` is the lookup, and the only place
|
|
29
|
+
-- where the two columns are read. A currency it does not know, a company
|
|
30
|
+
-- it does not know, a country with no model: each is refused by name.
|
|
31
|
+
-- None of the three is guessed, and there is no fallback currency and no
|
|
32
|
+
-- fallback country anywhere in it.
|
|
33
|
+
-- * `currency_unit(rounding)` is the smallest amount the currency has — a
|
|
34
|
+
-- cent in the euro, a yen in the yen. The tolerances that used to be
|
|
35
|
+
-- written `0.005` are half a unit, which is the thing they always meant.
|
|
36
|
+
-- * `amount_text_format(rounding)` is the `to_char` mask that goes with it,
|
|
37
|
+
-- for the two places an amount is put into a sentence.
|
|
38
|
+
|
|
39
|
+
-- ---------------------------------------------------------------------------
|
|
40
|
+
-- The pair
|
|
41
|
+
-- ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
create type money_rounding as (
|
|
44
|
+
decimals smallint,
|
|
45
|
+
method rounding_method
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
comment on type money_rounding is
|
|
49
|
+
'How an amount is written: the decimals of its currency and the rounding method of its country. Resolved by rounding_of(), applied by round_amount().';
|
|
50
|
+
|
|
51
|
+
-- ---------------------------------------------------------------------------
|
|
52
|
+
-- The arithmetic
|
|
53
|
+
-- ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
create or replace function round_amount(p_amount numeric, p_rounding money_rounding)
|
|
56
|
+
returns numeric
|
|
57
|
+
language plpgsql
|
|
58
|
+
immutable
|
|
59
|
+
as $$
|
|
60
|
+
declare
|
|
61
|
+
v_decimals smallint;
|
|
62
|
+
v_unit numeric;
|
|
63
|
+
v_value numeric;
|
|
64
|
+
v_floor numeric;
|
|
65
|
+
v_rest numeric;
|
|
66
|
+
v_result numeric;
|
|
67
|
+
begin
|
|
68
|
+
if p_amount is null then
|
|
69
|
+
return null;
|
|
70
|
+
end if;
|
|
71
|
+
|
|
72
|
+
v_decimals := (p_rounding).decimals;
|
|
73
|
+
if v_decimals is null then
|
|
74
|
+
raise exception 'no_rounding_scale: an amount cannot be rounded without the decimals of its currency';
|
|
75
|
+
end if;
|
|
76
|
+
if (p_rounding).method is null then
|
|
77
|
+
raise exception 'no_rounding_method: an amount cannot be rounded without the method of its country';
|
|
78
|
+
end if;
|
|
79
|
+
|
|
80
|
+
-- Everything happens on the absolute value and the sign is put back at the
|
|
81
|
+
-- end, so that the rounding of -x is the rounding of x signed back. A rule
|
|
82
|
+
-- that is not symmetric puts a unit between an invoice and its credit note.
|
|
83
|
+
v_unit := power(10::numeric, (-v_decimals)::numeric);
|
|
84
|
+
v_value := abs(p_amount);
|
|
85
|
+
v_floor := trunc(v_value, v_decimals);
|
|
86
|
+
v_rest := v_value - v_floor;
|
|
87
|
+
|
|
88
|
+
case (p_rounding).method
|
|
89
|
+
when 'half_up' then
|
|
90
|
+
-- Half away from zero, which on an absolute value is half up.
|
|
91
|
+
v_result := round(v_value, v_decimals);
|
|
92
|
+
when 'down' then
|
|
93
|
+
v_result := v_floor;
|
|
94
|
+
when 'up' then
|
|
95
|
+
v_result := case when v_rest = 0 then v_floor else v_floor + v_unit end;
|
|
96
|
+
when 'half_even' then
|
|
97
|
+
if v_rest * 2 > v_unit then
|
|
98
|
+
v_result := v_floor + v_unit;
|
|
99
|
+
elsif v_rest * 2 < v_unit then
|
|
100
|
+
v_result := v_floor;
|
|
101
|
+
else
|
|
102
|
+
-- Exactly half: towards the even neighbour, which is what "banker's
|
|
103
|
+
-- rounding" means and what a country that declares it is asking for.
|
|
104
|
+
v_result := case when (v_floor / v_unit) % 2 = 0 then v_floor else v_floor + v_unit end;
|
|
105
|
+
end if;
|
|
106
|
+
end case;
|
|
107
|
+
|
|
108
|
+
-- The scale of the answer is the currency's, whatever the arithmetic left
|
|
109
|
+
-- behind: an amount in yen is written with no decimals at all.
|
|
110
|
+
v_result := round(v_result, v_decimals);
|
|
111
|
+
return case when p_amount < 0 then -v_result else v_result end;
|
|
112
|
+
end;
|
|
113
|
+
$$;
|
|
114
|
+
|
|
115
|
+
comment on function round_amount(numeric, money_rounding) is
|
|
116
|
+
'Rounds an amount at the decimals of its currency, by the method of its country. The only function of the schema that names a rounding method; every other one asks rounding_of() and passes the answer here.';
|
|
117
|
+
|
|
118
|
+
-- The smallest amount this currency has. A cent in the euro, a yen in the
|
|
119
|
+
-- yen, a millime in the dinar.
|
|
120
|
+
create or replace function currency_unit(p_rounding money_rounding)
|
|
121
|
+
returns numeric
|
|
122
|
+
language sql
|
|
123
|
+
immutable
|
|
124
|
+
as $$
|
|
125
|
+
select round(power(10::numeric, (-(p_rounding).decimals)::numeric), (p_rounding).decimals);
|
|
126
|
+
$$;
|
|
127
|
+
|
|
128
|
+
comment on function currency_unit(money_rounding) is
|
|
129
|
+
'The smallest amount a currency has: a cent in the euro, a yen in the yen. A tolerance is written as a fraction of this rather than as a fraction of a cent.';
|
|
130
|
+
|
|
131
|
+
-- The mask `to_char` needs to write an amount of this currency. A yen result
|
|
132
|
+
-- reported as "1234.00" claims a precision the currency does not have.
|
|
133
|
+
create or replace function amount_text_format(p_rounding money_rounding)
|
|
134
|
+
returns text
|
|
135
|
+
language sql
|
|
136
|
+
immutable
|
|
137
|
+
as $$
|
|
138
|
+
select 'FM9999999999999990'
|
|
139
|
+
|| case when (p_rounding).decimals > 0
|
|
140
|
+
then '.' || repeat('0', (p_rounding).decimals)
|
|
141
|
+
else '' end;
|
|
142
|
+
$$;
|
|
143
|
+
|
|
144
|
+
comment on function amount_text_format(money_rounding) is
|
|
145
|
+
'The to_char mask an amount of this currency is written with. Two decimals for the euro, none for the yen, three for the dinar.';
|
|
146
|
+
|
|
147
|
+
-- ---------------------------------------------------------------------------
|
|
148
|
+
-- The lookup
|
|
149
|
+
-- ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
create or replace function rounding_of(p_company_id uuid, p_currency_code text default null)
|
|
152
|
+
returns money_rounding
|
|
153
|
+
language plpgsql
|
|
154
|
+
stable
|
|
155
|
+
as $$
|
|
156
|
+
declare
|
|
157
|
+
v_currency char(3);
|
|
158
|
+
v_country char(2);
|
|
159
|
+
v_out money_rounding;
|
|
160
|
+
begin
|
|
161
|
+
select coalesce(p_currency_code, c.currency_code), coalesce(c.fiscal_country, c.country)
|
|
162
|
+
into v_currency, v_country
|
|
163
|
+
from companies c
|
|
164
|
+
where c.id = p_company_id;
|
|
165
|
+
|
|
166
|
+
if not found then
|
|
167
|
+
raise exception 'unknown_company: % does not exist, and an amount is rounded by the country and the currency of a company', p_company_id;
|
|
168
|
+
end if;
|
|
169
|
+
|
|
170
|
+
-- The decimals are the currency's, from the instance's own reference data.
|
|
171
|
+
select cu.decimal_places into v_out.decimals
|
|
172
|
+
from currencies cu where cu.code = v_currency;
|
|
173
|
+
if v_out.decimals is null then
|
|
174
|
+
raise exception 'unknown_currency: % is not a currency of this installation, so the decimals an amount is written with are unknown', v_currency;
|
|
175
|
+
end if;
|
|
176
|
+
|
|
177
|
+
-- The method is the country's, from the pack. A pack that says nothing
|
|
178
|
+
-- about rounding got the column's own default when it was compiled, which
|
|
179
|
+
-- is a decision taken once in the schema and not a country borrowed here.
|
|
180
|
+
select cd.rounding_method into v_out.method
|
|
181
|
+
from country_defaults cd where cd.country = v_country;
|
|
182
|
+
if v_out.method is null then
|
|
183
|
+
raise exception 'no_country_model: this installation carries no country model for %, so the method its amounts are rounded by is unknown', v_country;
|
|
184
|
+
end if;
|
|
185
|
+
|
|
186
|
+
return v_out;
|
|
187
|
+
end;
|
|
188
|
+
$$;
|
|
189
|
+
|
|
190
|
+
comment on function rounding_of(uuid, text) is
|
|
191
|
+
'How this company writes an amount in this currency, or in its own when none is named. The only place currencies.decimal_places and country_defaults.rounding_method are read.';
|
|
192
|
+
|
|
193
|
+
-- These three are read by every posting and every report, so they are open to
|
|
194
|
+
-- a signed-in member and to nobody else. `anon` reads the public surface of
|
|
195
|
+
-- an installation and books nothing.
|
|
196
|
+
revoke execute on function round_amount(numeric, money_rounding) from public, anon;
|
|
197
|
+
revoke execute on function currency_unit(money_rounding) from public, anon;
|
|
198
|
+
revoke execute on function amount_text_format(money_rounding) from public, anon;
|
|
199
|
+
revoke execute on function rounding_of(uuid, text) from public, anon;
|
|
200
|
+
|
|
201
|
+
grant execute on function round_amount(numeric, money_rounding) to authenticated, service_role;
|
|
202
|
+
grant execute on function currency_unit(money_rounding) to authenticated, service_role;
|
|
203
|
+
grant execute on function amount_text_format(money_rounding) to authenticated, service_role;
|
|
204
|
+
grant execute on function rounding_of(uuid, text) to authenticated, service_role;
|
|
205
|
+
|
|
206
|
+
revoke execute on all functions in schema public from public;
|