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,1211 @@
|
|
|
1
|
+
-- Ekwo OS — a tax point the law can be written in, and an engine that reads it.
|
|
2
|
+
--
|
|
3
|
+
-- `country_defaults.tax_point_rule` has been a single word since
|
|
4
|
+
-- `20260912111751` — `invoice_date`, `delivery_date`, `payment_date` — and two
|
|
5
|
+
-- things were true of it. No function read it: `docs/decisions.md` recorded
|
|
6
|
+
-- the column as "declared, no reader", and `post_document()` dated a tax by
|
|
7
|
+
-- the day the entry was booked on, which is a different question. And two
|
|
8
|
+
-- packs could not say their own law in one word, which their own citations
|
|
9
|
+
-- already admitted.
|
|
10
|
+
--
|
|
11
|
+
-- **Luxembourg.** Art. 21 of the modified law of 12 February 1979: "Le fait
|
|
12
|
+
-- générateur de la taxe intervient et la taxe devient exigible au moment où la
|
|
13
|
+
-- livraison de biens ou la prestation de services est effectuée." The invoice
|
|
14
|
+
-- is art. 24, par. 1er: "Par dérogation aux articles 21, 22 et 23, lorsqu'il y
|
|
15
|
+
-- a obligation d'émettre une facture, la taxe devient exigible : a) lors de
|
|
16
|
+
-- l'émission de la facture si elle est émise dans le délai visé à l'article
|
|
17
|
+
-- 63, paragraphe 5 ; b) le jour où expire le délai visé au point a) en
|
|
18
|
+
-- l'absence d'émission de la facture dans ce délai." The pack declared
|
|
19
|
+
-- `invoice_date`, which is the derogation given as if it were the principle.
|
|
20
|
+
--
|
|
21
|
+
-- **Belgium is the same shape**, and nobody had looked. Art. 16, § 1er and
|
|
22
|
+
-- art. 22, § 1er put the fait générateur and the exigibility at the moment of
|
|
23
|
+
-- the supply; art. 17, § 1er and art. 22bis, § 1er derogate — "au moment de
|
|
24
|
+
-- l'émission de la facture, à concurrence du montant facturé, peu importe que
|
|
25
|
+
-- l'émission de cette facture ait lieu avant ou après le moment où la
|
|
26
|
+
-- livraison est effectuée", with the fifteenth day of the following month
|
|
27
|
+
-- where no invoice was issued before it. The pack's citation said all of this
|
|
28
|
+
-- and the word said `invoice_date`.
|
|
29
|
+
--
|
|
30
|
+
-- **Estonia.** KMS § 11 lg 1, in the official English translation: "The time
|
|
31
|
+
-- of supply or the time of receipt of services is deemed to be the date on
|
|
32
|
+
-- which the **first** of one of the following acts is performed: 1) the goods
|
|
33
|
+
-- are dispatched or made available to the purchaser, or the services are
|
|
34
|
+
-- provided; 2) full or partial payment is received for the goods or services
|
|
35
|
+
-- […]". Two branches, and the pack declared the first one. The invoice is
|
|
36
|
+
-- **not** a branch of lg 1 — it belongs to lg 2, which is the separate rule
|
|
37
|
+
-- for intra-Community supply — so the rule Estonia keeps is the earliest of a
|
|
38
|
+
-- supply and a payment, and nothing else.
|
|
39
|
+
--
|
|
40
|
+
-- **France was read and is right as it stands.** CGI art. 269, 1, a puts the
|
|
41
|
+
-- fait générateur at the supply and art. 269, 2, a makes the tax chargeable
|
|
42
|
+
-- then, an acompte advancing it to the amount received; services are art. 269,
|
|
43
|
+
-- 2, c, chargeable on collection unless the taxpayer opts for the débits, and
|
|
44
|
+
-- that half is `taxes.cash_basis` on the service taxes by a decision this
|
|
45
|
+
-- repository already wrote down. `delivery_date` is France's principle.
|
|
46
|
+
--
|
|
47
|
+
-- ---------------------------------------------------------------------------
|
|
48
|
+
-- What this migration does
|
|
49
|
+
-- ---------------------------------------------------------------------------
|
|
50
|
+
--
|
|
51
|
+
-- **Two words, and the vocabulary stays closed.** `invoice_if_issued` is the
|
|
52
|
+
-- Luxembourg and Belgian shape: the supply is the principle, an invoice
|
|
53
|
+
-- displaces it where the country requires one and one was issued.
|
|
54
|
+
-- `earliest_of_delivery_or_payment` is the Estonian shape: whichever of the
|
|
55
|
+
-- two happened first. Both say what they mean in their own name, which is the
|
|
56
|
+
-- rule `applies_when` set for a closed vocabulary — an accountant reads the
|
|
57
|
+
-- value and knows what it is — and neither is an expression a pack could
|
|
58
|
+
-- write a condition in.
|
|
59
|
+
--
|
|
60
|
+
-- **One reader, named.** `tax_point_of()` is the only function that reads
|
|
61
|
+
-- `country_defaults.tax_point_rule`, the way `numbering_rules()` is the only
|
|
62
|
+
-- one that reads `number_format`. The vocabulary is a `case` inside it and
|
|
63
|
+
-- nowhere else; `post_document()` calls it and knows no country.
|
|
64
|
+
--
|
|
65
|
+
-- **Two columns, because a tax point is not an accounting date.** A December
|
|
66
|
+
-- invoice booked in January is one entry on one date and a tax due in
|
|
67
|
+
-- December, and the ledger has to be able to say both. `documents.tax_point_date`
|
|
68
|
+
-- is EN 16931's BT-7, which the table lacked and which an e-invoice carries;
|
|
69
|
+
-- where it is filled it is the answer, because a rule is a default and a
|
|
70
|
+
-- stated fact is not. `entry_lines.tax_point_date` is what the declaration
|
|
71
|
+
-- reads, beside the `declaration_box` and the `box_amount` already there.
|
|
72
|
+
--
|
|
73
|
+
-- **Null is the reading of every ledger written before today.** No backfill
|
|
74
|
+
-- and no default: a line with no tax point is declared on its entry's date,
|
|
75
|
+
-- which is what `vat_return()` did for every line until this file. So the
|
|
76
|
+
-- goldens of the six packs do not move — none of their documents carries a
|
|
77
|
+
-- delivery date or an accounting date apart from its own — and the change is
|
|
78
|
+
-- visible only where a document says something the engine could not read
|
|
79
|
+
-- before.
|
|
80
|
+
--
|
|
81
|
+
-- **Where the payment branch stops.** `post_document()` passes no payment
|
|
82
|
+
-- date, and it is not an omission: matching is recorded against the entry
|
|
83
|
+
-- this function is writing, so at posting time no payment of this document
|
|
84
|
+
-- exists. A rule whose earliest branch is a payment therefore falls back on
|
|
85
|
+
-- the branch it can see, and the payment half is honoured only where the tax
|
|
86
|
+
-- itself says `cash_basis` — `settle_cash_basis_tax()`, which now writes the
|
|
87
|
+
-- collection date onto both legs of its transfer instead of leaving the
|
|
88
|
+
-- entry's date to speak for it. A prepayment that is not a cash-basis tax is a
|
|
89
|
+
-- document Ekwo does not have; `docs/international.md` says so under its own
|
|
90
|
+
-- heading rather than this file inventing one.
|
|
91
|
+
--
|
|
92
|
+
-- ---------------------------------------------------------------------------
|
|
93
|
+
-- The other half of ST31: a source that reaches the database
|
|
94
|
+
-- ---------------------------------------------------------------------------
|
|
95
|
+
--
|
|
96
|
+
-- `20260915161842` turned `certification.sources` into a register and gave
|
|
97
|
+
-- `tax_templates` and `tax_report_box_templates` a `source_key`. Three places
|
|
98
|
+
-- were left: a chart, a statement and a statement line each name a `source` in
|
|
99
|
+
-- the pack and had nowhere to put it, so `legal_reference` reached the
|
|
100
|
+
-- database with no way to open the text behind it. Three nullable columns,
|
|
101
|
+
-- filled by the compiled seed like the two before them, no backfill and no
|
|
102
|
+
-- foreign key — `ekwo pack check` is where an unknown key is refused, because
|
|
103
|
+
-- the register lives in a jsonb column of another table.
|
|
104
|
+
|
|
105
|
+
-- ---------------------------------------------------------------------------
|
|
106
|
+
-- The vocabulary widens
|
|
107
|
+
-- ---------------------------------------------------------------------------
|
|
108
|
+
--
|
|
109
|
+
-- Dropped and re-added rather than edited: the constraint of `20260912111751`
|
|
110
|
+
-- is published, and this is the migration that changes it.
|
|
111
|
+
|
|
112
|
+
alter table country_defaults
|
|
113
|
+
drop constraint if exists country_defaults_tax_point_rule_known;
|
|
114
|
+
|
|
115
|
+
alter table country_defaults
|
|
116
|
+
add constraint country_defaults_tax_point_rule_known
|
|
117
|
+
check (tax_point_rule is null or tax_point_rule in (
|
|
118
|
+
'invoice_date',
|
|
119
|
+
'delivery_date',
|
|
120
|
+
'payment_date',
|
|
121
|
+
'invoice_if_issued',
|
|
122
|
+
'earliest_of_delivery_or_payment'
|
|
123
|
+
));
|
|
124
|
+
|
|
125
|
+
comment on column country_defaults.tax_point_rule is
|
|
126
|
+
'When the tax becomes chargeable under this country''s general rule, in a closed vocabulary: invoice_date (the invoice fixes it), delivery_date (the supply fixes it), payment_date (collection fixes it), invoice_if_issued (the supply is the principle and an invoice displaces it where the country requires one — Belgium art. 17 and 22bis, Luxembourg art. 24), earliest_of_delivery_or_payment (whichever came first — Estonia KMS § 11 lg 1). A tax that departs from the country rule says so itself, with cash_basis. tax_point_of() is the only function that reads this column.';
|
|
127
|
+
|
|
128
|
+
-- ---------------------------------------------------------------------------
|
|
129
|
+
-- Where a tax point is written down
|
|
130
|
+
-- ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
alter table documents
|
|
133
|
+
add column if not exists tax_point_date date; -- BT-7
|
|
134
|
+
|
|
135
|
+
comment on column documents.tax_point_date is
|
|
136
|
+
'EN 16931 BT-7: the day the tax on this document falls due, which is not the day the entry is booked on. Given on the document it is kept — an e-invoice states its own tax point and a stated fact outranks a rule — and left null it is worked out by post_document() from the country rule and written back here.';
|
|
137
|
+
|
|
138
|
+
alter table entry_lines
|
|
139
|
+
add column if not exists tax_point_date date;
|
|
140
|
+
|
|
141
|
+
comment on column entry_lines.tax_point_date is
|
|
142
|
+
'The day the tax of this line fell due, from the document''s tax point or, on the transfer that makes a cash-basis tax due, from the collection. It is the date vat_return() puts the figure in a period by. Null on a line no tax posting wrote and on every line written before this column existed: the entry''s own date then answers, which is the reading the declaration always had.';
|
|
143
|
+
|
|
144
|
+
-- ---------------------------------------------------------------------------
|
|
145
|
+
-- The one reader of the country rule
|
|
146
|
+
-- ---------------------------------------------------------------------------
|
|
147
|
+
--
|
|
148
|
+
-- Deliberately not `immutable`: it reads two tables. `stable` is what
|
|
149
|
+
-- `numbering_rules()` is, for the same reason.
|
|
150
|
+
--
|
|
151
|
+
-- The dates it is given are the document's own. `p_document_date` is the
|
|
152
|
+
-- invoice date and is never null on a document; `p_delivery_date` is BT-72 and
|
|
153
|
+
-- usually is; `p_payment_date` is the day the price was received where that is
|
|
154
|
+
-- already known, and null where it is not.
|
|
155
|
+
--
|
|
156
|
+
-- A country that declares no rule gets null, not a borrowed one. That is
|
|
157
|
+
-- invariant 2 of `CONTRIBUTING.md`: there is no fallback country, and a reader
|
|
158
|
+
-- that needs a value a pack did not give says so rather than answering with
|
|
159
|
+
-- another country's law. Null here means "the entry's date", which is what the
|
|
160
|
+
-- declaration read before any of this existed, so a pack that says nothing
|
|
161
|
+
-- loses nothing.
|
|
162
|
+
|
|
163
|
+
create or replace function tax_point_of(
|
|
164
|
+
p_company_id uuid,
|
|
165
|
+
p_document_date date,
|
|
166
|
+
p_delivery_date date default null,
|
|
167
|
+
p_payment_date date default null
|
|
168
|
+
)
|
|
169
|
+
returns date
|
|
170
|
+
language sql
|
|
171
|
+
stable
|
|
172
|
+
as $$
|
|
173
|
+
select case d.tax_point_rule
|
|
174
|
+
-- The invoice fixes it, full stop.
|
|
175
|
+
when 'invoice_date' then p_document_date
|
|
176
|
+
-- The supply fixes it. A document that does not say when it was
|
|
177
|
+
-- delivered is one where the invoice is all there is to go on.
|
|
178
|
+
when 'delivery_date' then coalesce(p_delivery_date, p_document_date)
|
|
179
|
+
-- Collection fixes it, and until there is one there is no answer.
|
|
180
|
+
when 'payment_date' then p_payment_date
|
|
181
|
+
-- The supply is the principle and the invoice is the derogation.
|
|
182
|
+
-- A document being booked here is an invoice and carries its date,
|
|
183
|
+
-- so the derogation applies; the delivery is what is left when it
|
|
184
|
+
-- does not.
|
|
185
|
+
when 'invoice_if_issued' then coalesce(p_document_date, p_delivery_date)
|
|
186
|
+
-- Whichever of the two came first. `least` ignores a null, which is
|
|
187
|
+
-- exactly right: a branch nobody can date is a branch that has not
|
|
188
|
+
-- happened.
|
|
189
|
+
when 'earliest_of_delivery_or_payment'
|
|
190
|
+
then least(coalesce(p_delivery_date, p_document_date), p_payment_date)
|
|
191
|
+
else null
|
|
192
|
+
end
|
|
193
|
+
from companies c
|
|
194
|
+
left join country_defaults d on d.country = c.fiscal_country
|
|
195
|
+
where c.id = p_company_id;
|
|
196
|
+
$$;
|
|
197
|
+
|
|
198
|
+
comment on function tax_point_of(uuid, date, date, date) is
|
|
199
|
+
'The day the tax on a document falls due, under the rule its company''s country declares. The only function that reads country_defaults.tax_point_rule, and the only place the vocabulary of that column is written out. Null where the country declares no rule or where the rule names a date the caller does not have, and null means the entry''s own date to every reader of it.';
|
|
200
|
+
|
|
201
|
+
revoke execute on function tax_point_of(uuid, date, date, date) from public, anon;
|
|
202
|
+
grant execute on function tax_point_of(uuid, date, date, date) to authenticated, service_role;
|
|
203
|
+
|
|
204
|
+
-- ---------------------------------------------------------------------------
|
|
205
|
+
-- Three columns so that a compiled reference can be opened
|
|
206
|
+
-- ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
alter table chart_templates
|
|
209
|
+
add column if not exists source_key text;
|
|
210
|
+
|
|
211
|
+
comment on column chart_templates.source_key is
|
|
212
|
+
'Key of the entry in country_packs.sources where this chart''s legal_reference can be read. Null where the pack names none.';
|
|
213
|
+
|
|
214
|
+
alter table statement_templates
|
|
215
|
+
add column if not exists source_key text;
|
|
216
|
+
|
|
217
|
+
comment on column statement_templates.source_key is
|
|
218
|
+
'Key of the entry in country_packs.sources where this statement''s legal_reference can be read. Null where the pack names none.';
|
|
219
|
+
|
|
220
|
+
alter table statement_line_templates
|
|
221
|
+
add column if not exists source_key text;
|
|
222
|
+
|
|
223
|
+
comment on column statement_line_templates.source_key is
|
|
224
|
+
'Key of the entry in country_packs.sources where this line''s legal_reference can be read. Null where the pack names none.';
|
|
225
|
+
|
|
226
|
+
-- No table, view or sequence is created above: a column added to a table is
|
|
227
|
+
-- reachable by whoever could already select it, under the policies it already
|
|
228
|
+
-- has. The one object created is `tax_point_of()`, and it carries its own
|
|
229
|
+
-- revoke and grant.
|
|
230
|
+
|
|
231
|
+
-- ---------------------------------------------------------------------------
|
|
232
|
+
-- post_document() reads the country rule
|
|
233
|
+
-- ---------------------------------------------------------------------------
|
|
234
|
+
--
|
|
235
|
+
-- Replaced whole, because a function is replaced whole in PostgreSQL.
|
|
236
|
+
-- Everything but the tax point is `20260916103000` unchanged.
|
|
237
|
+
|
|
238
|
+
create or replace function post_document(p_document_id uuid)
|
|
239
|
+
returns entries
|
|
240
|
+
language plpgsql
|
|
241
|
+
as $$
|
|
242
|
+
declare
|
|
243
|
+
v_doc documents%rowtype;
|
|
244
|
+
v_entry entries%rowtype;
|
|
245
|
+
v_journal uuid;
|
|
246
|
+
v_date date;
|
|
247
|
+
v_is_sale boolean;
|
|
248
|
+
v_is_credit boolean;
|
|
249
|
+
v_kind tax_document_kind;
|
|
250
|
+
v_base_credit boolean;
|
|
251
|
+
v_seq integer := 0;
|
|
252
|
+
v_contact uuid;
|
|
253
|
+
v_maturity date;
|
|
254
|
+
v_terms smallint;
|
|
255
|
+
v_counterpart uuid;
|
|
256
|
+
-- Plain `numeric`, not `numeric(16, 2)`: a local that carries a scale is a
|
|
257
|
+
-- second rounding rule hiding in a declaration, and it is not the currency's.
|
|
258
|
+
-- The only thing that rounds here is `round_amount`.
|
|
259
|
+
v_diff numeric;
|
|
260
|
+
v_diff_cur numeric;
|
|
261
|
+
v_amount numeric;
|
|
262
|
+
v_book numeric;
|
|
263
|
+
v_box_amount numeric;
|
|
264
|
+
v_share numeric;
|
|
265
|
+
v_share_book numeric;
|
|
266
|
+
v_share_box numeric;
|
|
267
|
+
v_left numeric;
|
|
268
|
+
v_left_box numeric;
|
|
269
|
+
v_side_left numeric;
|
|
270
|
+
v_side_left_neg numeric;
|
|
271
|
+
v_side_credit boolean;
|
|
272
|
+
v_label text;
|
|
273
|
+
v_rate numeric(18, 8);
|
|
274
|
+
v_home char(3);
|
|
275
|
+
v_foreign boolean;
|
|
276
|
+
v_total_cur numeric;
|
|
277
|
+
-- Two currencies, one method: the document is stated in its own and the
|
|
278
|
+
-- ledger keeps the company's, and a yen invoice paid in euros rounds each
|
|
279
|
+
-- side at the decimals that side has.
|
|
280
|
+
v_round money_rounding;
|
|
281
|
+
v_book_round money_rounding;
|
|
282
|
+
v_cash boolean;
|
|
283
|
+
v_transition uuid;
|
|
284
|
+
-- The day the tax falls due, which is not the day the entry is booked on.
|
|
285
|
+
v_tax_point date;
|
|
286
|
+
v_postings integer;
|
|
287
|
+
-- Where the three parties of this document are, resolved once and only when
|
|
288
|
+
-- a tax on it asks. `document_territory()` is the ladder.
|
|
289
|
+
v_terr_seller text;
|
|
290
|
+
v_terr_buyer text;
|
|
291
|
+
v_terr_supply text;
|
|
292
|
+
r record;
|
|
293
|
+
p record;
|
|
294
|
+
g record;
|
|
295
|
+
begin
|
|
296
|
+
select * into v_doc from documents where id = p_document_id for update;
|
|
297
|
+
if not found then
|
|
298
|
+
raise exception 'unknown_document: document % does not exist', p_document_id;
|
|
299
|
+
end if;
|
|
300
|
+
if v_doc.state = 'posted' then
|
|
301
|
+
raise exception 'document_already_posted: document % is already posted', p_document_id;
|
|
302
|
+
end if;
|
|
303
|
+
if v_doc.state = 'cancelled' then
|
|
304
|
+
raise exception 'document_cancelled: document % cannot be posted', p_document_id;
|
|
305
|
+
end if;
|
|
306
|
+
if v_doc.entry_id is not null then
|
|
307
|
+
raise exception 'document_already_booked: document % already points at entry %',
|
|
308
|
+
p_document_id, v_doc.entry_id;
|
|
309
|
+
end if;
|
|
310
|
+
|
|
311
|
+
if v_doc.doc_type in ('sale_quote', 'purchase_order') then
|
|
312
|
+
raise exception 'document_not_accountable: a % is not booked', v_doc.doc_type;
|
|
313
|
+
end if;
|
|
314
|
+
|
|
315
|
+
v_is_sale := v_doc.doc_type in ('sale_invoice', 'sale_credit_note');
|
|
316
|
+
v_is_credit := v_doc.doc_type in ('sale_credit_note', 'purchase_credit_note');
|
|
317
|
+
v_kind := case when v_is_credit then 'credit_note' else 'invoice' end::tax_document_kind;
|
|
318
|
+
-- Sale invoice and purchase credit note credit the base; the other two debit it.
|
|
319
|
+
v_base_credit := (v_is_sale <> v_is_credit);
|
|
320
|
+
|
|
321
|
+
v_date := coalesce(v_doc.accounting_date, v_doc.document_date);
|
|
322
|
+
|
|
323
|
+
if not exists (
|
|
324
|
+
select 1 from document_lines
|
|
325
|
+
where document_id = p_document_id and line_type = 'product' and amount_untaxed <> 0
|
|
326
|
+
) then
|
|
327
|
+
raise exception 'document_empty: document % has no billable line', p_document_id;
|
|
328
|
+
end if;
|
|
329
|
+
|
|
330
|
+
-- A fixed-amount tax has no basis to spread over lines; refuse rather than
|
|
331
|
+
-- guess.
|
|
332
|
+
if exists (
|
|
333
|
+
select 1 from document_lines l join taxes t on t.id = l.tax_id
|
|
334
|
+
where l.document_id = p_document_id and t.amount_type <> 'percent'
|
|
335
|
+
) then
|
|
336
|
+
raise exception 'unsupported_tax_amount_type: only percentage taxes can be posted';
|
|
337
|
+
end if;
|
|
338
|
+
|
|
339
|
+
-- Every tax used must be in force on the accounting date.
|
|
340
|
+
for r in
|
|
341
|
+
select distinct t.id, t.code, t.valid_from, t.valid_to
|
|
342
|
+
from document_lines l join taxes t on t.id = l.tax_id
|
|
343
|
+
where l.document_id = p_document_id
|
|
344
|
+
loop
|
|
345
|
+
if v_date < r.valid_from or (r.valid_to is not null and v_date > r.valid_to) then
|
|
346
|
+
raise exception 'tax_not_in_force: tax % is not applicable on %', r.code, v_date;
|
|
347
|
+
end if;
|
|
348
|
+
end loop;
|
|
349
|
+
|
|
350
|
+
-- Every tax that names a territory must find the parties where it says they
|
|
351
|
+
-- are. This is the one thing the core may do with a territory condition: it
|
|
352
|
+
-- refuses a tax that cannot apply, and it never chooses, substitutes or
|
|
353
|
+
-- suggests one — the core picks no tax for anybody, in any country.
|
|
354
|
+
--
|
|
355
|
+
-- The resolution is skipped entirely where nothing asks, which is every
|
|
356
|
+
-- document of every pack written before this migration.
|
|
357
|
+
if exists (
|
|
358
|
+
select 1
|
|
359
|
+
from document_lines l
|
|
360
|
+
join taxes t on t.id = l.tax_id
|
|
361
|
+
where l.document_id = p_document_id
|
|
362
|
+
and (t.applies_seller_territory is not null
|
|
363
|
+
or t.applies_buyer_territory is not null
|
|
364
|
+
or t.applies_supply_territory is not null)
|
|
365
|
+
) then
|
|
366
|
+
v_terr_seller := document_territory(p_document_id, 'seller');
|
|
367
|
+
v_terr_buyer := document_territory(p_document_id, 'buyer');
|
|
368
|
+
v_terr_supply := document_territory(p_document_id, 'supply');
|
|
369
|
+
|
|
370
|
+
for r in
|
|
371
|
+
select distinct t.code,
|
|
372
|
+
t.applies_seller_territory as seller,
|
|
373
|
+
t.applies_buyer_territory as buyer,
|
|
374
|
+
t.applies_supply_territory as supply
|
|
375
|
+
from document_lines l
|
|
376
|
+
join taxes t on t.id = l.tax_id
|
|
377
|
+
where l.document_id = p_document_id
|
|
378
|
+
and (t.applies_seller_territory is not null
|
|
379
|
+
or t.applies_buyer_territory is not null
|
|
380
|
+
or t.applies_supply_territory is not null)
|
|
381
|
+
order by 1
|
|
382
|
+
loop
|
|
383
|
+
for p in
|
|
384
|
+
select v.party, v.wanted, v.actual, v.hint
|
|
385
|
+
from (values
|
|
386
|
+
('seller', r.seller, v_terr_seller,
|
|
387
|
+
'territory_code on the party that sells, or the country beside it'),
|
|
388
|
+
('buyer', r.buyer, v_terr_buyer,
|
|
389
|
+
'territory_code on the party that buys, or the country beside it'),
|
|
390
|
+
('supply', r.supply, v_terr_supply,
|
|
391
|
+
'supply_territory_code or delivery_country on the document')
|
|
392
|
+
) as v (party, wanted, actual, hint)
|
|
393
|
+
where v.wanted is not null
|
|
394
|
+
order by v.party
|
|
395
|
+
loop
|
|
396
|
+
if p.actual is null then
|
|
397
|
+
raise exception 'no_party_territory: tax % applies where the % is in %, and nothing on this document says where the % is; set %',
|
|
398
|
+
r.code, p.party, p.wanted, p.party, p.hint;
|
|
399
|
+
end if;
|
|
400
|
+
if not territory_within(p.actual, p.wanted) then
|
|
401
|
+
raise exception 'tax_territory_mismatch: tax % applies where the % is in %; on this document the % is in %',
|
|
402
|
+
r.code, p.party, p.wanted, p.party, p.actual;
|
|
403
|
+
end if;
|
|
404
|
+
end loop;
|
|
405
|
+
end loop;
|
|
406
|
+
end if;
|
|
407
|
+
|
|
408
|
+
-- Totals are derived; make sure they reflect the lines as they stand now.
|
|
409
|
+
perform documents_refresh_totals(p_document_id);
|
|
410
|
+
select * into v_doc from documents where id = p_document_id;
|
|
411
|
+
|
|
412
|
+
select c.currency_code into v_home from companies c where c.id = v_doc.company_id;
|
|
413
|
+
v_rate := v_doc.exchange_rate;
|
|
414
|
+
v_foreign := v_doc.currency_code <> v_home;
|
|
415
|
+
v_round := rounding_of(v_doc.company_id, v_doc.currency_code);
|
|
416
|
+
v_book_round := rounding_of(v_doc.company_id);
|
|
417
|
+
|
|
418
|
+
-- When the tax on this document falls due, under the rule its country
|
|
419
|
+
-- declares. A document that carries BT-7 has been told and nobody overrides
|
|
420
|
+
-- it; otherwise `tax_point_of()` reads the country's word. The payment
|
|
421
|
+
-- argument is null here and says so: matching needs the entry this function
|
|
422
|
+
-- is writing, so at posting time no payment of this document has been
|
|
423
|
+
-- recorded, and a rule whose earliest branch is a payment falls back on the
|
|
424
|
+
-- branch it can see. Null comes back where the rule is `payment_date` or the
|
|
425
|
+
-- country declared none, and null on a line means "the entry's own date",
|
|
426
|
+
-- which is the reading every ledger written before this one had.
|
|
427
|
+
v_tax_point := coalesce(
|
|
428
|
+
v_doc.tax_point_date,
|
|
429
|
+
tax_point_of(v_doc.company_id, v_doc.document_date, v_doc.delivery_date, null));
|
|
430
|
+
|
|
431
|
+
v_journal := coalesce(
|
|
432
|
+
v_doc.journal_id,
|
|
433
|
+
case when v_is_sale
|
|
434
|
+
then (select sales_journal_id from companies where id = v_doc.company_id)
|
|
435
|
+
else (select purchase_journal_id from companies where id = v_doc.company_id)
|
|
436
|
+
end
|
|
437
|
+
);
|
|
438
|
+
if v_journal is null then
|
|
439
|
+
raise exception 'no_journal: set journal_id on the document or a default journal on the company';
|
|
440
|
+
end if;
|
|
441
|
+
|
|
442
|
+
perform assert_period_open(v_doc.company_id, v_date, true);
|
|
443
|
+
|
|
444
|
+
v_label := coalesce(v_doc.number, v_doc.supplier_reference, 'document');
|
|
445
|
+
|
|
446
|
+
insert into entries (company_id, journal_id, fiscal_year_id, entry_date, reference,
|
|
447
|
+
description, state, document_id, currency_code)
|
|
448
|
+
values (v_doc.company_id, v_journal, fiscal_year_at(v_doc.company_id, v_date), v_date,
|
|
449
|
+
coalesce(v_doc.number, v_doc.supplier_reference),
|
|
450
|
+
v_label || case when v_doc.supplier_reference is not null and v_doc.number is not null
|
|
451
|
+
then ' / ' || v_doc.supplier_reference else '' end,
|
|
452
|
+
'draft', p_document_id, v_doc.currency_code)
|
|
453
|
+
returning * into v_entry;
|
|
454
|
+
|
|
455
|
+
-- ------------------------------------------------------------------ bases
|
|
456
|
+
for r in
|
|
457
|
+
select l.account_id,
|
|
458
|
+
l.tax_id,
|
|
459
|
+
sum(l.amount_untaxed) as base_amount,
|
|
460
|
+
min(l.sequence) as seq,
|
|
461
|
+
string_agg(distinct l.name, ', ') as label
|
|
462
|
+
from document_lines l
|
|
463
|
+
where l.document_id = p_document_id
|
|
464
|
+
and l.line_type = 'product'
|
|
465
|
+
group by l.account_id, l.tax_id
|
|
466
|
+
having sum(l.amount_untaxed) <> 0
|
|
467
|
+
order by 4
|
|
468
|
+
loop
|
|
469
|
+
select tp.posting_type, tp.declaration_box, tp.factor_percent, tp.box_factor_percent,
|
|
470
|
+
coalesce(t.cash_basis, false) as cash_basis
|
|
471
|
+
into p
|
|
472
|
+
from tax_postings tp
|
|
473
|
+
join taxes t on t.id = tp.tax_id
|
|
474
|
+
where tp.tax_id = r.tax_id
|
|
475
|
+
and tp.document_kind = v_kind
|
|
476
|
+
and tp.posting_type = 'base'
|
|
477
|
+
limit 1;
|
|
478
|
+
|
|
479
|
+
v_amount := round_amount(r.base_amount * coalesce(p.factor_percent, 100) / 100, v_round);
|
|
480
|
+
v_book := round_amount(v_amount / v_rate, v_book_round);
|
|
481
|
+
v_seq := v_seq + 10;
|
|
482
|
+
|
|
483
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name,
|
|
484
|
+
debit, credit, tax_id, tax_line, posting_type,
|
|
485
|
+
declaration_box, box_amount, tax_point_date,
|
|
486
|
+
currency_code, amount_currency)
|
|
487
|
+
values (v_entry.id, v_doc.company_id, r.account_id, v_seq, left(r.label, 200),
|
|
488
|
+
case when v_base_credit then 0 else v_book end,
|
|
489
|
+
case when v_base_credit then v_book else 0 end,
|
|
490
|
+
r.tax_id, false, p.posting_type,
|
|
491
|
+
case when coalesce(p.cash_basis, false) then null else p.declaration_box end,
|
|
492
|
+
case when p.declaration_box is null then null
|
|
493
|
+
else round_amount(r.base_amount * coalesce(p.box_factor_percent, 100) / 100 / v_rate,
|
|
494
|
+
v_book_round) end,
|
|
495
|
+
v_tax_point,
|
|
496
|
+
v_doc.currency_code,
|
|
497
|
+
case when v_foreign then v_amount end);
|
|
498
|
+
end loop;
|
|
499
|
+
|
|
500
|
+
-- ------------------------------------------------------------------ taxes
|
|
501
|
+
for r in
|
|
502
|
+
select s.tax_id, s.tax_code, s.tax_name, s.tax_amount
|
|
503
|
+
from document_tax_summary s
|
|
504
|
+
where s.document_id = p_document_id
|
|
505
|
+
and s.tax_id is not null
|
|
506
|
+
and s.tax_amount <> 0
|
|
507
|
+
order by s.tax_code
|
|
508
|
+
loop
|
|
509
|
+
select t.cash_basis, t.cash_basis_transition_account_id
|
|
510
|
+
into v_cash, v_transition
|
|
511
|
+
from taxes t where t.id = r.tax_id;
|
|
512
|
+
|
|
513
|
+
if v_cash then
|
|
514
|
+
-- A tax that waits needs somewhere to wait. Refuse by name rather than
|
|
515
|
+
-- book it on the account it is due on, which would make it due.
|
|
516
|
+
if v_transition is null then
|
|
517
|
+
raise exception 'no_cash_basis_account: tax % falls due on collection and names no transition account',
|
|
518
|
+
r.tax_code;
|
|
519
|
+
end if;
|
|
520
|
+
-- One posting per side, or the transition lines of a document cannot be
|
|
521
|
+
-- told apart when the matching sends each of them on. A tax whose
|
|
522
|
+
-- postings net out has nothing waiting to collect anyway.
|
|
523
|
+
select count(*) into v_postings
|
|
524
|
+
from tax_postings tp
|
|
525
|
+
where tp.tax_id = r.tax_id and tp.document_kind = v_kind
|
|
526
|
+
and tp.posting_type = 'tax';
|
|
527
|
+
if v_postings > 1 then
|
|
528
|
+
raise exception 'cash_basis_split_tax: tax % falls due on collection and has % tax postings; it takes one',
|
|
529
|
+
r.tax_code, v_postings;
|
|
530
|
+
end if;
|
|
531
|
+
if exists (select 1 from tax_postings tp
|
|
532
|
+
where tp.tax_id = r.tax_id and tp.document_kind = v_kind
|
|
533
|
+
and tp.posting_type = 'tax_on_base') then
|
|
534
|
+
raise exception 'cash_basis_tax_on_base: tax % falls due on collection and carries a non-deductible share; a cost is not deferred',
|
|
535
|
+
r.tax_code;
|
|
536
|
+
end if;
|
|
537
|
+
-- And it needs a box to fall due *into*. `settle_cash_basis_tax()` only
|
|
538
|
+
-- ever looks at lines that carry a `box_amount`, and a posting with no
|
|
539
|
+
-- `declaration_box` produces none — so the amount would sit on the
|
|
540
|
+
-- transition account for ever, settled by nothing and reported by
|
|
541
|
+
-- nothing, with no error anywhere. Refuse it here, where the pack can
|
|
542
|
+
-- still be corrected, rather than discover it in a balance years later.
|
|
543
|
+
if not exists (select 1 from tax_postings tp
|
|
544
|
+
where tp.tax_id = r.tax_id and tp.document_kind = v_kind
|
|
545
|
+
and tp.posting_type = 'tax'
|
|
546
|
+
and tp.declaration_box is not null) then
|
|
547
|
+
raise exception 'no_cash_basis_box: tax % falls due on collection and its posting names no declaration box; the amount would wait on the transition account and never settle',
|
|
548
|
+
r.tax_code;
|
|
549
|
+
end if;
|
|
550
|
+
end if;
|
|
551
|
+
|
|
552
|
+
-- The postings of one side share out the tax of the group; the last of
|
|
553
|
+
-- each side takes what is left. Until `tax_on_base` there was never more
|
|
554
|
+
-- than one posting per side, so this changes no existing tax by a cent —
|
|
555
|
+
-- and it is what keeps a 50/50 split honest: 0.63 becomes 0.32 and 0.31,
|
|
556
|
+
-- where rounding each half on its own would book 0.64 against a document
|
|
557
|
+
-- that totals 0.63.
|
|
558
|
+
v_side_left := null;
|
|
559
|
+
v_side_left_neg := null;
|
|
560
|
+
|
|
561
|
+
for p in
|
|
562
|
+
select tp.posting_type, tp.factor_percent, tp.account_id,
|
|
563
|
+
tp.declaration_box, tp.box_factor_percent,
|
|
564
|
+
case when tp.factor_percent >= 0 then 1 else -1 end as side,
|
|
565
|
+
sum(abs(tp.factor_percent))
|
|
566
|
+
over (partition by case when tp.factor_percent >= 0 then 1 else -1 end)
|
|
567
|
+
as side_factor,
|
|
568
|
+
row_number() over (
|
|
569
|
+
partition by case when tp.factor_percent >= 0 then 1 else -1 end
|
|
570
|
+
order by tp.sequence, tp.id)
|
|
571
|
+
= count(*) over (
|
|
572
|
+
partition by case when tp.factor_percent >= 0 then 1 else -1 end)
|
|
573
|
+
as is_last_of_side
|
|
574
|
+
from tax_postings tp
|
|
575
|
+
where tp.tax_id = r.tax_id
|
|
576
|
+
and tp.document_kind = v_kind
|
|
577
|
+
and tp.posting_type in ('tax', 'tax_on_base')
|
|
578
|
+
order by tp.sequence, tp.id
|
|
579
|
+
loop
|
|
580
|
+
-- The tax of the group was rounded once, in the view. Every posting is
|
|
581
|
+
-- a share of that one figure, never of a re-derived one.
|
|
582
|
+
if p.side >= 0 then
|
|
583
|
+
if v_side_left is null then
|
|
584
|
+
v_side_left := round_amount(r.tax_amount * p.side_factor / 100, v_round);
|
|
585
|
+
end if;
|
|
586
|
+
if p.is_last_of_side then
|
|
587
|
+
v_amount := v_side_left;
|
|
588
|
+
else
|
|
589
|
+
v_amount := round_amount(r.tax_amount * abs(p.factor_percent) / 100, v_round);
|
|
590
|
+
v_side_left := v_side_left - v_amount;
|
|
591
|
+
end if;
|
|
592
|
+
else
|
|
593
|
+
if v_side_left_neg is null then
|
|
594
|
+
v_side_left_neg := round_amount(r.tax_amount * p.side_factor / 100, v_round);
|
|
595
|
+
end if;
|
|
596
|
+
if p.is_last_of_side then
|
|
597
|
+
v_amount := v_side_left_neg;
|
|
598
|
+
else
|
|
599
|
+
v_amount := round_amount(r.tax_amount * abs(p.factor_percent) / 100, v_round);
|
|
600
|
+
v_side_left_neg := v_side_left_neg - v_amount;
|
|
601
|
+
end if;
|
|
602
|
+
end if;
|
|
603
|
+
|
|
604
|
+
if v_amount = 0 then
|
|
605
|
+
continue;
|
|
606
|
+
end if;
|
|
607
|
+
-- A positive factor keeps the side of the base, a negative one flips it.
|
|
608
|
+
v_side_credit := case when p.factor_percent >= 0 then v_base_credit else not v_base_credit end;
|
|
609
|
+
-- The box keeps its own rounding: `box_factor_percent` was always
|
|
610
|
+
-- independent from `factor_percent`, because a declaration figure is
|
|
611
|
+
-- not a ledger figure and only the ledger has to balance.
|
|
612
|
+
v_box_amount := case when p.declaration_box is null then null
|
|
613
|
+
else round_amount(r.tax_amount * p.box_factor_percent / 100 / v_rate,
|
|
614
|
+
v_book_round) end;
|
|
615
|
+
v_book := round_amount(v_amount / v_rate, v_book_round);
|
|
616
|
+
|
|
617
|
+
if p.posting_type = 'tax' then
|
|
618
|
+
v_seq := v_seq + 10;
|
|
619
|
+
|
|
620
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name,
|
|
621
|
+
debit, credit, tax_id, tax_line, posting_type,
|
|
622
|
+
declaration_box, box_amount, tax_point_date,
|
|
623
|
+
currency_code, amount_currency)
|
|
624
|
+
values (v_entry.id, v_doc.company_id,
|
|
625
|
+
case when v_cash then v_transition else p.account_id end,
|
|
626
|
+
v_seq, r.tax_name,
|
|
627
|
+
case when v_side_credit then 0 else v_book end,
|
|
628
|
+
case when v_side_credit then v_book else 0 end,
|
|
629
|
+
r.tax_id, true, p.posting_type,
|
|
630
|
+
case when v_cash then null else p.declaration_box end,
|
|
631
|
+
v_box_amount, v_tax_point, v_doc.currency_code,
|
|
632
|
+
case when v_foreign then v_amount end);
|
|
633
|
+
continue;
|
|
634
|
+
end if;
|
|
635
|
+
|
|
636
|
+
-- `tax_on_base`: the tax is a cost, so it lands on the accounts of the
|
|
637
|
+
-- lines it taxes, split in proportion to their base. The last share
|
|
638
|
+
-- takes whatever is left, so the shares add up to the amount that was
|
|
639
|
+
-- rounded once on the group and the entry still balances to the cent.
|
|
640
|
+
v_left := v_amount;
|
|
641
|
+
v_left_box := v_box_amount;
|
|
642
|
+
|
|
643
|
+
for g in
|
|
644
|
+
select account_id,
|
|
645
|
+
base_amount,
|
|
646
|
+
seq,
|
|
647
|
+
sum(base_amount) over () as total_base,
|
|
648
|
+
row_number() over (order by seq) = count(*) over () as is_last
|
|
649
|
+
from (
|
|
650
|
+
select l.account_id,
|
|
651
|
+
sum(l.amount_untaxed) as base_amount,
|
|
652
|
+
min(l.sequence) as seq
|
|
653
|
+
from document_lines l
|
|
654
|
+
where l.document_id = p_document_id
|
|
655
|
+
and l.line_type = 'product'
|
|
656
|
+
and l.tax_id = r.tax_id
|
|
657
|
+
group by l.account_id
|
|
658
|
+
having sum(l.amount_untaxed) <> 0
|
|
659
|
+
) as groups
|
|
660
|
+
order by seq
|
|
661
|
+
loop
|
|
662
|
+
if g.is_last then
|
|
663
|
+
v_share := v_left;
|
|
664
|
+
v_share_box := v_left_box;
|
|
665
|
+
else
|
|
666
|
+
v_share := round_amount(v_amount * g.base_amount / g.total_base, v_round);
|
|
667
|
+
v_share_box := case when v_box_amount is null then null
|
|
668
|
+
else round_amount(v_box_amount * g.base_amount / g.total_base,
|
|
669
|
+
v_book_round) end;
|
|
670
|
+
v_left := v_left - v_share;
|
|
671
|
+
v_left_box := v_left_box - v_share_box;
|
|
672
|
+
end if;
|
|
673
|
+
|
|
674
|
+
if v_share = 0 then
|
|
675
|
+
continue;
|
|
676
|
+
end if;
|
|
677
|
+
|
|
678
|
+
v_seq := v_seq + 10;
|
|
679
|
+
v_share_book := round_amount(v_share / v_rate, v_book_round);
|
|
680
|
+
|
|
681
|
+
-- `tax_line` stays false: the amount is on a base account and belongs
|
|
682
|
+
-- to the base side of the declaration, which is why the Belgian grids
|
|
683
|
+
-- 82 and 83 report it together with the base.
|
|
684
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name,
|
|
685
|
+
debit, credit, tax_id, tax_line, posting_type,
|
|
686
|
+
declaration_box, box_amount, tax_point_date,
|
|
687
|
+
currency_code, amount_currency)
|
|
688
|
+
values (v_entry.id, v_doc.company_id, g.account_id, v_seq, r.tax_name,
|
|
689
|
+
case when v_side_credit then 0 else v_share_book end,
|
|
690
|
+
case when v_side_credit then v_share_book else 0 end,
|
|
691
|
+
r.tax_id, false, p.posting_type,
|
|
692
|
+
p.declaration_box, v_share_box, v_tax_point, v_doc.currency_code,
|
|
693
|
+
case when v_foreign then v_share end);
|
|
694
|
+
end loop;
|
|
695
|
+
end loop;
|
|
696
|
+
end loop;
|
|
697
|
+
|
|
698
|
+
-- ------------------------------------------------------------ counterpart
|
|
699
|
+
select total_debit - total_credit into v_diff from entries where id = v_entry.id;
|
|
700
|
+
select coalesce(sum(case when l.debit > 0 then l.amount_currency else -l.amount_currency end), 0)
|
|
701
|
+
into v_diff_cur
|
|
702
|
+
from entry_lines l where l.entry_id = v_entry.id;
|
|
703
|
+
|
|
704
|
+
if v_diff = 0 then
|
|
705
|
+
raise exception 'document_counterpart_zero: document % produced a nil counterpart', p_document_id;
|
|
706
|
+
end if;
|
|
707
|
+
|
|
708
|
+
v_contact := commercial_entity(v_doc.contact_id);
|
|
709
|
+
v_counterpart := resolve_counterpart_account(v_doc.company_id, v_contact, v_is_sale);
|
|
710
|
+
|
|
711
|
+
select payment_terms_days into v_terms from contacts where id = v_contact;
|
|
712
|
+
v_maturity := coalesce(v_doc.due_date, v_doc.document_date + coalesce(v_terms, 30));
|
|
713
|
+
|
|
714
|
+
v_amount := abs(v_diff);
|
|
715
|
+
-- The counterpart balances the entry in both currencies. The total it is
|
|
716
|
+
-- checked against is the document's own, which is the currency
|
|
717
|
+
-- `amount_total` is stated in.
|
|
718
|
+
v_total_cur := case when v_foreign then abs(v_diff_cur) else v_amount end;
|
|
719
|
+
v_seq := v_seq + 10;
|
|
720
|
+
|
|
721
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name,
|
|
722
|
+
debit, credit, contact_id, date_maturity, currency_code,
|
|
723
|
+
amount_currency)
|
|
724
|
+
values (v_entry.id, v_doc.company_id, v_counterpart, v_seq, v_label,
|
|
725
|
+
case when v_diff > 0 then 0 else v_amount end,
|
|
726
|
+
case when v_diff > 0 then v_amount else 0 end,
|
|
727
|
+
v_contact, v_maturity, v_doc.currency_code,
|
|
728
|
+
case when v_foreign then v_total_cur end);
|
|
729
|
+
|
|
730
|
+
-- The ledger is right by construction. If the header disagrees, the header
|
|
731
|
+
-- is what is wrong, and we say so instead of quietly patching a line.
|
|
732
|
+
-- Half a unit of the document's own currency, which is what `0.005` used to
|
|
733
|
+
-- mean when every currency was assumed to have cents.
|
|
734
|
+
if abs(v_total_cur - abs(v_doc.amount_total)) > currency_unit(v_round) / 2 then
|
|
735
|
+
raise exception 'document_total_mismatch: document % totals % but its lines book %',
|
|
736
|
+
p_document_id, v_doc.amount_total, v_total_cur;
|
|
737
|
+
end if;
|
|
738
|
+
|
|
739
|
+
-- --------------------------------------------------------------- posting
|
|
740
|
+
v_entry := post_entry(v_entry.id);
|
|
741
|
+
|
|
742
|
+
update documents
|
|
743
|
+
set state = 'posted',
|
|
744
|
+
number = coalesce(number, v_entry.number),
|
|
745
|
+
entry_id = v_entry.id,
|
|
746
|
+
accounting_date = v_date,
|
|
747
|
+
tax_point_date = v_tax_point
|
|
748
|
+
where id = p_document_id;
|
|
749
|
+
|
|
750
|
+
return v_entry;
|
|
751
|
+
end;
|
|
752
|
+
$$;
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
-- ---------------------------------------------------------------------------
|
|
756
|
+
-- The transfer that makes a cash-basis tax due says so on its lines
|
|
757
|
+
-- ---------------------------------------------------------------------------
|
|
758
|
+
--
|
|
759
|
+
-- Replaced whole for the same reason. Both legs carry the collection date:
|
|
760
|
+
-- the one that lands on the account and the box it is declared in, and the one
|
|
761
|
+
-- that empties the transition account the amount waited on. They are one
|
|
762
|
+
-- posting falling due, so they carry one tax point. Everything else is
|
|
763
|
+
-- `20260916103000` unchanged.
|
|
764
|
+
|
|
765
|
+
create or replace function settle_cash_basis_tax(
|
|
766
|
+
p_document_id uuid,
|
|
767
|
+
p_date date
|
|
768
|
+
)
|
|
769
|
+
returns uuid
|
|
770
|
+
language plpgsql
|
|
771
|
+
as $$
|
|
772
|
+
declare
|
|
773
|
+
v_doc documents%rowtype;
|
|
774
|
+
v_company companies%rowtype;
|
|
775
|
+
v_entry entries%rowtype;
|
|
776
|
+
v_kind tax_document_kind;
|
|
777
|
+
v_total numeric;
|
|
778
|
+
v_paid numeric;
|
|
779
|
+
v_ratio numeric;
|
|
780
|
+
v_seq integer := 0;
|
|
781
|
+
v_final uuid;
|
|
782
|
+
v_box text;
|
|
783
|
+
v_target uuid;
|
|
784
|
+
v_due numeric;
|
|
785
|
+
v_done numeric;
|
|
786
|
+
v_delta numeric;
|
|
787
|
+
v_box_due numeric;
|
|
788
|
+
v_box_done numeric;
|
|
789
|
+
v_box_delta numeric;
|
|
790
|
+
-- The transfer is written in the ledger's currency on both sides, which is
|
|
791
|
+
-- the company's own: the entry it produces says so.
|
|
792
|
+
v_round money_rounding;
|
|
793
|
+
v_credit boolean;
|
|
794
|
+
v_type tax_posting_type;
|
|
795
|
+
w record;
|
|
796
|
+
begin
|
|
797
|
+
select * into v_doc from documents where id = p_document_id;
|
|
798
|
+
if not found or v_doc.entry_id is null then
|
|
799
|
+
return null;
|
|
800
|
+
end if;
|
|
801
|
+
|
|
802
|
+
-- Nothing is waiting on this document: the answer for every tax that falls
|
|
803
|
+
-- due when it is invoiced, which is most of them.
|
|
804
|
+
if not exists (
|
|
805
|
+
select 1
|
|
806
|
+
from entry_lines q join taxes t on t.id = q.tax_id
|
|
807
|
+
where q.entry_id = v_doc.entry_id
|
|
808
|
+
and t.cash_basis
|
|
809
|
+
and q.declaration_box is null
|
|
810
|
+
and q.box_amount is not null
|
|
811
|
+
) then
|
|
812
|
+
return null;
|
|
813
|
+
end if;
|
|
814
|
+
|
|
815
|
+
select * into v_company from companies where id = v_doc.company_id;
|
|
816
|
+
v_round := rounding_of(v_doc.company_id);
|
|
817
|
+
if v_company.miscellaneous_journal_id is null then
|
|
818
|
+
raise exception 'no_miscellaneous_journal: company % has no journal for the transfer of a cash-basis tax',
|
|
819
|
+
v_doc.company_id;
|
|
820
|
+
end if;
|
|
821
|
+
|
|
822
|
+
v_kind := case when v_doc.doc_type in ('sale_credit_note', 'purchase_credit_note')
|
|
823
|
+
then 'credit_note' else 'invoice' end::tax_document_kind;
|
|
824
|
+
|
|
825
|
+
-- What share of this document has been settled, read on its own third-party
|
|
826
|
+
-- lines and in the ledger's currency on both sides of the division.
|
|
827
|
+
select coalesce(sum(abs(tl.debit - tl.credit)), 0), coalesce(sum(tl.matched_amount), 0)
|
|
828
|
+
into v_total, v_paid
|
|
829
|
+
from entry_lines tl join accounts a on a.id = tl.account_id
|
|
830
|
+
where tl.entry_id = v_doc.entry_id
|
|
831
|
+
and a.reconcilable
|
|
832
|
+
and a.account_type in ('asset_receivable', 'liability_payable');
|
|
833
|
+
|
|
834
|
+
if v_total = 0 then
|
|
835
|
+
return null;
|
|
836
|
+
end if;
|
|
837
|
+
-- An overpayment settles the document, and no more: a tax is due on what
|
|
838
|
+
-- was invoiced.
|
|
839
|
+
v_ratio := least(1, greatest(0, v_paid / v_total));
|
|
840
|
+
|
|
841
|
+
for w in
|
|
842
|
+
select l2.id, l2.account_id, l2.tax_id, l2.tax_line, l2.box_amount,
|
|
843
|
+
l2.debit, l2.credit, l2.name, l2.sequence
|
|
844
|
+
from entry_lines l2 join taxes t on t.id = l2.tax_id
|
|
845
|
+
where l2.entry_id = v_doc.entry_id
|
|
846
|
+
and t.cash_basis
|
|
847
|
+
and l2.declaration_box is null
|
|
848
|
+
and l2.box_amount is not null
|
|
849
|
+
order by l2.sequence
|
|
850
|
+
loop
|
|
851
|
+
select tp.account_id, tp.declaration_box, tp.posting_type
|
|
852
|
+
into v_final, v_box, v_type
|
|
853
|
+
from tax_postings tp
|
|
854
|
+
where tp.tax_id = w.tax_id
|
|
855
|
+
and tp.document_kind = v_kind
|
|
856
|
+
and tp.posting_type = (case when w.tax_line then 'tax' else 'base' end)::tax_posting_type
|
|
857
|
+
limit 1;
|
|
858
|
+
|
|
859
|
+
-- The account the transfer of this line lands on, which is also how an
|
|
860
|
+
-- earlier transfer of the same line is recognised.
|
|
861
|
+
v_target := case when w.tax_line then v_final else w.account_id end;
|
|
862
|
+
if v_target is null then
|
|
863
|
+
raise exception 'no_cash_basis_target: the tax of line % names no account to fall due on', w.id;
|
|
864
|
+
end if;
|
|
865
|
+
-- A line only waits because a box was worked out for it, and that box came
|
|
866
|
+
-- from this very posting. If it has none, the amount would wait for ever.
|
|
867
|
+
if v_box is null then
|
|
868
|
+
raise exception 'no_cash_basis_box: the tax of line % holds an amount for a box the posting does not name', w.id;
|
|
869
|
+
end if;
|
|
870
|
+
|
|
871
|
+
if w.tax_line then
|
|
872
|
+
v_due := round_amount((w.debit + w.credit) * v_ratio, v_round);
|
|
873
|
+
select coalesce(sum(case when w.credit > 0 then x.credit - x.debit
|
|
874
|
+
else x.debit - x.credit end), 0)
|
|
875
|
+
into v_done
|
|
876
|
+
from entry_lines x join entries e on e.id = x.entry_id
|
|
877
|
+
where e.document_id = p_document_id
|
|
878
|
+
and e.id <> v_doc.entry_id
|
|
879
|
+
and x.tax_id = w.tax_id
|
|
880
|
+
and x.tax_line
|
|
881
|
+
and x.account_id = v_target;
|
|
882
|
+
else
|
|
883
|
+
v_due := 0;
|
|
884
|
+
v_done := 0;
|
|
885
|
+
end if;
|
|
886
|
+
v_delta := v_due - v_done;
|
|
887
|
+
|
|
888
|
+
v_box_due := round_amount(w.box_amount * v_ratio, v_round);
|
|
889
|
+
select coalesce(sum(x.box_amount), 0)
|
|
890
|
+
into v_box_done
|
|
891
|
+
from entry_lines x join entries e on e.id = x.entry_id
|
|
892
|
+
where e.document_id = p_document_id
|
|
893
|
+
and e.id <> v_doc.entry_id
|
|
894
|
+
and x.tax_id = w.tax_id
|
|
895
|
+
and x.tax_line = w.tax_line
|
|
896
|
+
and x.account_id = v_target;
|
|
897
|
+
v_box_delta := v_box_due - v_box_done;
|
|
898
|
+
|
|
899
|
+
if v_delta = 0 and v_box_delta = 0 then
|
|
900
|
+
continue;
|
|
901
|
+
end if;
|
|
902
|
+
|
|
903
|
+
if v_entry.id is null then
|
|
904
|
+
insert into entries (company_id, journal_id, fiscal_year_id, entry_date, reference,
|
|
905
|
+
description, state, document_id, currency_code)
|
|
906
|
+
values (v_doc.company_id, v_company.miscellaneous_journal_id,
|
|
907
|
+
fiscal_year_at(v_doc.company_id, p_date), p_date,
|
|
908
|
+
v_doc.number,
|
|
909
|
+
coalesce(v_doc.number, 'document') || ' — tax due on settlement',
|
|
910
|
+
'draft', p_document_id, v_company.currency_code)
|
|
911
|
+
returning * into v_entry;
|
|
912
|
+
end if;
|
|
913
|
+
|
|
914
|
+
-- The side the document put the tax on, kept when the share grows and
|
|
915
|
+
-- flipped when it shrinks.
|
|
916
|
+
v_credit := (w.credit > 0) = (v_delta > 0);
|
|
917
|
+
|
|
918
|
+
v_seq := v_seq + 10;
|
|
919
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name,
|
|
920
|
+
debit, credit, tax_id, tax_line, posting_type,
|
|
921
|
+
declaration_box, box_amount, tax_point_date, currency_code)
|
|
922
|
+
values (v_entry.id, v_doc.company_id, v_target, v_seq, w.name,
|
|
923
|
+
case when v_credit then 0 else abs(v_delta) end,
|
|
924
|
+
case when v_credit then abs(v_delta) else 0 end,
|
|
925
|
+
w.tax_id, w.tax_line, v_type,
|
|
926
|
+
v_box, v_box_delta,
|
|
927
|
+
-- A tax that waits falls due on the day it is collected, and that
|
|
928
|
+
-- is the date this transfer carries. Saying it on the line rather
|
|
929
|
+
-- than leaving the entry's date to speak for it is what lets the
|
|
930
|
+
-- declaration read one column for every tax point there is.
|
|
931
|
+
p_date,
|
|
932
|
+
v_company.currency_code);
|
|
933
|
+
|
|
934
|
+
if v_delta <> 0 then
|
|
935
|
+
v_seq := v_seq + 10;
|
|
936
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name,
|
|
937
|
+
debit, credit, tax_id, tax_line, posting_type,
|
|
938
|
+
tax_point_date, currency_code)
|
|
939
|
+
values (v_entry.id, v_doc.company_id, w.account_id, v_seq, w.name,
|
|
940
|
+
case when v_credit then abs(v_delta) else 0 end,
|
|
941
|
+
case when v_credit then 0 else abs(v_delta) end,
|
|
942
|
+
w.tax_id, true, v_type, p_date, v_company.currency_code);
|
|
943
|
+
end if;
|
|
944
|
+
end loop;
|
|
945
|
+
|
|
946
|
+
if v_entry.id is null then
|
|
947
|
+
return null;
|
|
948
|
+
end if;
|
|
949
|
+
|
|
950
|
+
v_entry := post_entry(v_entry.id);
|
|
951
|
+
return v_entry.id;
|
|
952
|
+
end;
|
|
953
|
+
$$;
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
-- ---------------------------------------------------------------------------
|
|
957
|
+
-- The declaration reads the day the tax fell due
|
|
958
|
+
-- ---------------------------------------------------------------------------
|
|
959
|
+
--
|
|
960
|
+
-- Replaced whole for the same reason. One filter moves: a figure belongs to
|
|
961
|
+
-- the period its tax fell due in, and the entry's date answers only where the
|
|
962
|
+
-- line has nothing to say. Everything else is `20260916094500` unchanged,
|
|
963
|
+
-- cadence guard included.
|
|
964
|
+
--
|
|
965
|
+
-- `ec_sales_list()` is deliberately **not** changed with it. The recapitulative
|
|
966
|
+
-- statement declares an intra-Community supply, and the moment that arises is
|
|
967
|
+
-- its own article in both countries read here — KMS § 11 lg 2 in Estonia, art.
|
|
968
|
+
-- 17, § 2 in Belgium — neither of which is the rule `tax_point_rule` carries.
|
|
969
|
+
-- Moving the statement onto a date computed from the general rule would make
|
|
970
|
+
-- it wrong in a new way. `docs/international.md` records it as a gap.
|
|
971
|
+
|
|
972
|
+
create or replace function vat_return(
|
|
973
|
+
p_company_id uuid,
|
|
974
|
+
p_from date,
|
|
975
|
+
p_to date,
|
|
976
|
+
p_report_code text default null
|
|
977
|
+
)
|
|
978
|
+
returns table (
|
|
979
|
+
box text,
|
|
980
|
+
kind text,
|
|
981
|
+
amount numeric,
|
|
982
|
+
computed boolean,
|
|
983
|
+
name text,
|
|
984
|
+
sequence integer,
|
|
985
|
+
print_sequence integer,
|
|
986
|
+
hidden boolean,
|
|
987
|
+
report_code text
|
|
988
|
+
)
|
|
989
|
+
language plpgsql
|
|
990
|
+
stable
|
|
991
|
+
as $$
|
|
992
|
+
declare
|
|
993
|
+
-- 'box|kind' -> amount, for every box summed from the ledger, then the
|
|
994
|
+
-- totals `evaluate_totals()` derives from them. The key carries the kind
|
|
995
|
+
-- because the French CA3 puts a base and a tax on line 08 and a formula has
|
|
996
|
+
-- to be able to name one of them.
|
|
997
|
+
v_values jsonb := '{}'::jsonb;
|
|
998
|
+
v_formulas jsonb := '[]'::jsonb;
|
|
999
|
+
v_totals jsonb := '{}'::jsonb;
|
|
1000
|
+
v_rows jsonb := '[]'::jsonb;
|
|
1001
|
+
v_country char(2);
|
|
1002
|
+
v_report text;
|
|
1003
|
+
v_in char(2);
|
|
1004
|
+
v_count integer;
|
|
1005
|
+
v_codes text;
|
|
1006
|
+
-- How often this company files, what the form accepts, and what the two
|
|
1007
|
+
-- dates asked for actually are.
|
|
1008
|
+
v_files declaration_period;
|
|
1009
|
+
v_accepts declaration_period[];
|
|
1010
|
+
v_asked declaration_period;
|
|
1011
|
+
-- A declaration figure is not a ledger figure, but it is written in the
|
|
1012
|
+
-- same currency and with the same decimals.
|
|
1013
|
+
v_round money_rounding;
|
|
1014
|
+
r record;
|
|
1015
|
+
begin
|
|
1016
|
+
select c.fiscal_country into v_country
|
|
1017
|
+
from companies c where c.id = p_company_id;
|
|
1018
|
+
if not found then
|
|
1019
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
1020
|
+
end if;
|
|
1021
|
+
v_round := rounding_of(p_company_id);
|
|
1022
|
+
|
|
1023
|
+
-- Which form. The caller names one, or the country files exactly one on
|
|
1024
|
+
-- that date. Two and no name is a question only the caller can answer — a
|
|
1025
|
+
-- Canadian company files the federal return and the Québec one at once — so
|
|
1026
|
+
-- this asks instead of guessing.
|
|
1027
|
+
if p_report_code is not null then
|
|
1028
|
+
select t.country, t.code into v_in, v_report
|
|
1029
|
+
from tax_report_templates t
|
|
1030
|
+
where t.code = p_report_code
|
|
1031
|
+
and t.valid_from <= p_to
|
|
1032
|
+
and (t.valid_to is null or t.valid_to >= p_to)
|
|
1033
|
+
order by t.country
|
|
1034
|
+
limit 1;
|
|
1035
|
+
if v_report is null then
|
|
1036
|
+
raise exception 'unknown_tax_report: % is not a declaration form in force on %',
|
|
1037
|
+
p_report_code, p_to;
|
|
1038
|
+
end if;
|
|
1039
|
+
else
|
|
1040
|
+
select count(*), min(t.code), string_agg(t.code, ', ' order by t.code)
|
|
1041
|
+
into v_count, v_report, v_codes
|
|
1042
|
+
from tax_report_templates t
|
|
1043
|
+
where t.country = v_country
|
|
1044
|
+
and t.is_periodic_return
|
|
1045
|
+
and t.valid_from <= p_to
|
|
1046
|
+
and (t.valid_to is null or t.valid_to >= p_to);
|
|
1047
|
+
if v_count > 1 then
|
|
1048
|
+
raise exception 'ambiguous_tax_report: % files several declarations on % (%); name one',
|
|
1049
|
+
v_country, p_to, v_codes;
|
|
1050
|
+
end if;
|
|
1051
|
+
v_in := v_country;
|
|
1052
|
+
if v_count = 0 then
|
|
1053
|
+
v_report := null; -- no pack for this country: the ledger boxes, and no total.
|
|
1054
|
+
end if;
|
|
1055
|
+
end if;
|
|
1056
|
+
|
|
1057
|
+
-- The period asked for against the one this company files **this form** on.
|
|
1058
|
+
-- Four things have to hold before this refuses, and the fourth is what keeps
|
|
1059
|
+
-- it out of everybody's way: the company has recorded a cadence for this
|
|
1060
|
+
-- declaration, the form is filed on that cadence, the dates are themselves a
|
|
1061
|
+
-- whole cadence of that form, and the two are not the same. A fortnight, a
|
|
1062
|
+
-- half-year, a form the company has recorded nothing about — none of those is
|
|
1063
|
+
-- a filing on the wrong cadence, and none of them is refused.
|
|
1064
|
+
if v_report is not null then
|
|
1065
|
+
v_files := filing_period(p_company_id, v_report);
|
|
1066
|
+
if v_files is not null then
|
|
1067
|
+
select t.periods into v_accepts
|
|
1068
|
+
from tax_report_templates t
|
|
1069
|
+
where t.country = v_in and t.code = v_report;
|
|
1070
|
+
v_asked := declaration_period_of(p_from, p_to);
|
|
1071
|
+
if v_asked is not null
|
|
1072
|
+
and v_files = any(v_accepts)
|
|
1073
|
+
and v_asked = any(v_accepts)
|
|
1074
|
+
and v_asked <> v_files then
|
|
1075
|
+
raise exception
|
|
1076
|
+
'wrong_declaration_period: this company files % returns on %; % to % is a %',
|
|
1077
|
+
v_files, v_report, p_from, p_to, v_asked;
|
|
1078
|
+
end if;
|
|
1079
|
+
end if;
|
|
1080
|
+
end if;
|
|
1081
|
+
|
|
1082
|
+
-- 1. What the tax postings wrote on the ledger, in every box each of them
|
|
1083
|
+
-- names. A line carries the box it is known by; the posting behind it
|
|
1084
|
+
-- carries the whole list, which is one box for all but the handful of
|
|
1085
|
+
-- forms that print a figure twice. No country rule here either: the
|
|
1086
|
+
-- expansion is `unnest`, and which boxes there are is the pack's answer.
|
|
1087
|
+
for r in
|
|
1088
|
+
with lines as (
|
|
1089
|
+
select l.declaration_box as lbox,
|
|
1090
|
+
case when l.tax_line then 'tax' else 'base' end as lkind,
|
|
1091
|
+
l.tax_id as ltax,
|
|
1092
|
+
l.box_amount as lamount
|
|
1093
|
+
from entry_lines l
|
|
1094
|
+
join entries e on e.id = l.entry_id
|
|
1095
|
+
where l.company_id = p_company_id
|
|
1096
|
+
and e.state = 'posted'
|
|
1097
|
+
-- The period a figure belongs to is the day its tax fell due, and
|
|
1098
|
+
-- that is the line's own `tax_point_date` where one was worked out.
|
|
1099
|
+
-- Null is every line written before the column existed and every line
|
|
1100
|
+
-- of a country that declares no rule: the entry's date, exactly as
|
|
1101
|
+
-- before.
|
|
1102
|
+
and coalesce(l.tax_point_date, e.entry_date) between p_from and p_to
|
|
1103
|
+
and l.declaration_box is not null
|
|
1104
|
+
),
|
|
1105
|
+
-- The posting that wrote the line: which form it is on, and every box it
|
|
1106
|
+
-- prints in. A line with no posting behind it — an entry keyed by hand,
|
|
1107
|
+
-- a tax a company wrote itself — is on this form and in the one box it
|
|
1108
|
+
-- names, which is what the left join leaves.
|
|
1109
|
+
sourced as (
|
|
1110
|
+
select ln.lbox, ln.lkind, ln.lamount,
|
|
1111
|
+
coalesce(p.boxes, array[ln.lbox]) as lboxes
|
|
1112
|
+
from lines ln
|
|
1113
|
+
left join lateral (
|
|
1114
|
+
select min(tp.report_code) as report_code,
|
|
1115
|
+
array_agg(distinct b.box) as boxes
|
|
1116
|
+
from tax_postings tp
|
|
1117
|
+
cross join lateral unnest(tp.declaration_boxes) as b(box)
|
|
1118
|
+
where tp.tax_id = ln.ltax
|
|
1119
|
+
and tp.declaration_box = ln.lbox
|
|
1120
|
+
and tp.posting_type = ln.lkind::tax_posting_type
|
|
1121
|
+
) p on true
|
|
1122
|
+
-- A box number belongs to one form. A line whose posting names
|
|
1123
|
+
-- another form is not on this declaration; one that names none is
|
|
1124
|
+
-- the single-return case every European company is in.
|
|
1125
|
+
where v_report is null or coalesce(p.report_code, v_report) = v_report
|
|
1126
|
+
),
|
|
1127
|
+
ledger as (
|
|
1128
|
+
select x.box as lbox, s.lkind,
|
|
1129
|
+
round_amount(sum(s.lamount), v_round) as lamount
|
|
1130
|
+
from sourced s
|
|
1131
|
+
cross join lateral unnest(s.lboxes) as x(box)
|
|
1132
|
+
group by 1, 2
|
|
1133
|
+
having round_amount(sum(s.lamount), v_round) <> 0
|
|
1134
|
+
)
|
|
1135
|
+
select g.lbox, g.lkind, g.lamount, b.name as lname,
|
|
1136
|
+
b.sequence as lsequence,
|
|
1137
|
+
coalesce(b.print_sequence, b.sequence) as lprint,
|
|
1138
|
+
coalesce(b.hidden, false) as lhidden
|
|
1139
|
+
from ledger g
|
|
1140
|
+
left join tax_report_box_templates b
|
|
1141
|
+
on b.country = v_in and b.report_code = v_report
|
|
1142
|
+
and b.box = g.lbox and b.kind = g.lkind
|
|
1143
|
+
order by coalesce(b.sequence, 2147483647), g.lbox, g.lkind
|
|
1144
|
+
loop
|
|
1145
|
+
v_values := v_values || jsonb_build_object(r.lbox || '|' || r.lkind, r.lamount);
|
|
1146
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
1147
|
+
'box', r.lbox, 'kind', r.lkind, 'amount', r.lamount, 'computed', false,
|
|
1148
|
+
'name', r.lname, 'sequence', r.lsequence, 'print_sequence', r.lprint,
|
|
1149
|
+
'hidden', r.lhidden, 'report_code', v_report));
|
|
1150
|
+
end loop;
|
|
1151
|
+
|
|
1152
|
+
-- 2. The totals of the form, through the evaluator the statements use. A
|
|
1153
|
+
-- return prints what it has, so a nil total is left out of the answer —
|
|
1154
|
+
-- and kept in the working set, so a later total that names it reads a
|
|
1155
|
+
-- zero rather than a gap.
|
|
1156
|
+
if v_report is not null then
|
|
1157
|
+
select coalesce(jsonb_agg(jsonb_build_object(
|
|
1158
|
+
'key', b.box || '|total', 'plus', to_jsonb(b.plus_boxes),
|
|
1159
|
+
'minus', to_jsonb(b.minus_boxes), 'floor_zero', b.floor_zero,
|
|
1160
|
+
'rate', b.rate, 'rate_of', b.rate_of_box,
|
|
1161
|
+
'sequence', b.sequence
|
|
1162
|
+
) order by b.sequence, b.box), '[]'::jsonb)
|
|
1163
|
+
into v_formulas
|
|
1164
|
+
from tax_report_box_templates b
|
|
1165
|
+
where b.country = v_in
|
|
1166
|
+
and b.report_code = v_report
|
|
1167
|
+
and b.kind = 'total'
|
|
1168
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
1169
|
+
and (b.valid_to is null or b.valid_to >= p_to);
|
|
1170
|
+
|
|
1171
|
+
v_totals := evaluate_totals(v_values, v_formulas, v_round, false);
|
|
1172
|
+
|
|
1173
|
+
for r in
|
|
1174
|
+
select b.box as tbox, b.name as tname, b.sequence as tsequence,
|
|
1175
|
+
coalesce(b.print_sequence, b.sequence) as tprint, b.hidden as thidden
|
|
1176
|
+
from tax_report_box_templates b
|
|
1177
|
+
where b.country = v_in
|
|
1178
|
+
and b.report_code = v_report
|
|
1179
|
+
and b.kind = 'total'
|
|
1180
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
1181
|
+
and (b.valid_to is null or b.valid_to >= p_to)
|
|
1182
|
+
and v_totals ? (b.box || '|total')
|
|
1183
|
+
order by b.sequence, b.box
|
|
1184
|
+
loop
|
|
1185
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
1186
|
+
'box', r.tbox, 'kind', 'total',
|
|
1187
|
+
'amount', (v_totals ->> (r.tbox || '|total'))::numeric, 'computed', true,
|
|
1188
|
+
'name', r.tname, 'sequence', r.tsequence, 'print_sequence', r.tprint,
|
|
1189
|
+
'hidden', r.thidden, 'report_code', v_report));
|
|
1190
|
+
end loop;
|
|
1191
|
+
end if;
|
|
1192
|
+
|
|
1193
|
+
return query
|
|
1194
|
+
select (x ->> 'box')::text,
|
|
1195
|
+
(x ->> 'kind')::text,
|
|
1196
|
+
(x ->> 'amount')::numeric,
|
|
1197
|
+
(x ->> 'computed')::boolean,
|
|
1198
|
+
(x ->> 'name')::text,
|
|
1199
|
+
(x ->> 'sequence')::integer,
|
|
1200
|
+
(x ->> 'print_sequence')::integer,
|
|
1201
|
+
(x ->> 'hidden')::boolean,
|
|
1202
|
+
(x ->> 'report_code')::text
|
|
1203
|
+
from jsonb_array_elements(v_rows) as x;
|
|
1204
|
+
end;
|
|
1205
|
+
$$;
|
|
1206
|
+
|
|
1207
|
+
comment on function vat_return(uuid, date, date, text) is
|
|
1208
|
+
'Declaration boxes for a period: summed from the ledger by the day each figure''s tax fell due — `entry_lines.tax_point_date`, the entry''s own date where the line carries none — 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.';
|
|
1209
|
+
|
|
1210
|
+
revoke execute on function vat_return(uuid, date, date, text) from public, anon;
|
|
1211
|
+
grant execute on function vat_return(uuid, date, date, text) to authenticated, service_role;
|