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,795 @@
|
|
|
1
|
+
-- Ekwo OS — a company records one cadence per declaration, not one cadence.
|
|
2
|
+
--
|
|
3
|
+
-- `20260914163943` gave a company a filing cadence and named the column after
|
|
4
|
+
-- the return it was about: `companies.vat_period`. That was one column short of
|
|
5
|
+
-- the fact. A company is subject to **several** declarations, and each of them
|
|
6
|
+
-- has a cadence of its own that the return's does not decide:
|
|
7
|
+
--
|
|
8
|
+
-- * the recapitulative statement of intra-Community supplies is monthly in
|
|
9
|
+
-- France from the first euro, monthly in Belgium above a threshold that
|
|
10
|
+
-- counts goods alone whatever the return's cadence, filed on separately
|
|
11
|
+
-- chosen cadences for goods and for services in Luxembourg, and filed with
|
|
12
|
+
-- the return in Estonia;
|
|
13
|
+
-- * the annual return, where a country files one beside the periodic one, is
|
|
14
|
+
-- annual by definition and says nothing about the periodic one;
|
|
15
|
+
-- * and a company registered in two countries files each country's form on
|
|
16
|
+
-- that country's cadence.
|
|
17
|
+
--
|
|
18
|
+
-- So `ec_sales_list()` refused no period at all, where `vat_return()` refused
|
|
19
|
+
-- one the company does not file on. Borrowing the return's guard would have
|
|
20
|
+
-- refused a lawful monthly statement from a quarterly Belgian filer, which is
|
|
21
|
+
-- the ordinary case — the statement had no cadence of its own to read, and one
|
|
22
|
+
-- column named after one form could not give it one. `docs/international.md`
|
|
23
|
+
-- recorded it under *A company records how often it files its return, and that
|
|
24
|
+
-- is not how often it files anything else*.
|
|
25
|
+
--
|
|
26
|
+
-- Three pieces here, and the third is the one that carries the change.
|
|
27
|
+
--
|
|
28
|
+
-- `tax_report_templates.period_default` what the **form** is filed on unless
|
|
29
|
+
-- the company says otherwise. It moves the proposal from the country to
|
|
30
|
+
-- the form, which is where it belongs: a country proposing one cadence
|
|
31
|
+
-- could only ever speak about one declaration, and the moment a company
|
|
32
|
+
-- records a cadence per form the proposal has to be per form too.
|
|
33
|
+
-- `company_filing_periods` what **this company** files, one row
|
|
34
|
+
-- per declaration it is subject to. Absent means not recorded, which is
|
|
35
|
+
-- not an error and not a cadence.
|
|
36
|
+
-- `companies.vat_period` kept, deprecated, and **derived**.
|
|
37
|
+
-- See below: it is a mirror of one row of the table and never a second
|
|
38
|
+
-- place the fact is decided.
|
|
39
|
+
--
|
|
40
|
+
-- **Why `companies.vat_period` is kept.** It has been published since v0.3.0:
|
|
41
|
+
-- `ekwo status` prints it, `ekwo init` writes it, and an installation's own
|
|
42
|
+
-- clients read it over PostgREST. Dropping it would break a reader outside this
|
|
43
|
+
-- repository for a column whose value is still exactly right — a company files
|
|
44
|
+
-- its periodic return on one cadence, and that is what it says.
|
|
45
|
+
--
|
|
46
|
+
-- **Why it is not a second truth.** The rule of this repository is one source
|
|
47
|
+
-- for one fact, and two writable columns holding one cadence is how they drift.
|
|
48
|
+
-- So the table is the source and the column is its mirror, held in step by two
|
|
49
|
+
-- triggers that each write only when the other is out of date: a row written
|
|
50
|
+
-- for the country's periodic return updates the column, and a write to the
|
|
51
|
+
-- column writes the row. That is the shape `20260915200000` used for
|
|
52
|
+
-- `declaration_box` and `declaration_boxes`, and it is here for the same
|
|
53
|
+
-- reason: a published column stays readable and a writer that knows only the
|
|
54
|
+
-- old name still writes the new fact.
|
|
55
|
+
--
|
|
56
|
+
-- The mirror needs to know **which** of a company's declarations is the one the
|
|
57
|
+
-- column is named after, and it finds it the way the rest of the core finds
|
|
58
|
+
-- anything about a country: by asking the pack. `periodic_return_code()`
|
|
59
|
+
-- returns the code of the form the company's fiscal country files periodically
|
|
60
|
+
-- when there is exactly one, and null when there is none or more than one — a
|
|
61
|
+
-- country that files two periodic returns at once has no single form the column
|
|
62
|
+
-- could mean, and the column then stays where it is while the table carries
|
|
63
|
+
-- both. No country is named anywhere in this file.
|
|
64
|
+
--
|
|
65
|
+
-- **What is not moved.** A company whose installation carries no periodic
|
|
66
|
+
-- return form for its country keeps whatever is in the column and gains no row:
|
|
67
|
+
-- there is no declaration to name, so there is nothing the value could be
|
|
68
|
+
-- recorded against. Nothing is lost and nothing is invented.
|
|
69
|
+
|
|
70
|
+
-- ---------------------------------------------------------------------------
|
|
71
|
+
-- What a form proposes
|
|
72
|
+
-- ---------------------------------------------------------------------------
|
|
73
|
+
--
|
|
74
|
+
-- `country_defaults.vat_period_default` asked the wrong question. It asked what
|
|
75
|
+
-- a *country* proposes, and a country proposes nothing: a law proposes a
|
|
76
|
+
-- cadence for a form. As long as a pack carried one form the two were the same
|
|
77
|
+
-- sentence, and the day a pack carries two the country-level answer cannot say
|
|
78
|
+
-- which one it is about.
|
|
79
|
+
--
|
|
80
|
+
-- It also made a rule out of an accident. `ekwo pack check` let a pack propose
|
|
81
|
+
-- a cadence only where its form accepts exactly one, on the reasoning that a
|
|
82
|
+
-- form filed on several means the answer depends on a fact about the company —
|
|
83
|
+
-- turnover, which is how Belgium, France and Luxembourg all set it. That
|
|
84
|
+
-- reasoning is about the *law* and the rule read the *length of a list*, and
|
|
85
|
+
-- the two part company: regulation 25(1) of the Value Added Tax Regulations
|
|
86
|
+
-- 1995 makes the prescribed accounting period three months for everybody, and a
|
|
87
|
+
-- month or a year is something the Commissioners allow or direct on
|
|
88
|
+
-- application. A form filed on three cadences whose law gives one default was
|
|
89
|
+
-- the case the rule could not express. With the proposal on the form, it can:
|
|
90
|
+
-- the form says what it accepts and what it is filed on when nobody has asked
|
|
91
|
+
-- for anything else, and `ekwo pack check` only refuses a proposal the form
|
|
92
|
+
-- does not accept.
|
|
93
|
+
|
|
94
|
+
alter table tax_report_templates
|
|
95
|
+
add column if not exists period_default declaration_period;
|
|
96
|
+
|
|
97
|
+
alter table tax_report_templates
|
|
98
|
+
add constraint tax_report_templates_default_is_offered check (
|
|
99
|
+
period_default is null or period_default = any (periods)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
comment on column tax_report_templates.period_default is
|
|
103
|
+
'Cadence this form is filed on unless the company has asked the administration for another, from packs/<cc>/tax_report.json. Null where the law makes the cadence depend on a fact about the company — turnover — because proposing one of several lawful answers there would be choosing a filing deadline for somebody the pack knows nothing about. Set where the law gives one answer to everybody, whatever else the form accepts. Read at install; never read by the return.';
|
|
104
|
+
|
|
105
|
+
-- What the packs already proposed, moved onto the form it was about. A country
|
|
106
|
+
-- default that names a cadence the form does not accept is left behind rather
|
|
107
|
+
-- than written into a column that refuses it: `ekwo pack check` has refused
|
|
108
|
+
-- that combination since the proposal existed, so there is none to move.
|
|
109
|
+
update tax_report_templates t
|
|
110
|
+
set period_default = d.vat_period_default
|
|
111
|
+
from country_defaults d
|
|
112
|
+
where d.country = t.country
|
|
113
|
+
and t.is_periodic_return
|
|
114
|
+
and d.vat_period_default is not null
|
|
115
|
+
and d.vat_period_default = any (t.periods)
|
|
116
|
+
and t.period_default is null;
|
|
117
|
+
|
|
118
|
+
comment on column country_defaults.vat_period_default is
|
|
119
|
+
'Deprecated by tax_report_templates.period_default, which asks the same question of the form rather than of the country. Kept because it is published and still written by the pack compiler, with the value of the periodic return''s own default; a country that files two periodic returns has two answers and this column can hold one, which is why the form is now the place. Read nothing new from here.';
|
|
120
|
+
|
|
121
|
+
-- ---------------------------------------------------------------------------
|
|
122
|
+
-- Which of a company's declarations the deprecated column is named after
|
|
123
|
+
-- ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
create or replace function periodic_return_code(p_company_id uuid)
|
|
126
|
+
returns text
|
|
127
|
+
language sql
|
|
128
|
+
stable
|
|
129
|
+
as $$
|
|
130
|
+
select case when count(distinct t.code) = 1 then min(t.code) end
|
|
131
|
+
from companies c
|
|
132
|
+
join tax_report_templates t on t.country = c.fiscal_country
|
|
133
|
+
where c.id = p_company_id
|
|
134
|
+
and t.is_periodic_return;
|
|
135
|
+
$$;
|
|
136
|
+
|
|
137
|
+
comment on function periodic_return_code(uuid) is
|
|
138
|
+
'The code of the periodic return the company''s fiscal country files, when the installation carries exactly one. Null when it carries none — no pack for that country — and null when it carries several, because then no single form is what a column named after "the" return could mean. It is what holds companies.vat_period and company_filing_periods in step, and it names the form by asking the pack rather than by knowing a country.';
|
|
139
|
+
|
|
140
|
+
revoke execute on function periodic_return_code(uuid) from public, anon;
|
|
141
|
+
grant execute on function periodic_return_code(uuid) to authenticated, service_role;
|
|
142
|
+
|
|
143
|
+
-- ---------------------------------------------------------------------------
|
|
144
|
+
-- What this company files, declaration by declaration
|
|
145
|
+
-- ---------------------------------------------------------------------------
|
|
146
|
+
--
|
|
147
|
+
-- The key is the company and the code of the form, because that is what the
|
|
148
|
+
-- fact is: *this company files that declaration every month*. No surrogate
|
|
149
|
+
-- identifier — the pair is the natural key, it is the key every reader joins
|
|
150
|
+
-- on, and `company_members` and the rest of the schema already key a row of a
|
|
151
|
+
-- company on what names it.
|
|
152
|
+
--
|
|
153
|
+
-- `report_code` carries no foreign key onto `tax_report_templates`, and that is
|
|
154
|
+
-- deliberate. The form is keyed on `(country, code)`, a company's fiscal
|
|
155
|
+
-- country can change, and a company registered in a second country files that
|
|
156
|
+
-- country's form while its own fiscal country is elsewhere — a foreign key on
|
|
157
|
+
-- the pair would refuse a lawful row and a foreign key on the code alone does
|
|
158
|
+
-- not exist. What replaces it is a trigger that refuses a code no form of this
|
|
159
|
+
-- installation carries, which is the typo a foreign key would have caught and
|
|
160
|
+
-- nothing more.
|
|
161
|
+
|
|
162
|
+
create table company_filing_periods (
|
|
163
|
+
company_id uuid not null references companies(id) on delete cascade,
|
|
164
|
+
report_code text not null,
|
|
165
|
+
period declaration_period not null,
|
|
166
|
+
created_at timestamptz not null default now(),
|
|
167
|
+
updated_at timestamptz not null default now(),
|
|
168
|
+
primary key (company_id, report_code),
|
|
169
|
+
constraint company_filing_periods_has_a_report check (length(report_code) > 0)
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
comment on table company_filing_periods is
|
|
173
|
+
'How often this company files each declaration it is subject to: one row per form of tax_report_templates, and no row where nothing has been recorded. A company files its periodic return on one cadence and its recapitulative statement on another, and the second is not derivable from the first in any country read so far.';
|
|
174
|
+
comment on column company_filing_periods.report_code is
|
|
175
|
+
'The form, by the code tax_report_templates gives it. Not a foreign key: the form is keyed on (country, code) and a company may file a form of a country that is not its own. A trigger refuses a code no form of this installation carries.';
|
|
176
|
+
comment on column company_filing_periods.period is
|
|
177
|
+
'How often this company files that form. Not null: a row that recorded nothing would say the same thing as no row, in a second way.';
|
|
178
|
+
|
|
179
|
+
create trigger company_filing_periods_set_updated_at
|
|
180
|
+
before update on company_filing_periods
|
|
181
|
+
for each row execute function set_updated_at();
|
|
182
|
+
|
|
183
|
+
-- ---------------------------------------------------------------------------
|
|
184
|
+
-- A code that names no form is refused, by name
|
|
185
|
+
-- ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
create or replace function company_filing_periods_names_a_form()
|
|
188
|
+
returns trigger
|
|
189
|
+
language plpgsql
|
|
190
|
+
as $$
|
|
191
|
+
begin
|
|
192
|
+
if not exists (select 1 from tax_report_templates t where t.code = new.report_code) then
|
|
193
|
+
raise exception 'unknown_tax_report: % is not a declaration form of this installation', new.report_code;
|
|
194
|
+
end if;
|
|
195
|
+
return new;
|
|
196
|
+
end;
|
|
197
|
+
$$;
|
|
198
|
+
|
|
199
|
+
comment on function company_filing_periods_names_a_form() is
|
|
200
|
+
'Refuses a filing cadence recorded against a form this installation does not carry. Raise, do not warn: a row nothing can resolve is a cadence nobody files on, and the guards that read it would simply never fire.';
|
|
201
|
+
|
|
202
|
+
revoke execute on function company_filing_periods_names_a_form() from public, anon, authenticated, service_role;
|
|
203
|
+
|
|
204
|
+
create trigger company_filing_periods_names_a_form
|
|
205
|
+
before insert or update on company_filing_periods
|
|
206
|
+
for each row execute function company_filing_periods_names_a_form();
|
|
207
|
+
|
|
208
|
+
-- ---------------------------------------------------------------------------
|
|
209
|
+
-- Reading one cadence
|
|
210
|
+
-- ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
create or replace function filing_period(p_company_id uuid, p_report_code text)
|
|
213
|
+
returns declaration_period
|
|
214
|
+
language sql
|
|
215
|
+
stable
|
|
216
|
+
as $$
|
|
217
|
+
select f.period
|
|
218
|
+
from company_filing_periods f
|
|
219
|
+
where f.company_id = p_company_id
|
|
220
|
+
and f.report_code = p_report_code;
|
|
221
|
+
$$;
|
|
222
|
+
|
|
223
|
+
comment on function filing_period(uuid, text) is
|
|
224
|
+
'How often this company files that declaration, or null when it has not been recorded. Null is not a cadence and not an error: the books are kept the same either way, and a guard that reads it refuses nothing.';
|
|
225
|
+
|
|
226
|
+
revoke execute on function filing_period(uuid, text) from public, anon;
|
|
227
|
+
grant execute on function filing_period(uuid, text) to authenticated, service_role;
|
|
228
|
+
|
|
229
|
+
-- ---------------------------------------------------------------------------
|
|
230
|
+
-- What was recorded before, moved onto the declaration it was about
|
|
231
|
+
-- ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
insert into company_filing_periods (company_id, report_code, period)
|
|
234
|
+
select c.id, periodic_return_code(c.id), c.vat_period
|
|
235
|
+
from companies c
|
|
236
|
+
where c.vat_period is not null
|
|
237
|
+
and periodic_return_code(c.id) is not null
|
|
238
|
+
on conflict (company_id, report_code) do nothing;
|
|
239
|
+
|
|
240
|
+
-- ---------------------------------------------------------------------------
|
|
241
|
+
-- The deprecated column, kept in step
|
|
242
|
+
-- ---------------------------------------------------------------------------
|
|
243
|
+
--
|
|
244
|
+
-- Each trigger writes only when the other side is out of date, which is what
|
|
245
|
+
-- makes the pair terminate: the second write finds the value already there and
|
|
246
|
+
-- changes no row, so no third trigger fires.
|
|
247
|
+
|
|
248
|
+
create or replace function company_filing_periods_mirror()
|
|
249
|
+
returns trigger
|
|
250
|
+
language plpgsql
|
|
251
|
+
as $$
|
|
252
|
+
declare
|
|
253
|
+
v_company uuid;
|
|
254
|
+
v_code text;
|
|
255
|
+
v_was text;
|
|
256
|
+
v_return text;
|
|
257
|
+
v_now declaration_period;
|
|
258
|
+
begin
|
|
259
|
+
-- `old` and `new` are each assigned for two of the three operations, so both
|
|
260
|
+
-- are read in a branch that knows which one this is rather than in one
|
|
261
|
+
-- condition relying on the order an `or` happens to be evaluated in.
|
|
262
|
+
if tg_op = 'DELETE' then
|
|
263
|
+
v_company := old.company_id;
|
|
264
|
+
v_code := old.report_code;
|
|
265
|
+
v_was := old.report_code;
|
|
266
|
+
elsif tg_op = 'UPDATE' then
|
|
267
|
+
v_company := new.company_id;
|
|
268
|
+
v_code := new.report_code;
|
|
269
|
+
v_was := old.report_code;
|
|
270
|
+
else
|
|
271
|
+
v_company := new.company_id;
|
|
272
|
+
v_code := new.report_code;
|
|
273
|
+
v_was := new.report_code;
|
|
274
|
+
end if;
|
|
275
|
+
|
|
276
|
+
-- A row about any other declaration says nothing about a column named after
|
|
277
|
+
-- the periodic return. The code the row had is looked at too, so an update
|
|
278
|
+
-- that renames a row away from the return clears the column.
|
|
279
|
+
v_return := periodic_return_code(v_company);
|
|
280
|
+
if v_code is distinct from v_return and v_was is distinct from v_return then
|
|
281
|
+
return null;
|
|
282
|
+
end if;
|
|
283
|
+
|
|
284
|
+
v_now := filing_period(v_company, v_return);
|
|
285
|
+
update companies c
|
|
286
|
+
set vat_period = v_now
|
|
287
|
+
where c.id = v_company
|
|
288
|
+
and c.vat_period is distinct from v_now;
|
|
289
|
+
return null;
|
|
290
|
+
end;
|
|
291
|
+
$$;
|
|
292
|
+
|
|
293
|
+
comment on function company_filing_periods_mirror() is
|
|
294
|
+
'Writes companies.vat_period from the row that records how often this company files its country''s periodic return. The column is a mirror and never a second decision: it is written here, from the table, and only when the two differ.';
|
|
295
|
+
|
|
296
|
+
revoke execute on function company_filing_periods_mirror() from public, anon, authenticated, service_role;
|
|
297
|
+
|
|
298
|
+
create trigger company_filing_periods_mirror
|
|
299
|
+
after insert or update or delete on company_filing_periods
|
|
300
|
+
for each row execute function company_filing_periods_mirror();
|
|
301
|
+
|
|
302
|
+
create or replace function companies_vat_period_is_a_filing_period()
|
|
303
|
+
returns trigger
|
|
304
|
+
language plpgsql
|
|
305
|
+
as $$
|
|
306
|
+
declare
|
|
307
|
+
v_code text;
|
|
308
|
+
begin
|
|
309
|
+
-- Nothing moved, so there is nothing to record. `old` is read only in the
|
|
310
|
+
-- branch where it exists.
|
|
311
|
+
if tg_op = 'UPDATE' then
|
|
312
|
+
if new.vat_period is not distinct from old.vat_period then
|
|
313
|
+
return null;
|
|
314
|
+
end if;
|
|
315
|
+
elsif new.vat_period is null then
|
|
316
|
+
return null;
|
|
317
|
+
end if;
|
|
318
|
+
v_code := periodic_return_code(new.id);
|
|
319
|
+
-- Nothing names the declaration this value is about, so there is nothing to
|
|
320
|
+
-- record it against. The column keeps what it was given.
|
|
321
|
+
if v_code is null then
|
|
322
|
+
return null;
|
|
323
|
+
end if;
|
|
324
|
+
if new.vat_period is null then
|
|
325
|
+
delete from company_filing_periods f
|
|
326
|
+
where f.company_id = new.id and f.report_code = v_code;
|
|
327
|
+
else
|
|
328
|
+
insert into company_filing_periods (company_id, report_code, period)
|
|
329
|
+
values (new.id, v_code, new.vat_period)
|
|
330
|
+
on conflict (company_id, report_code) do update
|
|
331
|
+
set period = excluded.period
|
|
332
|
+
where company_filing_periods.period is distinct from excluded.period;
|
|
333
|
+
end if;
|
|
334
|
+
return null;
|
|
335
|
+
end;
|
|
336
|
+
$$;
|
|
337
|
+
|
|
338
|
+
comment on function companies_vat_period_is_a_filing_period() is
|
|
339
|
+
'Records a write to the deprecated companies.vat_period as what it is: how often this company files its country''s periodic return. Everything written before this migration named that column and nothing else, and this is what keeps such a writer correct without it learning a table.';
|
|
340
|
+
|
|
341
|
+
revoke execute on function companies_vat_period_is_a_filing_period() from public, anon, authenticated, service_role;
|
|
342
|
+
|
|
343
|
+
create trigger companies_vat_period_is_a_filing_period
|
|
344
|
+
after insert or update of vat_period on companies
|
|
345
|
+
for each row execute function companies_vat_period_is_a_filing_period();
|
|
346
|
+
|
|
347
|
+
comment on column companies.vat_period is
|
|
348
|
+
'Deprecated by company_filing_periods, and derived from it: how often this company files its country''s periodic return, which is one of the several declarations it is subject to. Null means nothing has been recorded, which is not an error and not a cadence. Write the table; this column is written from it, and a write here is recorded there.';
|
|
349
|
+
|
|
350
|
+
-- ---------------------------------------------------------------------------
|
|
351
|
+
-- Who reads and who writes
|
|
352
|
+
-- ---------------------------------------------------------------------------
|
|
353
|
+
--
|
|
354
|
+
-- Reading is reading a company: the same reach as the row this used to be a
|
|
355
|
+
-- column of. Writing is `company.write`, which is what governs
|
|
356
|
+
-- `companies.vat_period` today, and the two match on purpose — a capability
|
|
357
|
+
-- that could write the table and not the column would make the mirror a write
|
|
358
|
+
-- that does not take, which is the worst kind.
|
|
359
|
+
|
|
360
|
+
alter table company_filing_periods enable row level security;
|
|
361
|
+
|
|
362
|
+
create policy company_filing_periods_select on company_filing_periods
|
|
363
|
+
for select using (is_company_member(company_id));
|
|
364
|
+
create policy company_filing_periods_write on company_filing_periods
|
|
365
|
+
for all using (has_capability(company_id, 'company.write'))
|
|
366
|
+
with check (has_capability(company_id, 'company.write'));
|
|
367
|
+
|
|
368
|
+
comment on policy company_filing_periods_select on company_filing_periods is
|
|
369
|
+
'A member of the company reads how often it files, exactly as they read the company itself.';
|
|
370
|
+
comment on policy company_filing_periods_write on company_filing_periods is
|
|
371
|
+
'company.write, the capability that governs the deprecated column this mirrors. Anything narrower would let somebody record a cadence the mirror could not write.';
|
|
372
|
+
|
|
373
|
+
grant select, insert, update, delete on table company_filing_periods
|
|
374
|
+
to authenticated, service_role;
|
|
375
|
+
|
|
376
|
+
create trigger company_filing_periods_audit
|
|
377
|
+
after insert or update or delete on company_filing_periods
|
|
378
|
+
for each row execute function audit_changes('{"company":"company_id","key":["report_code"]}');
|
|
379
|
+
|
|
380
|
+
-- ---------------------------------------------------------------------------
|
|
381
|
+
-- The return reads the cadence of the return it was asked for
|
|
382
|
+
-- ---------------------------------------------------------------------------
|
|
383
|
+
--
|
|
384
|
+
-- Replaced whole, because a function is replaced whole in PostgreSQL.
|
|
385
|
+
-- Everything but the guard is `20260915200000` unchanged.
|
|
386
|
+
--
|
|
387
|
+
-- The change is one word wide and it is the whole point of this migration: the
|
|
388
|
+
-- cadence read is the one recorded **for the form being asked for**, not the
|
|
389
|
+
-- one recorded for the company. A company that files its return quarterly and
|
|
390
|
+
-- its recapitulative statement monthly is two rows, and each guard reads its
|
|
391
|
+
-- own.
|
|
392
|
+
|
|
393
|
+
create or replace function vat_return(
|
|
394
|
+
p_company_id uuid,
|
|
395
|
+
p_from date,
|
|
396
|
+
p_to date,
|
|
397
|
+
p_report_code text default null
|
|
398
|
+
)
|
|
399
|
+
returns table (
|
|
400
|
+
box text,
|
|
401
|
+
kind text,
|
|
402
|
+
amount numeric,
|
|
403
|
+
computed boolean,
|
|
404
|
+
name text,
|
|
405
|
+
sequence integer,
|
|
406
|
+
hidden boolean,
|
|
407
|
+
report_code text
|
|
408
|
+
)
|
|
409
|
+
language plpgsql
|
|
410
|
+
stable
|
|
411
|
+
as $$
|
|
412
|
+
declare
|
|
413
|
+
-- 'box|kind' -> amount, for every box summed from the ledger, then the
|
|
414
|
+
-- totals `evaluate_totals()` derives from them. The key carries the kind
|
|
415
|
+
-- because the French CA3 puts a base and a tax on line 08 and a formula has
|
|
416
|
+
-- to be able to name one of them.
|
|
417
|
+
v_values jsonb := '{}'::jsonb;
|
|
418
|
+
v_formulas jsonb := '[]'::jsonb;
|
|
419
|
+
v_totals jsonb := '{}'::jsonb;
|
|
420
|
+
v_rows jsonb := '[]'::jsonb;
|
|
421
|
+
v_country char(2);
|
|
422
|
+
v_report text;
|
|
423
|
+
v_in char(2);
|
|
424
|
+
v_count integer;
|
|
425
|
+
v_codes text;
|
|
426
|
+
-- How often this company files, what the form accepts, and what the two
|
|
427
|
+
-- dates asked for actually are.
|
|
428
|
+
v_files declaration_period;
|
|
429
|
+
v_accepts declaration_period[];
|
|
430
|
+
v_asked declaration_period;
|
|
431
|
+
-- A declaration figure is not a ledger figure, but it is written in the
|
|
432
|
+
-- same currency and with the same decimals.
|
|
433
|
+
v_round money_rounding;
|
|
434
|
+
r record;
|
|
435
|
+
begin
|
|
436
|
+
select c.fiscal_country into v_country
|
|
437
|
+
from companies c where c.id = p_company_id;
|
|
438
|
+
if not found then
|
|
439
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
440
|
+
end if;
|
|
441
|
+
v_round := rounding_of(p_company_id);
|
|
442
|
+
|
|
443
|
+
-- Which form. The caller names one, or the country files exactly one on
|
|
444
|
+
-- that date. Two and no name is a question only the caller can answer — a
|
|
445
|
+
-- Canadian company files the federal return and the Québec one at once — so
|
|
446
|
+
-- this asks instead of guessing.
|
|
447
|
+
if p_report_code is not null then
|
|
448
|
+
select t.country, t.code into v_in, v_report
|
|
449
|
+
from tax_report_templates t
|
|
450
|
+
where t.code = p_report_code
|
|
451
|
+
and t.valid_from <= p_to
|
|
452
|
+
and (t.valid_to is null or t.valid_to >= p_to)
|
|
453
|
+
order by t.country
|
|
454
|
+
limit 1;
|
|
455
|
+
if v_report is null then
|
|
456
|
+
raise exception 'unknown_tax_report: % is not a declaration form in force on %',
|
|
457
|
+
p_report_code, p_to;
|
|
458
|
+
end if;
|
|
459
|
+
else
|
|
460
|
+
select count(*), min(t.code), string_agg(t.code, ', ' order by t.code)
|
|
461
|
+
into v_count, v_report, v_codes
|
|
462
|
+
from tax_report_templates t
|
|
463
|
+
where t.country = v_country
|
|
464
|
+
and t.is_periodic_return
|
|
465
|
+
and t.valid_from <= p_to
|
|
466
|
+
and (t.valid_to is null or t.valid_to >= p_to);
|
|
467
|
+
if v_count > 1 then
|
|
468
|
+
raise exception 'ambiguous_tax_report: % files several declarations on % (%); name one',
|
|
469
|
+
v_country, p_to, v_codes;
|
|
470
|
+
end if;
|
|
471
|
+
v_in := v_country;
|
|
472
|
+
if v_count = 0 then
|
|
473
|
+
v_report := null; -- no pack for this country: the ledger boxes, and no total.
|
|
474
|
+
end if;
|
|
475
|
+
end if;
|
|
476
|
+
|
|
477
|
+
-- The period asked for against the one this company files **this form** on.
|
|
478
|
+
-- Four things have to hold before this refuses, and the fourth is what keeps
|
|
479
|
+
-- it out of everybody's way: the company has recorded a cadence for this
|
|
480
|
+
-- declaration, the form is filed on that cadence, the dates are themselves a
|
|
481
|
+
-- whole cadence of that form, and the two are not the same. A fortnight, a
|
|
482
|
+
-- half-year, a form the company has recorded nothing about — none of those is
|
|
483
|
+
-- a filing on the wrong cadence, and none of them is refused.
|
|
484
|
+
if v_report is not null then
|
|
485
|
+
v_files := filing_period(p_company_id, v_report);
|
|
486
|
+
if v_files is not null then
|
|
487
|
+
select t.periods into v_accepts
|
|
488
|
+
from tax_report_templates t
|
|
489
|
+
where t.country = v_in and t.code = v_report;
|
|
490
|
+
v_asked := declaration_period_of(p_from, p_to);
|
|
491
|
+
if v_asked is not null
|
|
492
|
+
and v_files = any(v_accepts)
|
|
493
|
+
and v_asked = any(v_accepts)
|
|
494
|
+
and v_asked <> v_files then
|
|
495
|
+
raise exception
|
|
496
|
+
'wrong_declaration_period: this company files % returns on %; % to % is a %',
|
|
497
|
+
v_files, v_report, p_from, p_to, v_asked;
|
|
498
|
+
end if;
|
|
499
|
+
end if;
|
|
500
|
+
end if;
|
|
501
|
+
|
|
502
|
+
-- 1. What the tax postings wrote on the ledger, in every box each of them
|
|
503
|
+
-- names. A line carries the box it is known by; the posting behind it
|
|
504
|
+
-- carries the whole list, which is one box for all but the handful of
|
|
505
|
+
-- forms that print a figure twice. No country rule here either: the
|
|
506
|
+
-- expansion is `unnest`, and which boxes there are is the pack's answer.
|
|
507
|
+
for r in
|
|
508
|
+
with lines as (
|
|
509
|
+
select l.declaration_box as lbox,
|
|
510
|
+
case when l.tax_line then 'tax' else 'base' end as lkind,
|
|
511
|
+
l.tax_id as ltax,
|
|
512
|
+
l.box_amount as lamount
|
|
513
|
+
from entry_lines l
|
|
514
|
+
join entries e on e.id = l.entry_id
|
|
515
|
+
where l.company_id = p_company_id
|
|
516
|
+
and e.state = 'posted'
|
|
517
|
+
and e.entry_date between p_from and p_to
|
|
518
|
+
and l.declaration_box is not null
|
|
519
|
+
),
|
|
520
|
+
-- The posting that wrote the line: which form it is on, and every box it
|
|
521
|
+
-- prints in. A line with no posting behind it — an entry keyed by hand,
|
|
522
|
+
-- a tax a company wrote itself — is on this form and in the one box it
|
|
523
|
+
-- names, which is what the left join leaves.
|
|
524
|
+
sourced as (
|
|
525
|
+
select ln.lbox, ln.lkind, ln.lamount,
|
|
526
|
+
coalesce(p.boxes, array[ln.lbox]) as lboxes
|
|
527
|
+
from lines ln
|
|
528
|
+
left join lateral (
|
|
529
|
+
select min(tp.report_code) as report_code,
|
|
530
|
+
array_agg(distinct b.box) as boxes
|
|
531
|
+
from tax_postings tp
|
|
532
|
+
cross join lateral unnest(tp.declaration_boxes) as b(box)
|
|
533
|
+
where tp.tax_id = ln.ltax
|
|
534
|
+
and tp.declaration_box = ln.lbox
|
|
535
|
+
and tp.posting_type = ln.lkind::tax_posting_type
|
|
536
|
+
) p on true
|
|
537
|
+
-- A box number belongs to one form. A line whose posting names
|
|
538
|
+
-- another form is not on this declaration; one that names none is
|
|
539
|
+
-- the single-return case every European company is in.
|
|
540
|
+
where v_report is null or coalesce(p.report_code, v_report) = v_report
|
|
541
|
+
),
|
|
542
|
+
ledger as (
|
|
543
|
+
select x.box as lbox, s.lkind,
|
|
544
|
+
round_amount(sum(s.lamount), v_round) as lamount
|
|
545
|
+
from sourced s
|
|
546
|
+
cross join lateral unnest(s.lboxes) as x(box)
|
|
547
|
+
group by 1, 2
|
|
548
|
+
having round_amount(sum(s.lamount), v_round) <> 0
|
|
549
|
+
)
|
|
550
|
+
select g.lbox, g.lkind, g.lamount, b.name as lname,
|
|
551
|
+
b.sequence as lsequence, coalesce(b.hidden, false) as lhidden
|
|
552
|
+
from ledger g
|
|
553
|
+
left join tax_report_box_templates b
|
|
554
|
+
on b.country = v_in and b.report_code = v_report
|
|
555
|
+
and b.box = g.lbox and b.kind = g.lkind
|
|
556
|
+
order by coalesce(b.sequence, 2147483647), g.lbox, g.lkind
|
|
557
|
+
loop
|
|
558
|
+
v_values := v_values || jsonb_build_object(r.lbox || '|' || r.lkind, r.lamount);
|
|
559
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
560
|
+
'box', r.lbox, 'kind', r.lkind, 'amount', r.lamount, 'computed', false,
|
|
561
|
+
'name', r.lname, 'sequence', r.lsequence, 'hidden', r.lhidden,
|
|
562
|
+
'report_code', v_report));
|
|
563
|
+
end loop;
|
|
564
|
+
|
|
565
|
+
-- 2. The totals of the form, through the evaluator the statements use. A
|
|
566
|
+
-- return prints what it has, so a nil total is left out of the answer —
|
|
567
|
+
-- and kept in the working set, so a later total that names it reads a
|
|
568
|
+
-- zero rather than a gap.
|
|
569
|
+
if v_report is not null then
|
|
570
|
+
select coalesce(jsonb_agg(jsonb_build_object(
|
|
571
|
+
'key', b.box || '|total', 'plus', to_jsonb(b.plus_boxes),
|
|
572
|
+
'minus', to_jsonb(b.minus_boxes), 'floor_zero', b.floor_zero,
|
|
573
|
+
'sequence', b.sequence
|
|
574
|
+
) order by b.sequence, b.box), '[]'::jsonb)
|
|
575
|
+
into v_formulas
|
|
576
|
+
from tax_report_box_templates b
|
|
577
|
+
where b.country = v_in
|
|
578
|
+
and b.report_code = v_report
|
|
579
|
+
and b.kind = 'total'
|
|
580
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
581
|
+
and (b.valid_to is null or b.valid_to >= p_to);
|
|
582
|
+
|
|
583
|
+
v_totals := evaluate_totals(v_values, v_formulas, v_round, false);
|
|
584
|
+
|
|
585
|
+
for r in
|
|
586
|
+
select b.box as tbox, b.name as tname, b.sequence as tsequence, b.hidden as thidden
|
|
587
|
+
from tax_report_box_templates b
|
|
588
|
+
where b.country = v_in
|
|
589
|
+
and b.report_code = v_report
|
|
590
|
+
and b.kind = 'total'
|
|
591
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
592
|
+
and (b.valid_to is null or b.valid_to >= p_to)
|
|
593
|
+
and v_totals ? (b.box || '|total')
|
|
594
|
+
order by b.sequence, b.box
|
|
595
|
+
loop
|
|
596
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
597
|
+
'box', r.tbox, 'kind', 'total',
|
|
598
|
+
'amount', (v_totals ->> (r.tbox || '|total'))::numeric, 'computed', true,
|
|
599
|
+
'name', r.tname, 'sequence', r.tsequence, 'hidden', r.thidden,
|
|
600
|
+
'report_code', v_report));
|
|
601
|
+
end loop;
|
|
602
|
+
end if;
|
|
603
|
+
|
|
604
|
+
return query
|
|
605
|
+
select (x ->> 'box')::text,
|
|
606
|
+
(x ->> 'kind')::text,
|
|
607
|
+
(x ->> 'amount')::numeric,
|
|
608
|
+
(x ->> 'computed')::boolean,
|
|
609
|
+
(x ->> 'name')::text,
|
|
610
|
+
(x ->> 'sequence')::integer,
|
|
611
|
+
(x ->> 'hidden')::boolean,
|
|
612
|
+
(x ->> 'report_code')::text
|
|
613
|
+
from jsonb_array_elements(v_rows) as x;
|
|
614
|
+
end;
|
|
615
|
+
$$;
|
|
616
|
+
|
|
617
|
+
comment on function vat_return(uuid, date, date, text) is
|
|
618
|
+
'Declaration boxes for a period: summed from the ledger, then the totals of the country''s form worked out by evaluate_totals(), the same evaluator financial_statement() uses. Refuses a period the company does not file **this form** on, when it has recorded one for it. No country rule lives in this function.';
|
|
619
|
+
|
|
620
|
+
revoke execute on function vat_return(uuid, date, date, text) from public, anon;
|
|
621
|
+
grant execute on function vat_return(uuid, date, date, text) to authenticated, service_role;
|
|
622
|
+
|
|
623
|
+
-- ---------------------------------------------------------------------------
|
|
624
|
+
-- The recapitulative statement gains the same guard
|
|
625
|
+
-- ---------------------------------------------------------------------------
|
|
626
|
+
--
|
|
627
|
+
-- The statement now has somewhere to read its own cadence from, so it can
|
|
628
|
+
-- refuse a period the company does not file it on — with the same reticence as
|
|
629
|
+
-- the return, and one condition more.
|
|
630
|
+
--
|
|
631
|
+
-- **It has to be told which declaration it is.** `vat_return()` finds its form
|
|
632
|
+
-- from the country, because a periodic return is flagged as one. Nothing flags
|
|
633
|
+
-- a recapitulative statement, and no pack of this repository declares one: the
|
|
634
|
+
-- pack format carries a single `tax_report.json`, which is the periodic return.
|
|
635
|
+
-- So the caller names the form, and a caller that names none is asking for the
|
|
636
|
+
-- figures rather than filing — nothing is refused. That is the prudent half of
|
|
637
|
+
-- the same principle: refuse only where the refusal is certain.
|
|
638
|
+
--
|
|
639
|
+
-- The gap that leaves — a country files several declarations and a pack
|
|
640
|
+
-- declares one form — is written up in `docs/international.md` rather than
|
|
641
|
+
-- patched here. Widening the pack format to a list of forms is a change to
|
|
642
|
+
-- every pack and to `ekwo pack check`, and it is not what this migration is.
|
|
643
|
+
--
|
|
644
|
+
-- The rest of the function is `20260915181500` unchanged.
|
|
645
|
+
|
|
646
|
+
drop function if exists ec_sales_list(uuid, date, date);
|
|
647
|
+
|
|
648
|
+
create or replace function ec_sales_list(
|
|
649
|
+
p_company_id uuid,
|
|
650
|
+
p_from date,
|
|
651
|
+
p_to date,
|
|
652
|
+
p_report_code text default null
|
|
653
|
+
)
|
|
654
|
+
returns table (
|
|
655
|
+
vat_country char(2),
|
|
656
|
+
vat_number text,
|
|
657
|
+
nature text,
|
|
658
|
+
amount numeric,
|
|
659
|
+
currency_code char(3),
|
|
660
|
+
documents integer,
|
|
661
|
+
contact_ids uuid[],
|
|
662
|
+
contact_names text[],
|
|
663
|
+
issue text
|
|
664
|
+
)
|
|
665
|
+
language plpgsql
|
|
666
|
+
stable
|
|
667
|
+
as $$
|
|
668
|
+
declare
|
|
669
|
+
v_country char(2);
|
|
670
|
+
v_prefix char(2);
|
|
671
|
+
v_currency char(3);
|
|
672
|
+
v_round money_rounding;
|
|
673
|
+
v_files declaration_period;
|
|
674
|
+
v_accepts declaration_period[];
|
|
675
|
+
v_asked declaration_period;
|
|
676
|
+
begin
|
|
677
|
+
select c.fiscal_country, c.currency_code into v_country, v_currency
|
|
678
|
+
from companies c where c.id = p_company_id;
|
|
679
|
+
if not found then
|
|
680
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
681
|
+
end if;
|
|
682
|
+
if not exists (select 1 from territories) then
|
|
683
|
+
raise exception 'no_territories: apply supabase/seed/00_territories.sql';
|
|
684
|
+
end if;
|
|
685
|
+
|
|
686
|
+
-- Which period this is, when the caller said which statement they are
|
|
687
|
+
-- filing. Five things have to hold: a form was named, it exists, the company
|
|
688
|
+
-- has recorded a cadence for it, the dates are themselves a whole cadence the
|
|
689
|
+
-- form is filed on, and the two differ. A monthly statement from a quarterly
|
|
690
|
+
-- filer is the ordinary case in three of the four countries read while this
|
|
691
|
+
-- was written, and nothing here touches it: the cadence read is the
|
|
692
|
+
-- statement's own.
|
|
693
|
+
if p_report_code is not null then
|
|
694
|
+
select t.periods into v_accepts
|
|
695
|
+
from tax_report_templates t
|
|
696
|
+
where t.code = p_report_code
|
|
697
|
+
and t.valid_from <= p_to
|
|
698
|
+
and (t.valid_to is null or t.valid_to >= p_to)
|
|
699
|
+
order by t.country
|
|
700
|
+
limit 1;
|
|
701
|
+
if v_accepts is null then
|
|
702
|
+
raise exception 'unknown_tax_report: % is not a declaration form in force on %',
|
|
703
|
+
p_report_code, p_to;
|
|
704
|
+
end if;
|
|
705
|
+
v_files := filing_period(p_company_id, p_report_code);
|
|
706
|
+
v_asked := declaration_period_of(p_from, p_to);
|
|
707
|
+
if v_files is not null
|
|
708
|
+
and v_asked is not null
|
|
709
|
+
and v_files = any(v_accepts)
|
|
710
|
+
and v_asked = any(v_accepts)
|
|
711
|
+
and v_asked <> v_files then
|
|
712
|
+
raise exception
|
|
713
|
+
'wrong_declaration_period: this company files % on %; % to % is a %',
|
|
714
|
+
v_files, p_report_code, p_from, p_to, v_asked;
|
|
715
|
+
end if;
|
|
716
|
+
end if;
|
|
717
|
+
|
|
718
|
+
v_prefix := vat_prefix_of(v_country);
|
|
719
|
+
v_round := rounding_of(p_company_id);
|
|
720
|
+
|
|
721
|
+
return query
|
|
722
|
+
with supply as (
|
|
723
|
+
select d.contact_id,
|
|
724
|
+
ct.name as contact_name,
|
|
725
|
+
regexp_replace(t.treatment::text, '^intracom_', '') as nature,
|
|
726
|
+
upper(regexp_replace(coalesce(ct.vat_number, ''), '[^A-Za-z0-9]', '', 'g')) as vat_raw,
|
|
727
|
+
ct.country as contact_country,
|
|
728
|
+
e.entry_date,
|
|
729
|
+
e.document_id,
|
|
730
|
+
l.credit - l.debit as amount
|
|
731
|
+
from entry_lines l
|
|
732
|
+
join entries e on e.id = l.entry_id
|
|
733
|
+
join taxes t on t.id = l.tax_id
|
|
734
|
+
left join documents d on d.id = e.document_id
|
|
735
|
+
left join contacts ct on ct.id = d.contact_id
|
|
736
|
+
where l.company_id = p_company_id
|
|
737
|
+
and e.state = 'posted'
|
|
738
|
+
and e.entry_date between p_from and p_to
|
|
739
|
+
and not l.tax_line
|
|
740
|
+
and t.treatment::text like 'intracom!_%' escape '!'
|
|
741
|
+
and t.treatment::text not like 'intracom!_acquisition!_%' escape '!'
|
|
742
|
+
),
|
|
743
|
+
keyed as (
|
|
744
|
+
select s.*,
|
|
745
|
+
vat_prefix_of(
|
|
746
|
+
case when s.vat_raw ~ '^[A-Z]{2}' then substring(s.vat_raw from 1 for 2)
|
|
747
|
+
when s.vat_raw = '' then null
|
|
748
|
+
else s.contact_country
|
|
749
|
+
end
|
|
750
|
+
) as vat_country,
|
|
751
|
+
case when s.vat_raw = '' then null
|
|
752
|
+
when s.vat_raw ~ '^[A-Z]{2}' then substring(s.vat_raw from 3)
|
|
753
|
+
else s.vat_raw
|
|
754
|
+
end as vat_number
|
|
755
|
+
from supply s
|
|
756
|
+
),
|
|
757
|
+
scoped as (
|
|
758
|
+
select k.*, eu_vat_scope_of(k.vat_country, k.entry_date) as scope
|
|
759
|
+
from keyed k
|
|
760
|
+
),
|
|
761
|
+
judged as (
|
|
762
|
+
select s.*,
|
|
763
|
+
case
|
|
764
|
+
when s.contact_id is null then 'no_customer'
|
|
765
|
+
when s.vat_number is null or s.vat_number = '' then 'no_vat_number'
|
|
766
|
+
when s.vat_country is null then 'no_vat_country'
|
|
767
|
+
when s.vat_country = v_prefix then 'vat_country_is_the_company_country'
|
|
768
|
+
when s.scope = 'none' then 'vat_country_outside_the_union'
|
|
769
|
+
when s.scope <> 'full' and s.nature = 'services'
|
|
770
|
+
then 'vat_country_outside_the_union_for_this_supply'
|
|
771
|
+
end as issue
|
|
772
|
+
from scoped s
|
|
773
|
+
)
|
|
774
|
+
select j.vat_country::char(2),
|
|
775
|
+
j.vat_number,
|
|
776
|
+
j.nature,
|
|
777
|
+
round_amount(sum(j.amount), v_round),
|
|
778
|
+
v_currency,
|
|
779
|
+
count(distinct j.document_id)::integer,
|
|
780
|
+
array_agg(distinct j.contact_id) filter (where j.contact_id is not null),
|
|
781
|
+
array_agg(distinct j.contact_name) filter (where j.contact_name is not null),
|
|
782
|
+
j.issue
|
|
783
|
+
from judged j
|
|
784
|
+
group by j.vat_country, j.vat_number, j.nature, j.issue,
|
|
785
|
+
case when j.issue is not null then j.contact_id end
|
|
786
|
+
having round_amount(sum(j.amount), v_round) <> 0
|
|
787
|
+
order by (j.issue is not null), j.vat_country, j.vat_number, j.nature;
|
|
788
|
+
end;
|
|
789
|
+
$$;
|
|
790
|
+
|
|
791
|
+
comment on function ec_sales_list(uuid, date, date, text) is
|
|
792
|
+
'The recapitulative statement of intra-Community supplies for a period: one line per customer VAT identification number and per nature — goods, services, and whatever the treatment vocabulary gains next — summed from the posted ledger in the company''s currency, credit notes deducted. The country of a line is the prefix the customer''s numbers carry, read from `territories`, so a Greek customer is listed under EL. A supply that cannot be declared comes back with the reason in `issue` rather than being left out. Name the form in p_report_code to have the period checked against the cadence this company files **that statement** on, which is not the cadence of its periodic return in any country read so far; name none and nothing is refused. No country rule lives in this function.';
|
|
793
|
+
|
|
794
|
+
revoke execute on function ec_sales_list(uuid, date, date, text) from public, anon;
|
|
795
|
+
grant execute on function ec_sales_list(uuid, date, date, text) to authenticated, service_role;
|