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,144 @@
|
|
|
1
|
+
-- Ekwo OS — the words a country with no value added tax needed.
|
|
2
|
+
--
|
|
3
|
+
-- The first pack of a country outside the common system of VAT — sales tax
|
|
4
|
+
-- and use tax, `kind` `sales_tax`, nothing recoverable at any stage — asked
|
|
5
|
+
-- the vocabulary three questions it could not answer. Two of them are here;
|
|
6
|
+
-- the third is a rule of `ekwo pack check` and touches no table.
|
|
7
|
+
--
|
|
8
|
+
-- ## A tax the buyer assesses on themselves, which is not a reverse charge
|
|
9
|
+
--
|
|
10
|
+
-- `tax_treatment` had `domestic_reverse_charge` for the case where national
|
|
11
|
+
-- law moves the liability for **the value added tax** from a supplier
|
|
12
|
+
-- established in the buyer's country to the buyer, and
|
|
13
|
+
-- `foreign_services_received` for the general business-to-business rule of
|
|
14
|
+
-- articles 44 and 196 of Directive 2006/112/EC. Both are operations of a
|
|
15
|
+
-- system in which the buyer, having charged themselves, deducts the same
|
|
16
|
+
-- amount at the other end; both sit behind an invoice a supplier issued and
|
|
17
|
+
-- was relieved on.
|
|
18
|
+
--
|
|
19
|
+
-- California's use tax is none of that. Section 6201 of the Revenue and
|
|
20
|
+
-- Taxation Code imposes an excise tax on the storage, use or other consumption
|
|
21
|
+
-- in the State of tangible personal property purchased from any retailer, and
|
|
22
|
+
-- section 6202(a) makes the person storing, using or consuming it liable for
|
|
23
|
+
-- it. There is no exempt supply behind it, no supplier who was relieved of
|
|
24
|
+
-- anything, no recapitulative statement, and nothing at all is recovered: the
|
|
25
|
+
-- tax is a cost, which is why the same document carries a `tax_on_base`
|
|
26
|
+
-- posting. `packs/us/` said `domestic_reverse_charge` because that was the
|
|
27
|
+
-- only word for a liability sitting with the buyer, and wrote into its own
|
|
28
|
+
-- `legal_reference` that it was not one — a pack contradicting itself in
|
|
29
|
+
-- prose, which `docs/international.md` recorded as a gap the day the pack
|
|
30
|
+
-- landed.
|
|
31
|
+
--
|
|
32
|
+
-- `self_assessed` is that word: **a tax a buyer owes directly to an
|
|
33
|
+
-- administration under that administration's own law, and computes and
|
|
34
|
+
-- declares themselves.** It says nothing about a supplier, because there may
|
|
35
|
+
-- be no supplier the levying State can reach, and nothing about recovery,
|
|
36
|
+
-- because that is what the postings say.
|
|
37
|
+
--
|
|
38
|
+
-- It is not refused inside the common system, and the restraint is
|
|
39
|
+
-- deliberate: a Member State levying a duty of its own that a buyer
|
|
40
|
+
-- self-assesses would be describing this and not article 194. What tells the
|
|
41
|
+
-- two apart is the law each tax cites, which every tax already carries.
|
|
42
|
+
--
|
|
43
|
+
-- The value is placed beside `domestic_reverse_charge` so that the two
|
|
44
|
+
-- mechanisms in which a buyer charges themselves read together, in the enum,
|
|
45
|
+
-- in `TAX_TREATMENTS` and in the pack schema — the one ordering
|
|
46
|
+
-- `tests/vat_codes.test.ts` holds across the three — and so that the four
|
|
47
|
+
-- intra-Community supplies stay contiguous after it.
|
|
48
|
+
--
|
|
49
|
+
-- Nothing in the ledger branches on a treatment, here or anywhere. What a tax
|
|
50
|
+
-- *does* is said by its postings. What this value changes is what a pack is
|
|
51
|
+
-- allowed to say about the tax's EN 16931 category — a buyer assessing a tax
|
|
52
|
+
-- on themselves under a State's own law holds no invoice the standard
|
|
53
|
+
-- governs, so there is no category of a supplier's to record.
|
|
54
|
+
--
|
|
55
|
+
-- ## What an exemption depends on
|
|
56
|
+
--
|
|
57
|
+
-- A sale for resale is untaxed because the seller holds a resale certificate:
|
|
58
|
+
-- section 6091 presumes every receipt taxable until they take one from the
|
|
59
|
+
-- purchaser, and Regulation 1668 says what it has to contain. A seller must
|
|
60
|
+
-- collect in a State where they have economic nexus, which since *South
|
|
61
|
+
-- Dakota v. Wayfair* is a running total of sales into it. A sale of food is
|
|
62
|
+
-- untaxed under section 6359 unless it is hot, or carbonated, or alcoholic, or
|
|
63
|
+
-- eaten where admission was charged.
|
|
64
|
+
--
|
|
65
|
+
-- Three exemptions, three answers that are not in the books, and one column of
|
|
66
|
+
-- `tax_templates` that carried none of it: a pack could state the code a
|
|
67
|
+
-- bookkeeper reaches for **once the answer is known** and could not say what
|
|
68
|
+
-- the question was. A reader of the chart saw three zero-rated codes and one
|
|
69
|
+
-- long sentence each.
|
|
70
|
+
--
|
|
71
|
+
-- `conditions` is the smallest thing that says it: **what the exemption turns
|
|
72
|
+
-- on, and never how to evaluate it.** A closed vocabulary of five words, no
|
|
73
|
+
-- value beside any of them — no threshold amount, no certificate number, no
|
|
74
|
+
-- operator and no expression — because a pack that could carry the test would
|
|
75
|
+
-- be a pack that executes, which is the one thing this format exists not to
|
|
76
|
+
-- be. The amount of a threshold and the contents of a certificate are in the
|
|
77
|
+
-- article the tax already cites; what is recorded here is that there **is** a
|
|
78
|
+
-- certificate, and that there **is** a threshold, so that an application can
|
|
79
|
+
-- put the question to a human being instead of pretending to answer it.
|
|
80
|
+
--
|
|
81
|
+
-- `buyer_certificate` the buyer hands the seller a document the seller has
|
|
82
|
+
-- to hold and be able to produce.
|
|
83
|
+
-- `buyer_status` a quality of the buyer the statute names — a
|
|
84
|
+
-- government, a body the law exempts as such.
|
|
85
|
+
-- `transport_evidence` proof that what was sold went where the exemption
|
|
86
|
+
-- requires it to have gone.
|
|
87
|
+
-- `seller_threshold` a running total the seller crossed, or has not.
|
|
88
|
+
-- `supply_nature` what is supplied, classified more finely than any
|
|
89
|
+
-- ledger holds it.
|
|
90
|
+
--
|
|
91
|
+
-- It stops at `tax_templates` and does not reach `taxes`, exactly as
|
|
92
|
+
-- `source_key` does and for the same reason: this is the pack's transcription
|
|
93
|
+
-- of a country's rule, not a property of the tax a company went on to edit.
|
|
94
|
+
-- A company's `taxes` row carries `country` and `code`, which is the join, and
|
|
95
|
+
-- nothing in the ledger reads either column.
|
|
96
|
+
--
|
|
97
|
+
-- Two halves of the American note are **not** closed by this and are written
|
|
98
|
+
-- up in `docs/international.md`: there is still no place on a contact for a
|
|
99
|
+
-- certificate and its validity, and no place anywhere for a rolling total per
|
|
100
|
+
-- territory. Both are larger than the pack format — one is document
|
|
101
|
+
-- management and the other is reporting — and neither is invented here.
|
|
102
|
+
--
|
|
103
|
+
-- ## Which entry of a register is the list BT-121 comes from
|
|
104
|
+
--
|
|
105
|
+
-- The third change is one line of a comment. `vatRegime()` used to take the
|
|
106
|
+
-- **first** entry of `certification.sources` whose `kind` was `standard` and
|
|
107
|
+
-- treat its title as the published list an exemption reason code outside the
|
|
108
|
+
-- Union may be taken from. `standard` means "a technical norm or code list",
|
|
109
|
+
-- which the FASB Accounting Standards Codification and FRS 102 both are, so
|
|
110
|
+
-- naming an accounting standard silently authorised a reason code on any tax
|
|
111
|
+
-- of the pack. No pack carries one and nothing wrong was ever emitted; the
|
|
112
|
+
-- mechanism was a trap all the same. An entry now says so of itself, with
|
|
113
|
+
-- `reason_codes`, and `ekwo pack check` refuses a second one and refuses the
|
|
114
|
+
-- flag on an entry that is not a `standard`. The register is a jsonb column,
|
|
115
|
+
-- so the shape it documents is the only thing here that moves.
|
|
116
|
+
--
|
|
117
|
+
-- A value added to an enum cannot be used in the transaction that adds it.
|
|
118
|
+
-- Nothing below uses `self_assessed`; the seeds that do are applied after.
|
|
119
|
+
|
|
120
|
+
alter type tax_treatment add value 'self_assessed' after 'domestic_reverse_charge';
|
|
121
|
+
|
|
122
|
+
-- What an exemption or a rate turns on, where the answer is not in the books.
|
|
123
|
+
-- A closed vocabulary and never a test: no value, no operator, no expression.
|
|
124
|
+
create type tax_condition as enum (
|
|
125
|
+
'buyer_certificate',
|
|
126
|
+
'buyer_status',
|
|
127
|
+
'transport_evidence',
|
|
128
|
+
'seller_threshold',
|
|
129
|
+
'supply_nature'
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
alter table tax_templates
|
|
133
|
+
add column if not exists conditions tax_condition[] not null default '{}'::tax_condition[];
|
|
134
|
+
|
|
135
|
+
comment on column tax_templates.conditions is
|
|
136
|
+
'What this tax turns on that the ledger cannot see, from a closed vocabulary: buyer_certificate, buyer_status, transport_evidence, seller_threshold, supply_nature. It says what the question is, never how to answer it — there is no value, no operator and no expression here, and the article that sets a threshold or prescribes a certificate is in legal_reference. Written by the generated seed; read by nothing in the ledger.';
|
|
137
|
+
|
|
138
|
+
comment on column country_packs.sources is
|
|
139
|
+
'Register of the texts this pack was built from, in the pack''s own order: [{key, title, publisher, url, consulted_on, kind, reason_codes}]. `kind` is one of law, regulation, form, standard, portal, guidance. `reason_codes` is true on the one entry — at most one, and a `standard` — that publishes the list an exemption reason code of this country comes from; absent everywhere else. Written by the generated seed; never a copy of the text itself.';
|
|
140
|
+
|
|
141
|
+
-- No table, view or function is created. A type is reachable by whoever can
|
|
142
|
+
-- already read the column that uses it, and a column added to a table is
|
|
143
|
+
-- reachable by whoever could already select that table, under the policies it
|
|
144
|
+
-- already has. So there is nothing to grant and nothing to revoke.
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
-- Ekwo OS — a box of a declaration can be a rate applied to another box, and
|
|
2
|
+
-- the order a form is printed in stops deciding the order it is worked out in.
|
|
3
|
+
--
|
|
4
|
+
-- Two changes, and they are one change: the second is what the first needs to
|
|
5
|
+
-- be safe.
|
|
6
|
+
--
|
|
7
|
+
-- ---------------------------------------------------------------------------
|
|
8
|
+
-- 1. `rate` and `rate_of_box`
|
|
9
|
+
-- ---------------------------------------------------------------------------
|
|
10
|
+
--
|
|
11
|
+
-- Until now a computed box was a list of boxes to add and a list to subtract.
|
|
12
|
+
-- That covers every European return this repository carries, because a
|
|
13
|
+
-- European return reports a tax the ledger already holds: the amount was
|
|
14
|
+
-- worked out document by document, posted, and the form only adds it up.
|
|
15
|
+
--
|
|
16
|
+
-- A form that is not a value added tax return does not work that way.
|
|
17
|
+
-- CDTFA-401-A states four of its lines as a multiplication in as many words:
|
|
18
|
+
-- line 13 is line 12 times 0.06, line 14 is line 12 times 0.0025, line 15 is
|
|
19
|
+
-- line 12 times 0.01, and Sections C and D are a base times 0.05 and times
|
|
20
|
+
-- 0.039375. The taxable total is worked out once, for the period, and the
|
|
21
|
+
-- rates are applied to it.
|
|
22
|
+
--
|
|
23
|
+
-- **A rate is not an expression.** It is the one arithmetic a declaration form
|
|
24
|
+
-- actually writes out, and it is said here with two named fields and no
|
|
25
|
+
-- syntax: `rate` is a percentage and `rate_of_box` is the box it applies to.
|
|
26
|
+
-- There is still nothing a pack can execute, nothing to parse, and a reviewer
|
|
27
|
+
-- reads `6.00 of box 12` in the diff. The alternative — an expression language
|
|
28
|
+
-- — is the thing `20260912090407` refused and this does not reopen it.
|
|
29
|
+
--
|
|
30
|
+
-- Three rules hold it in place, as constraints rather than as conventions:
|
|
31
|
+
--
|
|
32
|
+
-- * a rate names a box, and a box named by a rate needs a rate: the pair is
|
|
33
|
+
-- declared together or not at all;
|
|
34
|
+
-- * only a computed box carries one, which is the rule `plus` and `minus`
|
|
35
|
+
-- already follow — `kind` says how a box gets its amount, and a box the
|
|
36
|
+
-- ledger fills cannot also be derived;
|
|
37
|
+
-- * a rate and a list are two ways of computing one box, so a box takes one
|
|
38
|
+
-- or the other. A box that was both would be a formula, quietly.
|
|
39
|
+
--
|
|
40
|
+
-- A box that names itself is refused here too, by name, rather than left to
|
|
41
|
+
-- the evaluator's cycle detection: `rate_of_box` is one reference, so the
|
|
42
|
+
-- shortest cycle is visible to a `check`.
|
|
43
|
+
--
|
|
44
|
+
-- ---------------------------------------------------------------------------
|
|
45
|
+
-- 2. `print_sequence`
|
|
46
|
+
-- ---------------------------------------------------------------------------
|
|
47
|
+
--
|
|
48
|
+
-- `sequence` has meant two things at once since the boxes became data: the
|
|
49
|
+
-- order a form prints its boxes in, and the order they are worked out in.
|
|
50
|
+
-- Three packs have now paid for that. Luxembourg's eCDF return prints a
|
|
51
|
+
-- subtotal above the boxes it adds up; Estonia escaped it by luck; and
|
|
52
|
+
-- CDTFA-401-A prints line 11 on page 1 and computes it from Sections A and B
|
|
53
|
+
-- on page 3, so `packs/us` orders its form by dependency and the order the
|
|
54
|
+
-- administration prints it in is lost.
|
|
55
|
+
--
|
|
56
|
+
-- `evaluate_totals()` has worked the totals out in the order they depend on
|
|
57
|
+
-- each other since `20260912104719`, so the evaluation half of the conflation
|
|
58
|
+
-- was already fiction: what remained was a rule in `ekwo pack check` refusing
|
|
59
|
+
-- a total that names a total declared after it, which forced a pack to spend
|
|
60
|
+
-- its one ordering field on the evaluator. That rule goes, replaced by the
|
|
61
|
+
-- cycle check the statements already have, and `print_sequence` carries the
|
|
62
|
+
-- order the form is printed in where it differs from the order the pack
|
|
63
|
+
-- declares its boxes in.
|
|
64
|
+
--
|
|
65
|
+
-- Null means "the same as `sequence`", which is what every pack written before
|
|
66
|
+
-- this said, and `vat_return()` answers the resolved value so a renderer has
|
|
67
|
+
-- one column to order by and never two.
|
|
68
|
+
--
|
|
69
|
+
-- A rate makes the separation necessary rather than merely tidy: a box worked
|
|
70
|
+
-- out from another box has a dependency the form's own print order knows
|
|
71
|
+
-- nothing about, and a pack should not have to choose which of the two it
|
|
72
|
+
-- writes down.
|
|
73
|
+
|
|
74
|
+
-- ---------------------------------------------------------------------------
|
|
75
|
+
-- The columns
|
|
76
|
+
-- ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
alter table tax_report_box_templates
|
|
79
|
+
add column if not exists rate numeric,
|
|
80
|
+
add column if not exists rate_of_box text,
|
|
81
|
+
add column if not exists print_sequence integer;
|
|
82
|
+
|
|
83
|
+
comment on column tax_report_box_templates.rate is
|
|
84
|
+
'Percentage this box applies to the box named by rate_of_box. The one arithmetic a declaration form writes out; not an expression, and never a second way to say plus.';
|
|
85
|
+
comment on column tax_report_box_templates.rate_of_box is
|
|
86
|
+
'The box the rate is applied to, bare or qualified with its kind (`12:total`), resolved the way a plus reference is.';
|
|
87
|
+
comment on column tax_report_box_templates.print_sequence is
|
|
88
|
+
'Where the administration prints this box, when that is not where the pack declares it. Null means the same as sequence: a form prints a subtotal above what it adds up, and the evaluation order is the dependencies and never this.';
|
|
89
|
+
|
|
90
|
+
alter table tax_report_box_templates
|
|
91
|
+
add constraint tax_report_box_templates_rate_names_a_box
|
|
92
|
+
check ((rate is null) = (rate_of_box is null)),
|
|
93
|
+
add constraint tax_report_box_templates_rate_is_a_total
|
|
94
|
+
check (rate is null or kind = 'total'),
|
|
95
|
+
add constraint tax_report_box_templates_rate_or_a_list
|
|
96
|
+
check (rate is null or (plus_boxes = '{}'::text[] and minus_boxes = '{}'::text[])),
|
|
97
|
+
add constraint tax_report_box_templates_rate_is_not_itself
|
|
98
|
+
check (rate_of_box is null or split_part(rate_of_box, ':', 1) <> box);
|
|
99
|
+
|
|
100
|
+
-- ---------------------------------------------------------------------------
|
|
101
|
+
-- evaluate_totals — the one evaluator, which now knows one more shape
|
|
102
|
+
-- ---------------------------------------------------------------------------
|
|
103
|
+
--
|
|
104
|
+
-- Replaced whole, because a function is replaced whole in PostgreSQL. The
|
|
105
|
+
-- signature does not move, so there is nothing to drop and nothing to grant
|
|
106
|
+
-- again. Everything but the rate is `20260914121200` unchanged.
|
|
107
|
+
--
|
|
108
|
+
-- A formula may now carry `rate` and `rate_of` instead of `plus` and `minus`.
|
|
109
|
+
-- It is the same machine either way: the reference set decides when the
|
|
110
|
+
-- formula is ready, and a pass that settles nothing is still a cycle that says
|
|
111
|
+
-- which keys are in it. A statement passes neither field and reads exactly as
|
|
112
|
+
-- it did.
|
|
113
|
+
|
|
114
|
+
create or replace function evaluate_totals(
|
|
115
|
+
p_values jsonb,
|
|
116
|
+
p_formulas jsonb,
|
|
117
|
+
p_rounding money_rounding,
|
|
118
|
+
p_keep_zero boolean default false
|
|
119
|
+
)
|
|
120
|
+
returns jsonb
|
|
121
|
+
language plpgsql
|
|
122
|
+
immutable
|
|
123
|
+
as $$
|
|
124
|
+
declare
|
|
125
|
+
v_values jsonb := coalesce(p_values, '{}'::jsonb);
|
|
126
|
+
v_out jsonb := '{}'::jsonb;
|
|
127
|
+
v_done jsonb := '{}'::jsonb;
|
|
128
|
+
v_all jsonb := coalesce(p_formulas, '[]'::jsonb);
|
|
129
|
+
v_left integer;
|
|
130
|
+
v_settled integer;
|
|
131
|
+
v_ready boolean;
|
|
132
|
+
v_amount numeric;
|
|
133
|
+
v_part numeric;
|
|
134
|
+
v_refs text[];
|
|
135
|
+
v_ref text;
|
|
136
|
+
v_key text;
|
|
137
|
+
v_rate numeric;
|
|
138
|
+
v_of text;
|
|
139
|
+
v_stuck text;
|
|
140
|
+
f jsonb;
|
|
141
|
+
begin
|
|
142
|
+
select count(*) into v_left from jsonb_array_elements(v_all);
|
|
143
|
+
|
|
144
|
+
while v_left > 0 loop
|
|
145
|
+
v_settled := 0;
|
|
146
|
+
|
|
147
|
+
for f in
|
|
148
|
+
select t.x from jsonb_array_elements(v_all) with ordinality as t(x, ord)
|
|
149
|
+
order by coalesce((t.x ->> 'sequence')::integer, 0), t.ord
|
|
150
|
+
loop
|
|
151
|
+
v_key := f ->> 'key';
|
|
152
|
+
if v_done ? v_key then
|
|
153
|
+
continue;
|
|
154
|
+
end if;
|
|
155
|
+
|
|
156
|
+
v_rate := (f ->> 'rate')::numeric;
|
|
157
|
+
v_of := f ->> 'rate_of';
|
|
158
|
+
|
|
159
|
+
-- Every reference this formula reads, whichever shape it is written in.
|
|
160
|
+
-- A rate has exactly one, and it is held to the same readiness rule as a
|
|
161
|
+
-- member of a plus list: a box worked out from a box has to wait for it.
|
|
162
|
+
select coalesce(array_agg(e.value), '{}'::text[]) into v_refs
|
|
163
|
+
from (
|
|
164
|
+
select jsonb_array_elements_text(coalesce(f -> 'plus', '[]'::jsonb)) as value
|
|
165
|
+
union all
|
|
166
|
+
select jsonb_array_elements_text(coalesce(f -> 'minus', '[]'::jsonb))
|
|
167
|
+
union all
|
|
168
|
+
select v_of where v_of is not null
|
|
169
|
+
) as e;
|
|
170
|
+
|
|
171
|
+
-- Ready when every reference that names another formula has been worked
|
|
172
|
+
-- out already.
|
|
173
|
+
v_ready := true;
|
|
174
|
+
foreach v_ref in array v_refs loop
|
|
175
|
+
if exists (
|
|
176
|
+
select 1 from jsonb_array_elements(v_all) as g(x)
|
|
177
|
+
where (g.x ->> 'key') <> v_key
|
|
178
|
+
and not (v_done ? (g.x ->> 'key'))
|
|
179
|
+
and case when strpos(v_ref, ':') > 0
|
|
180
|
+
then (g.x ->> 'key') = replace(v_ref, ':', '|')
|
|
181
|
+
else split_part((g.x ->> 'key'), '|', 1) = v_ref
|
|
182
|
+
end
|
|
183
|
+
) then
|
|
184
|
+
v_ready := false;
|
|
185
|
+
end if;
|
|
186
|
+
end loop;
|
|
187
|
+
if not v_ready then
|
|
188
|
+
continue;
|
|
189
|
+
end if;
|
|
190
|
+
|
|
191
|
+
if v_of is not null then
|
|
192
|
+
-- A rate: one reference, resolved exactly as a plus reference is, and
|
|
193
|
+
-- a percentage applied to it.
|
|
194
|
+
select coalesce(sum(e.value::numeric), 0) into v_part
|
|
195
|
+
from jsonb_each_text(v_values) as e(key, value)
|
|
196
|
+
where case when strpos(v_of, ':') > 0
|
|
197
|
+
then e.key = replace(v_of, ':', '|')
|
|
198
|
+
else split_part(e.key, '|', 1) = v_of
|
|
199
|
+
end;
|
|
200
|
+
v_amount := v_part * v_rate / 100;
|
|
201
|
+
else
|
|
202
|
+
v_amount := 0;
|
|
203
|
+
for v_ref in
|
|
204
|
+
select jsonb_array_elements_text(coalesce(f -> 'plus', '[]'::jsonb))
|
|
205
|
+
loop
|
|
206
|
+
select coalesce(sum(e.value::numeric), 0) into v_part
|
|
207
|
+
from jsonb_each_text(v_values) as e(key, value)
|
|
208
|
+
where case when strpos(v_ref, ':') > 0
|
|
209
|
+
then e.key = replace(v_ref, ':', '|')
|
|
210
|
+
else split_part(e.key, '|', 1) = v_ref
|
|
211
|
+
end;
|
|
212
|
+
v_amount := v_amount + v_part;
|
|
213
|
+
end loop;
|
|
214
|
+
for v_ref in
|
|
215
|
+
select jsonb_array_elements_text(coalesce(f -> 'minus', '[]'::jsonb))
|
|
216
|
+
loop
|
|
217
|
+
select coalesce(sum(e.value::numeric), 0) into v_part
|
|
218
|
+
from jsonb_each_text(v_values) as e(key, value)
|
|
219
|
+
where case when strpos(v_ref, ':') > 0
|
|
220
|
+
then e.key = replace(v_ref, ':', '|')
|
|
221
|
+
else split_part(e.key, '|', 1) = v_ref
|
|
222
|
+
end;
|
|
223
|
+
v_amount := v_amount - v_part;
|
|
224
|
+
end loop;
|
|
225
|
+
end if;
|
|
226
|
+
|
|
227
|
+
-- The floor belongs to the pair it splits — the Belgian 71 and 72, the
|
|
228
|
+
-- French 25 and 28 — so it applies before the sign the caller reads the
|
|
229
|
+
-- line with.
|
|
230
|
+
if coalesce((f ->> 'floor_zero')::boolean, false) then
|
|
231
|
+
v_amount := greatest(v_amount, 0);
|
|
232
|
+
end if;
|
|
233
|
+
v_amount := round_amount(v_amount * coalesce((f ->> 'factor')::numeric, 1), p_rounding);
|
|
234
|
+
|
|
235
|
+
v_values := v_values || jsonb_build_object(v_key, v_amount);
|
|
236
|
+
v_done := v_done || jsonb_build_object(v_key, true);
|
|
237
|
+
if p_keep_zero or v_amount <> 0 then
|
|
238
|
+
v_out := v_out || jsonb_build_object(v_key, v_amount);
|
|
239
|
+
end if;
|
|
240
|
+
v_settled := v_settled + 1;
|
|
241
|
+
v_left := v_left - 1;
|
|
242
|
+
end loop;
|
|
243
|
+
|
|
244
|
+
if v_settled = 0 then
|
|
245
|
+
select string_agg(t.x ->> 'key', ', ' order by t.x ->> 'key') into v_stuck
|
|
246
|
+
from jsonb_array_elements(v_all) as t(x)
|
|
247
|
+
where not (v_done ? (t.x ->> 'key'));
|
|
248
|
+
raise exception 'formula_cycle: these totals depend on each other and on nothing else: %', v_stuck;
|
|
249
|
+
end if;
|
|
250
|
+
end loop;
|
|
251
|
+
|
|
252
|
+
return v_out;
|
|
253
|
+
end;
|
|
254
|
+
$$;
|
|
255
|
+
|
|
256
|
+
comment on function evaluate_totals(jsonb, jsonb, money_rounding, boolean) is
|
|
257
|
+
'Works out the totals of a declaration form or of a financial statement — a plus/minus list, or a rate applied to one other key — in the order they depend on each other. The one place that calculation lives: vat_return() and financial_statement() both call it.';
|
|
258
|
+
|
|
259
|
+
-- ---------------------------------------------------------------------------
|
|
260
|
+
-- vat_return — passes the rate through, and answers the print order
|
|
261
|
+
-- ---------------------------------------------------------------------------
|
|
262
|
+
--
|
|
263
|
+
-- Dropped and recreated rather than replaced, because the returned table gains
|
|
264
|
+
-- a column and PostgreSQL will not change that in place. The three-argument
|
|
265
|
+
-- call every client makes keeps working: `p_report_code` still defaults.
|
|
266
|
+
--
|
|
267
|
+
-- Two changes, and everything else is `20260916094500` unchanged:
|
|
268
|
+
--
|
|
269
|
+
-- * the formulas handed to the evaluator carry `rate` and `rate_of`, so a
|
|
270
|
+
-- box stated as a percentage of another box is worked out by the same
|
|
271
|
+
-- function as every other total;
|
|
272
|
+
-- * every row answers `print_sequence`, resolved — the box's own where it
|
|
273
|
+
-- declares one, `sequence` where it does not — so a renderer orders by one
|
|
274
|
+
-- column. The rows themselves still come back in `sequence` order, which
|
|
275
|
+
-- is the order the pack declares and what every caller reads today.
|
|
276
|
+
|
|
277
|
+
drop function if exists vat_return(uuid, date, date, text);
|
|
278
|
+
|
|
279
|
+
create or replace function vat_return(
|
|
280
|
+
p_company_id uuid,
|
|
281
|
+
p_from date,
|
|
282
|
+
p_to date,
|
|
283
|
+
p_report_code text default null
|
|
284
|
+
)
|
|
285
|
+
returns table (
|
|
286
|
+
box text,
|
|
287
|
+
kind text,
|
|
288
|
+
amount numeric,
|
|
289
|
+
computed boolean,
|
|
290
|
+
name text,
|
|
291
|
+
sequence integer,
|
|
292
|
+
print_sequence integer,
|
|
293
|
+
hidden boolean,
|
|
294
|
+
report_code text
|
|
295
|
+
)
|
|
296
|
+
language plpgsql
|
|
297
|
+
stable
|
|
298
|
+
as $$
|
|
299
|
+
declare
|
|
300
|
+
-- 'box|kind' -> amount, for every box summed from the ledger, then the
|
|
301
|
+
-- totals `evaluate_totals()` derives from them. The key carries the kind
|
|
302
|
+
-- because the French CA3 puts a base and a tax on line 08 and a formula has
|
|
303
|
+
-- to be able to name one of them.
|
|
304
|
+
v_values jsonb := '{}'::jsonb;
|
|
305
|
+
v_formulas jsonb := '[]'::jsonb;
|
|
306
|
+
v_totals jsonb := '{}'::jsonb;
|
|
307
|
+
v_rows jsonb := '[]'::jsonb;
|
|
308
|
+
v_country char(2);
|
|
309
|
+
v_report text;
|
|
310
|
+
v_in char(2);
|
|
311
|
+
v_count integer;
|
|
312
|
+
v_codes text;
|
|
313
|
+
-- How often this company files, what the form accepts, and what the two
|
|
314
|
+
-- dates asked for actually are.
|
|
315
|
+
v_files declaration_period;
|
|
316
|
+
v_accepts declaration_period[];
|
|
317
|
+
v_asked declaration_period;
|
|
318
|
+
-- A declaration figure is not a ledger figure, but it is written in the
|
|
319
|
+
-- same currency and with the same decimals.
|
|
320
|
+
v_round money_rounding;
|
|
321
|
+
r record;
|
|
322
|
+
begin
|
|
323
|
+
select c.fiscal_country into v_country
|
|
324
|
+
from companies c where c.id = p_company_id;
|
|
325
|
+
if not found then
|
|
326
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
327
|
+
end if;
|
|
328
|
+
v_round := rounding_of(p_company_id);
|
|
329
|
+
|
|
330
|
+
-- Which form. The caller names one, or the country files exactly one on
|
|
331
|
+
-- that date. Two and no name is a question only the caller can answer — a
|
|
332
|
+
-- Canadian company files the federal return and the Québec one at once — so
|
|
333
|
+
-- this asks instead of guessing.
|
|
334
|
+
if p_report_code is not null then
|
|
335
|
+
select t.country, t.code into v_in, v_report
|
|
336
|
+
from tax_report_templates t
|
|
337
|
+
where t.code = p_report_code
|
|
338
|
+
and t.valid_from <= p_to
|
|
339
|
+
and (t.valid_to is null or t.valid_to >= p_to)
|
|
340
|
+
order by t.country
|
|
341
|
+
limit 1;
|
|
342
|
+
if v_report is null then
|
|
343
|
+
raise exception 'unknown_tax_report: % is not a declaration form in force on %',
|
|
344
|
+
p_report_code, p_to;
|
|
345
|
+
end if;
|
|
346
|
+
else
|
|
347
|
+
select count(*), min(t.code), string_agg(t.code, ', ' order by t.code)
|
|
348
|
+
into v_count, v_report, v_codes
|
|
349
|
+
from tax_report_templates t
|
|
350
|
+
where t.country = v_country
|
|
351
|
+
and t.is_periodic_return
|
|
352
|
+
and t.valid_from <= p_to
|
|
353
|
+
and (t.valid_to is null or t.valid_to >= p_to);
|
|
354
|
+
if v_count > 1 then
|
|
355
|
+
raise exception 'ambiguous_tax_report: % files several declarations on % (%); name one',
|
|
356
|
+
v_country, p_to, v_codes;
|
|
357
|
+
end if;
|
|
358
|
+
v_in := v_country;
|
|
359
|
+
if v_count = 0 then
|
|
360
|
+
v_report := null; -- no pack for this country: the ledger boxes, and no total.
|
|
361
|
+
end if;
|
|
362
|
+
end if;
|
|
363
|
+
|
|
364
|
+
-- The period asked for against the one this company files **this form** on.
|
|
365
|
+
-- Four things have to hold before this refuses, and the fourth is what keeps
|
|
366
|
+
-- it out of everybody's way: the company has recorded a cadence for this
|
|
367
|
+
-- declaration, the form is filed on that cadence, the dates are themselves a
|
|
368
|
+
-- whole cadence of that form, and the two are not the same. A fortnight, a
|
|
369
|
+
-- half-year, a form the company has recorded nothing about — none of those is
|
|
370
|
+
-- a filing on the wrong cadence, and none of them is refused.
|
|
371
|
+
if v_report is not null then
|
|
372
|
+
v_files := filing_period(p_company_id, v_report);
|
|
373
|
+
if v_files is not null then
|
|
374
|
+
select t.periods into v_accepts
|
|
375
|
+
from tax_report_templates t
|
|
376
|
+
where t.country = v_in and t.code = v_report;
|
|
377
|
+
v_asked := declaration_period_of(p_from, p_to);
|
|
378
|
+
if v_asked is not null
|
|
379
|
+
and v_files = any(v_accepts)
|
|
380
|
+
and v_asked = any(v_accepts)
|
|
381
|
+
and v_asked <> v_files then
|
|
382
|
+
raise exception
|
|
383
|
+
'wrong_declaration_period: this company files % returns on %; % to % is a %',
|
|
384
|
+
v_files, v_report, p_from, p_to, v_asked;
|
|
385
|
+
end if;
|
|
386
|
+
end if;
|
|
387
|
+
end if;
|
|
388
|
+
|
|
389
|
+
-- 1. What the tax postings wrote on the ledger, in every box each of them
|
|
390
|
+
-- names. A line carries the box it is known by; the posting behind it
|
|
391
|
+
-- carries the whole list, which is one box for all but the handful of
|
|
392
|
+
-- forms that print a figure twice. No country rule here either: the
|
|
393
|
+
-- expansion is `unnest`, and which boxes there are is the pack's answer.
|
|
394
|
+
for r in
|
|
395
|
+
with lines as (
|
|
396
|
+
select l.declaration_box as lbox,
|
|
397
|
+
case when l.tax_line then 'tax' else 'base' end as lkind,
|
|
398
|
+
l.tax_id as ltax,
|
|
399
|
+
l.box_amount as lamount
|
|
400
|
+
from entry_lines l
|
|
401
|
+
join entries e on e.id = l.entry_id
|
|
402
|
+
where l.company_id = p_company_id
|
|
403
|
+
and e.state = 'posted'
|
|
404
|
+
and e.entry_date between p_from and p_to
|
|
405
|
+
and l.declaration_box is not null
|
|
406
|
+
),
|
|
407
|
+
-- The posting that wrote the line: which form it is on, and every box it
|
|
408
|
+
-- prints in. A line with no posting behind it — an entry keyed by hand,
|
|
409
|
+
-- a tax a company wrote itself — is on this form and in the one box it
|
|
410
|
+
-- names, which is what the left join leaves.
|
|
411
|
+
sourced as (
|
|
412
|
+
select ln.lbox, ln.lkind, ln.lamount,
|
|
413
|
+
coalesce(p.boxes, array[ln.lbox]) as lboxes
|
|
414
|
+
from lines ln
|
|
415
|
+
left join lateral (
|
|
416
|
+
select min(tp.report_code) as report_code,
|
|
417
|
+
array_agg(distinct b.box) as boxes
|
|
418
|
+
from tax_postings tp
|
|
419
|
+
cross join lateral unnest(tp.declaration_boxes) as b(box)
|
|
420
|
+
where tp.tax_id = ln.ltax
|
|
421
|
+
and tp.declaration_box = ln.lbox
|
|
422
|
+
and tp.posting_type = ln.lkind::tax_posting_type
|
|
423
|
+
) p on true
|
|
424
|
+
-- A box number belongs to one form. A line whose posting names
|
|
425
|
+
-- another form is not on this declaration; one that names none is
|
|
426
|
+
-- the single-return case every European company is in.
|
|
427
|
+
where v_report is null or coalesce(p.report_code, v_report) = v_report
|
|
428
|
+
),
|
|
429
|
+
ledger as (
|
|
430
|
+
select x.box as lbox, s.lkind,
|
|
431
|
+
round_amount(sum(s.lamount), v_round) as lamount
|
|
432
|
+
from sourced s
|
|
433
|
+
cross join lateral unnest(s.lboxes) as x(box)
|
|
434
|
+
group by 1, 2
|
|
435
|
+
having round_amount(sum(s.lamount), v_round) <> 0
|
|
436
|
+
)
|
|
437
|
+
select g.lbox, g.lkind, g.lamount, b.name as lname,
|
|
438
|
+
b.sequence as lsequence,
|
|
439
|
+
coalesce(b.print_sequence, b.sequence) as lprint,
|
|
440
|
+
coalesce(b.hidden, false) as lhidden
|
|
441
|
+
from ledger g
|
|
442
|
+
left join tax_report_box_templates b
|
|
443
|
+
on b.country = v_in and b.report_code = v_report
|
|
444
|
+
and b.box = g.lbox and b.kind = g.lkind
|
|
445
|
+
order by coalesce(b.sequence, 2147483647), g.lbox, g.lkind
|
|
446
|
+
loop
|
|
447
|
+
v_values := v_values || jsonb_build_object(r.lbox || '|' || r.lkind, r.lamount);
|
|
448
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
449
|
+
'box', r.lbox, 'kind', r.lkind, 'amount', r.lamount, 'computed', false,
|
|
450
|
+
'name', r.lname, 'sequence', r.lsequence, 'print_sequence', r.lprint,
|
|
451
|
+
'hidden', r.lhidden, 'report_code', v_report));
|
|
452
|
+
end loop;
|
|
453
|
+
|
|
454
|
+
-- 2. The totals of the form, through the evaluator the statements use. A
|
|
455
|
+
-- return prints what it has, so a nil total is left out of the answer —
|
|
456
|
+
-- and kept in the working set, so a later total that names it reads a
|
|
457
|
+
-- zero rather than a gap.
|
|
458
|
+
if v_report is not null then
|
|
459
|
+
select coalesce(jsonb_agg(jsonb_build_object(
|
|
460
|
+
'key', b.box || '|total', 'plus', to_jsonb(b.plus_boxes),
|
|
461
|
+
'minus', to_jsonb(b.minus_boxes), 'floor_zero', b.floor_zero,
|
|
462
|
+
'rate', b.rate, 'rate_of', b.rate_of_box,
|
|
463
|
+
'sequence', b.sequence
|
|
464
|
+
) order by b.sequence, b.box), '[]'::jsonb)
|
|
465
|
+
into v_formulas
|
|
466
|
+
from tax_report_box_templates b
|
|
467
|
+
where b.country = v_in
|
|
468
|
+
and b.report_code = v_report
|
|
469
|
+
and b.kind = 'total'
|
|
470
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
471
|
+
and (b.valid_to is null or b.valid_to >= p_to);
|
|
472
|
+
|
|
473
|
+
v_totals := evaluate_totals(v_values, v_formulas, v_round, false);
|
|
474
|
+
|
|
475
|
+
for r in
|
|
476
|
+
select b.box as tbox, b.name as tname, b.sequence as tsequence,
|
|
477
|
+
coalesce(b.print_sequence, b.sequence) as tprint, b.hidden as thidden
|
|
478
|
+
from tax_report_box_templates b
|
|
479
|
+
where b.country = v_in
|
|
480
|
+
and b.report_code = v_report
|
|
481
|
+
and b.kind = 'total'
|
|
482
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
483
|
+
and (b.valid_to is null or b.valid_to >= p_to)
|
|
484
|
+
and v_totals ? (b.box || '|total')
|
|
485
|
+
order by b.sequence, b.box
|
|
486
|
+
loop
|
|
487
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
488
|
+
'box', r.tbox, 'kind', 'total',
|
|
489
|
+
'amount', (v_totals ->> (r.tbox || '|total'))::numeric, 'computed', true,
|
|
490
|
+
'name', r.tname, 'sequence', r.tsequence, 'print_sequence', r.tprint,
|
|
491
|
+
'hidden', r.thidden, 'report_code', v_report));
|
|
492
|
+
end loop;
|
|
493
|
+
end if;
|
|
494
|
+
|
|
495
|
+
return query
|
|
496
|
+
select (x ->> 'box')::text,
|
|
497
|
+
(x ->> 'kind')::text,
|
|
498
|
+
(x ->> 'amount')::numeric,
|
|
499
|
+
(x ->> 'computed')::boolean,
|
|
500
|
+
(x ->> 'name')::text,
|
|
501
|
+
(x ->> 'sequence')::integer,
|
|
502
|
+
(x ->> 'print_sequence')::integer,
|
|
503
|
+
(x ->> 'hidden')::boolean,
|
|
504
|
+
(x ->> 'report_code')::text
|
|
505
|
+
from jsonb_array_elements(v_rows) as x;
|
|
506
|
+
end;
|
|
507
|
+
$$;
|
|
508
|
+
|
|
509
|
+
comment on function vat_return(uuid, date, date, text) is
|
|
510
|
+
'Declaration boxes for a period: summed from the ledger, then the totals of the country''s form — a plus/minus list or a rate of another box — worked out by evaluate_totals(), the same evaluator financial_statement() uses. Answers print_sequence beside sequence, because the order a form is printed in is not the order it is worked out in. No country rule lives in this function.';
|
|
511
|
+
|
|
512
|
+
-- Rule 6 of supabase/migrations/README.md: a function created here comes out
|
|
513
|
+
-- executable by PUBLIC otherwise.
|
|
514
|
+
revoke execute on all functions in schema public from public;
|
|
515
|
+
|
|
516
|
+
-- And rule 7: the object this file creates names the roles that may reach it.
|
|
517
|
+
revoke execute on function vat_return(uuid, date, date, text) from public, anon;
|
|
518
|
+
grant execute on function vat_return(uuid, date, date, text) to authenticated, service_role;
|
|
519
|
+
|
|
520
|
+
-- `evaluate_totals()` was replaced in place and keeps the grants
|
|
521
|
+
-- `20260914121200` gave it; the blanket revoke above is never aimed at `anon`,
|
|
522
|
+
-- which holds explicit grants on the policy helpers, so naming it here takes
|
|
523
|
+
-- nothing else away.
|
|
524
|
+
revoke execute on function evaluate_totals(jsonb, jsonb, money_rounding, boolean) from public, anon;
|
|
525
|
+
grant execute on function evaluate_totals(jsonb, jsonb, money_rounding, boolean) to authenticated, service_role;
|