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,488 @@
|
|
|
1
|
+
-- Ekwo OS — a declaration that was filed is a row, not a report.
|
|
2
|
+
--
|
|
3
|
+
-- `vat_return()` recomputes from the ledger every time it is asked. That is
|
|
4
|
+
-- right for preparing a return and wrong for having filed one: post an entry
|
|
5
|
+
-- into a period that was declared three months ago — a late supplier invoice,
|
|
6
|
+
-- a correction, an accountant's adjustment — and the same function now answers
|
|
7
|
+
-- different figures than the ones the administration holds. **Nothing in the
|
|
8
|
+
-- database says which were the ones sent.**
|
|
9
|
+
--
|
|
10
|
+
-- That is where a tax audit starts, and it is the first thing this file fixes:
|
|
11
|
+
-- filing freezes the figures, box by box, and the freeze is what the file sent
|
|
12
|
+
-- is built from and what a later disagreement is measured against.
|
|
13
|
+
--
|
|
14
|
+
-- Three shapes hold it:
|
|
15
|
+
--
|
|
16
|
+
-- tax_filings one row per declaration of a period, with its state
|
|
17
|
+
-- tax_filing_boxes the figures as filed, one row per box
|
|
18
|
+
-- filing_drift() what the ledger would say today against what was sent
|
|
19
|
+
--
|
|
20
|
+
-- The boxes are rows and not a JSON document on purpose. A box is already an
|
|
21
|
+
-- object of this schema — it has a name, a sequence, a legal reference — and a
|
|
22
|
+
-- filed return has to be queryable the way everything else is: summed,
|
|
23
|
+
-- compared period to period, joined to the form it belongs to. A JSON blob
|
|
24
|
+
-- would make the filed figures the only numbers in this database nobody can
|
|
25
|
+
-- ask a question about.
|
|
26
|
+
|
|
27
|
+
create type tax_filing_state as enum (
|
|
28
|
+
'draft', 'ready', 'filed', 'accepted', 'rejected', 'paid', 'superseded'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
comment on type tax_filing_state is
|
|
32
|
+
'Where a declaration is. draft while it is being prepared, ready when a person has approved the figures, filed once it has gone, accepted or rejected by the administration, paid when the money followed, superseded when a corrective replaced it.';
|
|
33
|
+
|
|
34
|
+
create table tax_filings (
|
|
35
|
+
id uuid primary key default gen_random_uuid(),
|
|
36
|
+
company_id uuid not null references companies(id) on delete cascade,
|
|
37
|
+
-- The form, as the pack names it. No foreign key, for the reason
|
|
38
|
+
-- `company_filing_periods` has none: `tax_report_templates` is keyed on
|
|
39
|
+
-- (country, code) and a company may file the form of a country that is not
|
|
40
|
+
-- its own. A trigger refuses a code no pack carries.
|
|
41
|
+
report_code text not null,
|
|
42
|
+
period_start date not null,
|
|
43
|
+
period_end date not null,
|
|
44
|
+
state tax_filing_state not null default 'draft',
|
|
45
|
+
due_date date,
|
|
46
|
+
-- What the administration gave back: a deposit number, a receipt, whatever
|
|
47
|
+
-- the portal calls it. Free text because every administration calls it
|
|
48
|
+
-- something else, and a closed vocabulary here would be an opinion about
|
|
49
|
+
-- portals rather than about accounting.
|
|
50
|
+
reference text,
|
|
51
|
+
-- When the figures were last computed into this filing. Distinct from
|
|
52
|
+
-- having figures at all: a period where nothing happened produces a
|
|
53
|
+
-- declaration with no lines, and a nil return is a return — most
|
|
54
|
+
-- administrations require it and fine its absence.
|
|
55
|
+
prepared_at timestamptz,
|
|
56
|
+
filed_at timestamptz,
|
|
57
|
+
filed_by uuid,
|
|
58
|
+
-- The filing this one replaces. A corrective never overwrites: what was sent
|
|
59
|
+
-- was sent.
|
|
60
|
+
supersedes_id uuid references tax_filings(id) on delete set null,
|
|
61
|
+
notes text,
|
|
62
|
+
created_at timestamptz not null default now(),
|
|
63
|
+
updated_at timestamptz not null default now(),
|
|
64
|
+
constraint tax_filings_period check (period_start <= period_end),
|
|
65
|
+
constraint tax_filings_filed_has_a_date check (
|
|
66
|
+
(state in ('draft', 'ready')) = (filed_at is null)
|
|
67
|
+
or state = 'superseded'
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
comment on table tax_filings is
|
|
72
|
+
'One row per declaration of a period: what was filed, when, under which reference, and in which state. The figures are in tax_filing_boxes and they are frozen — this table exists so that "what did we declare" is a question with an answer.';
|
|
73
|
+
|
|
74
|
+
comment on column tax_filings.report_code is
|
|
75
|
+
'The form, as the pack names it. A company may file the form of a country that is not its own, which is why this is a code and not a foreign key.';
|
|
76
|
+
comment on column tax_filings.supersedes_id is
|
|
77
|
+
'The filing this corrective replaces, which moves to superseded. Nothing is ever overwritten: a declaration that went out stays as it went out.';
|
|
78
|
+
comment on column tax_filings.prepared_at is
|
|
79
|
+
'When the figures were last computed into this filing. A declaration with no lines is not an unprepared one: a period where nothing happened is filed nil, and that is an obligation rather than an omission.';
|
|
80
|
+
comment on column tax_filings.reference is
|
|
81
|
+
'What the administration gave back — a deposit number, a receipt. Free text: every portal calls it something else.';
|
|
82
|
+
|
|
83
|
+
create unique index tax_filings_live_period_idx
|
|
84
|
+
on tax_filings (company_id, report_code, period_start, period_end)
|
|
85
|
+
where state <> 'superseded';
|
|
86
|
+
create index tax_filings_company_state_idx on tax_filings (company_id, state);
|
|
87
|
+
create index tax_filings_due_idx on tax_filings (company_id, due_date);
|
|
88
|
+
create index tax_filings_supersedes_idx on tax_filings (supersedes_id);
|
|
89
|
+
|
|
90
|
+
create trigger tax_filings_set_updated_at
|
|
91
|
+
before update on tax_filings
|
|
92
|
+
for each row execute function set_updated_at();
|
|
93
|
+
|
|
94
|
+
create table tax_filing_boxes (
|
|
95
|
+
filing_id uuid not null references tax_filings(id) on delete cascade,
|
|
96
|
+
box text not null,
|
|
97
|
+
amount numeric(16, 2) not null,
|
|
98
|
+
primary key (filing_id, box)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
comment on table tax_filing_boxes is
|
|
102
|
+
'The figures as they were filed, one row per box. Frozen at filing and never recomputed: this is what the administration holds.';
|
|
103
|
+
|
|
104
|
+
create index tax_filing_boxes_box_idx on tax_filing_boxes (box);
|
|
105
|
+
|
|
106
|
+
alter table tax_filings enable row level security;
|
|
107
|
+
alter table tax_filing_boxes enable row level security;
|
|
108
|
+
|
|
109
|
+
-- ---------------------------------------------------------------------------
|
|
110
|
+
-- Who may file
|
|
111
|
+
--
|
|
112
|
+
-- `entries.read` is not enough and `company.write` is too much. Filing is an
|
|
113
|
+
-- act with an outside consequence — an administration receives something in
|
|
114
|
+
-- the company's name — and it is exactly the act a company may want to keep
|
|
115
|
+
-- for one person while several people keep the books. It sits on the two
|
|
116
|
+
-- presets that already carry the other outward acts.
|
|
117
|
+
-- ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
insert into capabilities (code, area, description) values
|
|
120
|
+
('filings.read', 'filings', 'Read the declarations this company has prepared and filed, and their figures.'),
|
|
121
|
+
('filings.write', 'filings', 'Prepare a declaration, freeze its figures, record that it was filed, accepted, rejected or paid.')
|
|
122
|
+
on conflict (code) do nothing;
|
|
123
|
+
|
|
124
|
+
insert into role_capabilities (role, capability)
|
|
125
|
+
select r.role, c.code
|
|
126
|
+
from (values ('owner'::member_role), ('accountant'::member_role)) as r(role),
|
|
127
|
+
(values ('filings.read'), ('filings.write')) as c(code)
|
|
128
|
+
on conflict do nothing;
|
|
129
|
+
|
|
130
|
+
-- Reading is granted more widely than filing: whoever can read the ledger can
|
|
131
|
+
-- see what the company declared from it.
|
|
132
|
+
insert into role_capabilities (role, capability)
|
|
133
|
+
select r.role, 'filings.read'
|
|
134
|
+
from (select distinct role from role_capabilities where capability = 'entries.read') as r
|
|
135
|
+
on conflict do nothing;
|
|
136
|
+
|
|
137
|
+
create policy tax_filings_select on tax_filings
|
|
138
|
+
for select using (has_capability(company_id, 'filings.read'));
|
|
139
|
+
create policy tax_filings_write on tax_filings
|
|
140
|
+
for all using (has_capability(company_id, 'filings.write'))
|
|
141
|
+
with check (has_capability(company_id, 'filings.write'));
|
|
142
|
+
|
|
143
|
+
create policy tax_filing_boxes_select on tax_filing_boxes
|
|
144
|
+
for select using (exists (
|
|
145
|
+
select 1 from tax_filings f
|
|
146
|
+
where f.id = tax_filing_boxes.filing_id
|
|
147
|
+
and has_capability(f.company_id, 'filings.read')));
|
|
148
|
+
create policy tax_filing_boxes_write on tax_filing_boxes
|
|
149
|
+
for all using (exists (
|
|
150
|
+
select 1 from tax_filings f
|
|
151
|
+
where f.id = tax_filing_boxes.filing_id
|
|
152
|
+
and has_capability(f.company_id, 'filings.write')))
|
|
153
|
+
with check (exists (
|
|
154
|
+
select 1 from tax_filings f
|
|
155
|
+
where f.id = tax_filing_boxes.filing_id
|
|
156
|
+
and has_capability(f.company_id, 'filings.write')));
|
|
157
|
+
|
|
158
|
+
comment on policy tax_filings_select on tax_filings is
|
|
159
|
+
'filings.read: what a company declared is not the same secret as what it books, and a bookkeeper reads it without being able to file.';
|
|
160
|
+
comment on policy tax_filings_write on tax_filings is
|
|
161
|
+
'filings.write: preparing and filing is an act with an outside consequence, granted apart from keeping the books.';
|
|
162
|
+
|
|
163
|
+
grant select, insert, update, delete on table tax_filings to authenticated, service_role;
|
|
164
|
+
grant select, insert, update, delete on table tax_filing_boxes to authenticated, service_role;
|
|
165
|
+
|
|
166
|
+
create trigger tax_filings_audit
|
|
167
|
+
after insert or update or delete on tax_filings
|
|
168
|
+
for each row execute function audit_changes('{"company":"company_id","key":["report_code","period_start"]}');
|
|
169
|
+
|
|
170
|
+
-- A form nobody carries is a typo, and a typo caught at filing time is a
|
|
171
|
+
-- declaration filed under the wrong form. Same guard, and same reason, as the
|
|
172
|
+
-- one `company_filing_periods` carries on the same column.
|
|
173
|
+
create or replace function tax_filings_report_exists()
|
|
174
|
+
returns trigger
|
|
175
|
+
language plpgsql
|
|
176
|
+
as $$
|
|
177
|
+
begin
|
|
178
|
+
if not exists (select 1 from tax_report_templates t where t.code = new.report_code) then
|
|
179
|
+
raise exception 'unknown_report_code: no pack in this installation carries the form %',
|
|
180
|
+
new.report_code;
|
|
181
|
+
end if;
|
|
182
|
+
return new;
|
|
183
|
+
end;
|
|
184
|
+
$$;
|
|
185
|
+
|
|
186
|
+
revoke execute on function tax_filings_report_exists() from public, anon;
|
|
187
|
+
|
|
188
|
+
create trigger tax_filings_report_exists
|
|
189
|
+
before insert or update of report_code on tax_filings
|
|
190
|
+
for each row execute function tax_filings_report_exists();
|
|
191
|
+
|
|
192
|
+
-- A filed declaration's figures are the ones that were filed. Changing them
|
|
193
|
+
-- afterwards is the one thing this whole file exists to prevent, so it is
|
|
194
|
+
-- refused by the database rather than by a convention.
|
|
195
|
+
create or replace function tax_filing_boxes_are_frozen()
|
|
196
|
+
returns trigger
|
|
197
|
+
language plpgsql
|
|
198
|
+
as $$
|
|
199
|
+
declare
|
|
200
|
+
v_state tax_filing_state;
|
|
201
|
+
begin
|
|
202
|
+
select f.state into v_state
|
|
203
|
+
from tax_filings f
|
|
204
|
+
where f.id = coalesce(new.filing_id, old.filing_id);
|
|
205
|
+
|
|
206
|
+
if v_state not in ('draft', 'ready') then
|
|
207
|
+
raise exception 'filing_is_frozen: the figures of a % declaration are what was filed', v_state;
|
|
208
|
+
end if;
|
|
209
|
+
return coalesce(new, old);
|
|
210
|
+
end;
|
|
211
|
+
$$;
|
|
212
|
+
|
|
213
|
+
revoke execute on function tax_filing_boxes_are_frozen() from public, anon;
|
|
214
|
+
|
|
215
|
+
create trigger tax_filing_boxes_are_frozen
|
|
216
|
+
before insert or update or delete on tax_filing_boxes
|
|
217
|
+
for each row execute function tax_filing_boxes_are_frozen();
|
|
218
|
+
|
|
219
|
+
-- ---------------------------------------------------------------------------
|
|
220
|
+
-- prepare_filing
|
|
221
|
+
--
|
|
222
|
+
-- Reads the declaration the way anybody would, and keeps the answer. Called
|
|
223
|
+
-- again on a draft it refreshes: preparing is iterative, and the figures move
|
|
224
|
+
-- until somebody says they are ready.
|
|
225
|
+
-- ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
create or replace function prepare_filing(
|
|
228
|
+
p_company_id uuid,
|
|
229
|
+
p_from date,
|
|
230
|
+
p_to date,
|
|
231
|
+
p_report_code text default null
|
|
232
|
+
)
|
|
233
|
+
returns tax_filings
|
|
234
|
+
language plpgsql
|
|
235
|
+
volatile
|
|
236
|
+
security invoker
|
|
237
|
+
as $$
|
|
238
|
+
declare
|
|
239
|
+
v_code text;
|
|
240
|
+
v_filing tax_filings;
|
|
241
|
+
begin
|
|
242
|
+
v_code := coalesce(p_report_code, periodic_return_code(p_company_id));
|
|
243
|
+
if v_code is null then
|
|
244
|
+
raise exception 'no_report_code: the pack of this company names no periodic return, so say which form this is';
|
|
245
|
+
end if;
|
|
246
|
+
|
|
247
|
+
select * into v_filing
|
|
248
|
+
from tax_filings f
|
|
249
|
+
where f.company_id = p_company_id
|
|
250
|
+
and f.report_code = v_code
|
|
251
|
+
and f.period_start = p_from
|
|
252
|
+
and f.period_end = p_to
|
|
253
|
+
and f.state <> 'superseded';
|
|
254
|
+
|
|
255
|
+
if found and v_filing.state not in ('draft', 'ready') then
|
|
256
|
+
raise exception 'filing_already_% : % for % to % has gone; a change to it is a corrective',
|
|
257
|
+
v_filing.state, v_code, p_from, p_to;
|
|
258
|
+
end if;
|
|
259
|
+
|
|
260
|
+
if not found then
|
|
261
|
+
insert into tax_filings (company_id, report_code, period_start, period_end)
|
|
262
|
+
values (p_company_id, v_code, p_from, p_to)
|
|
263
|
+
returning * into v_filing;
|
|
264
|
+
else
|
|
265
|
+
update tax_filings set state = 'draft' where id = v_filing.id returning * into v_filing;
|
|
266
|
+
delete from tax_filing_boxes where filing_id = v_filing.id;
|
|
267
|
+
end if;
|
|
268
|
+
|
|
269
|
+
insert into tax_filing_boxes (filing_id, box, amount)
|
|
270
|
+
select v_filing.id, r.box, r.amount
|
|
271
|
+
from vat_return(p_company_id, p_from, p_to, v_code) r
|
|
272
|
+
where not r.hidden;
|
|
273
|
+
|
|
274
|
+
update tax_filings set prepared_at = now() where id = v_filing.id
|
|
275
|
+
returning * into v_filing;
|
|
276
|
+
|
|
277
|
+
return v_filing;
|
|
278
|
+
end;
|
|
279
|
+
$$;
|
|
280
|
+
|
|
281
|
+
comment on function prepare_filing(uuid, date, date, text) is
|
|
282
|
+
'Computes the declaration and keeps the answer, box by box. Called again on a draft it refreshes; on a declaration that has gone it refuses and says the word for what is needed instead — a corrective.';
|
|
283
|
+
|
|
284
|
+
revoke execute on function prepare_filing(uuid, date, date, text) from public, anon;
|
|
285
|
+
grant execute on function prepare_filing(uuid, date, date, text) to authenticated, service_role;
|
|
286
|
+
|
|
287
|
+
-- ---------------------------------------------------------------------------
|
|
288
|
+
-- The transitions
|
|
289
|
+
--
|
|
290
|
+
-- Functions and not an `update`, for the reason posting is a function: what
|
|
291
|
+
-- changes is a state, and the row is one the member may already write. Each
|
|
292
|
+
-- refusal says which state the declaration is in, because "not allowed" on a
|
|
293
|
+
-- return somebody is trying to file is the least useful sentence a database
|
|
294
|
+
-- can produce.
|
|
295
|
+
-- ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
create or replace function file_filing(
|
|
298
|
+
p_filing_id uuid,
|
|
299
|
+
p_reference text default null,
|
|
300
|
+
p_filed_at timestamptz default null
|
|
301
|
+
)
|
|
302
|
+
returns tax_filings
|
|
303
|
+
language plpgsql
|
|
304
|
+
volatile
|
|
305
|
+
security invoker
|
|
306
|
+
as $$
|
|
307
|
+
declare
|
|
308
|
+
v_filing tax_filings;
|
|
309
|
+
begin
|
|
310
|
+
select * into v_filing from tax_filings f where f.id = p_filing_id;
|
|
311
|
+
if not found then
|
|
312
|
+
raise exception 'not_found: filing %', p_filing_id using errcode = 'no_data_found';
|
|
313
|
+
end if;
|
|
314
|
+
if v_filing.state not in ('draft', 'ready') then
|
|
315
|
+
raise exception 'filing_already_%: this declaration has already gone', v_filing.state;
|
|
316
|
+
end if;
|
|
317
|
+
|
|
318
|
+
if v_filing.prepared_at is null then
|
|
319
|
+
raise exception 'filing_not_prepared: compute the figures before filing them';
|
|
320
|
+
end if;
|
|
321
|
+
|
|
322
|
+
update tax_filings
|
|
323
|
+
set state = 'filed',
|
|
324
|
+
reference = coalesce(p_reference, reference),
|
|
325
|
+
filed_at = coalesce(p_filed_at, now()),
|
|
326
|
+
filed_by = auth.uid()
|
|
327
|
+
where id = p_filing_id
|
|
328
|
+
returning * into v_filing;
|
|
329
|
+
return v_filing;
|
|
330
|
+
end;
|
|
331
|
+
$$;
|
|
332
|
+
|
|
333
|
+
comment on function file_filing(uuid, text, timestamptz) is
|
|
334
|
+
'Records that a declaration has gone, with the reference the administration gave back. It asks that the figures were computed, not that there are any: a period where nothing happened is filed nil. From here the figures are frozen by a trigger, and a change to them is a corrective.';
|
|
335
|
+
|
|
336
|
+
create or replace function record_filing_outcome(
|
|
337
|
+
p_filing_id uuid,
|
|
338
|
+
p_state tax_filing_state,
|
|
339
|
+
p_reference text default null
|
|
340
|
+
)
|
|
341
|
+
returns tax_filings
|
|
342
|
+
language plpgsql
|
|
343
|
+
volatile
|
|
344
|
+
security invoker
|
|
345
|
+
as $$
|
|
346
|
+
declare
|
|
347
|
+
v_filing tax_filings;
|
|
348
|
+
begin
|
|
349
|
+
select * into v_filing from tax_filings f where f.id = p_filing_id;
|
|
350
|
+
if not found then
|
|
351
|
+
raise exception 'not_found: filing %', p_filing_id using errcode = 'no_data_found';
|
|
352
|
+
end if;
|
|
353
|
+
|
|
354
|
+
if p_state not in ('accepted', 'rejected', 'paid') then
|
|
355
|
+
raise exception 'not_an_outcome: % is not something an administration answers', p_state;
|
|
356
|
+
end if;
|
|
357
|
+
if v_filing.state = 'draft' or v_filing.state = 'ready' then
|
|
358
|
+
raise exception 'filing_not_sent: a declaration that has not gone cannot come back %', p_state;
|
|
359
|
+
end if;
|
|
360
|
+
if p_state = 'paid' and v_filing.state <> 'accepted' then
|
|
361
|
+
raise exception 'filing_not_accepted: paying follows acceptance, and this one is %', v_filing.state;
|
|
362
|
+
end if;
|
|
363
|
+
|
|
364
|
+
update tax_filings
|
|
365
|
+
set state = p_state,
|
|
366
|
+
reference = coalesce(p_reference, reference)
|
|
367
|
+
where id = p_filing_id
|
|
368
|
+
returning * into v_filing;
|
|
369
|
+
return v_filing;
|
|
370
|
+
end;
|
|
371
|
+
$$;
|
|
372
|
+
|
|
373
|
+
comment on function record_filing_outcome(uuid, tax_filing_state, text) is
|
|
374
|
+
'What came back: accepted, rejected, or paid. Paying follows acceptance, and a declaration that never went cannot come back at all.';
|
|
375
|
+
|
|
376
|
+
create or replace function supersede_filing(p_filing_id uuid)
|
|
377
|
+
returns tax_filings
|
|
378
|
+
language plpgsql
|
|
379
|
+
volatile
|
|
380
|
+
security invoker
|
|
381
|
+
as $$
|
|
382
|
+
declare
|
|
383
|
+
v_old tax_filings;
|
|
384
|
+
v_new tax_filings;
|
|
385
|
+
begin
|
|
386
|
+
select * into v_old from tax_filings f where f.id = p_filing_id;
|
|
387
|
+
if not found then
|
|
388
|
+
raise exception 'not_found: filing %', p_filing_id using errcode = 'no_data_found';
|
|
389
|
+
end if;
|
|
390
|
+
if v_old.state in ('draft', 'ready') then
|
|
391
|
+
raise exception 'filing_not_sent: a declaration that has not gone is corrected by preparing it again';
|
|
392
|
+
end if;
|
|
393
|
+
if v_old.state = 'superseded' then
|
|
394
|
+
raise exception 'filing_already_superseded: % was already replaced', p_filing_id;
|
|
395
|
+
end if;
|
|
396
|
+
|
|
397
|
+
update tax_filings set state = 'superseded' where id = p_filing_id;
|
|
398
|
+
|
|
399
|
+
insert into tax_filings (company_id, report_code, period_start, period_end,
|
|
400
|
+
due_date, supersedes_id)
|
|
401
|
+
values (v_old.company_id, v_old.report_code, v_old.period_start, v_old.period_end,
|
|
402
|
+
v_old.due_date, v_old.id)
|
|
403
|
+
returning * into v_new;
|
|
404
|
+
|
|
405
|
+
insert into tax_filing_boxes (filing_id, box, amount)
|
|
406
|
+
select v_new.id, r.box, r.amount
|
|
407
|
+
from vat_return(v_old.company_id, v_old.period_start, v_old.period_end, v_old.report_code) r
|
|
408
|
+
where not r.hidden;
|
|
409
|
+
|
|
410
|
+
update tax_filings set prepared_at = now() where id = v_new.id returning * into v_new;
|
|
411
|
+
|
|
412
|
+
return v_new;
|
|
413
|
+
end;
|
|
414
|
+
$$;
|
|
415
|
+
|
|
416
|
+
comment on function supersede_filing(uuid) is
|
|
417
|
+
'Opens a corrective: the declaration that went becomes superseded and a new draft is prepared from today''s ledger, pointing at it. What was sent stays as it was sent.';
|
|
418
|
+
|
|
419
|
+
revoke execute on function file_filing(uuid, text, timestamptz) from public, anon;
|
|
420
|
+
grant execute on function file_filing(uuid, text, timestamptz) to authenticated, service_role;
|
|
421
|
+
revoke execute on function record_filing_outcome(uuid, tax_filing_state, text) from public, anon;
|
|
422
|
+
grant execute on function record_filing_outcome(uuid, tax_filing_state, text) to authenticated, service_role;
|
|
423
|
+
revoke execute on function supersede_filing(uuid) from public, anon;
|
|
424
|
+
grant execute on function supersede_filing(uuid) to authenticated, service_role;
|
|
425
|
+
|
|
426
|
+
-- ---------------------------------------------------------------------------
|
|
427
|
+
-- filing_drift
|
|
428
|
+
--
|
|
429
|
+
-- What the ledger would say today, against what was sent. It falls out of the
|
|
430
|
+
-- freeze for nothing, and it is the most useful health check this schema has:
|
|
431
|
+
-- a figure that moved after filing is either a correction somebody owes the
|
|
432
|
+
-- administration, or an entry that landed in the wrong period.
|
|
433
|
+
-- ---------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
create or replace function filing_drift(p_filing_id uuid)
|
|
436
|
+
returns table (
|
|
437
|
+
box text,
|
|
438
|
+
filed numeric,
|
|
439
|
+
ledger numeric,
|
|
440
|
+
difference numeric
|
|
441
|
+
)
|
|
442
|
+
language plpgsql
|
|
443
|
+
stable
|
|
444
|
+
security invoker
|
|
445
|
+
as $$
|
|
446
|
+
declare
|
|
447
|
+
v_filing tax_filings;
|
|
448
|
+
begin
|
|
449
|
+
select * into v_filing from tax_filings f where f.id = p_filing_id;
|
|
450
|
+
if not found then
|
|
451
|
+
raise exception 'not_found: filing %', p_filing_id using errcode = 'no_data_found';
|
|
452
|
+
end if;
|
|
453
|
+
|
|
454
|
+
return query
|
|
455
|
+
with today as (
|
|
456
|
+
select r.box, r.amount
|
|
457
|
+
from vat_return(v_filing.company_id, v_filing.period_start, v_filing.period_end,
|
|
458
|
+
v_filing.report_code) r
|
|
459
|
+
where not r.hidden
|
|
460
|
+
),
|
|
461
|
+
filed as (
|
|
462
|
+
select b.box, b.amount from tax_filing_boxes b where b.filing_id = p_filing_id
|
|
463
|
+
)
|
|
464
|
+
select coalesce(f.box, t.box),
|
|
465
|
+
coalesce(f.amount, 0),
|
|
466
|
+
coalesce(t.amount, 0),
|
|
467
|
+
coalesce(t.amount, 0) - coalesce(f.amount, 0)
|
|
468
|
+
from filed f
|
|
469
|
+
full outer join today t on t.box = f.box
|
|
470
|
+
where coalesce(t.amount, 0) <> coalesce(f.amount, 0)
|
|
471
|
+
order by 1;
|
|
472
|
+
end;
|
|
473
|
+
$$;
|
|
474
|
+
|
|
475
|
+
comment on function filing_drift(uuid) is
|
|
476
|
+
'Box by box, what the ledger says now against what was filed, for the boxes where the two disagree. Empty is the answer everybody wants; anything else is either a corrective to file or an entry in the wrong period.';
|
|
477
|
+
|
|
478
|
+
revoke execute on function filing_drift(uuid) from public, anon;
|
|
479
|
+
grant execute on function filing_drift(uuid) to authenticated, service_role;
|
|
480
|
+
|
|
481
|
+
-- The file that was sent and the receipt that came back are attachments, like
|
|
482
|
+
-- every other piece: one place where files live, one set of policies, one
|
|
483
|
+
-- storage path convention.
|
|
484
|
+
alter table attachments drop constraint attachments_entity_type;
|
|
485
|
+
alter table attachments add constraint attachments_entity_type check (
|
|
486
|
+
entity_type in ('company', 'contact', 'document', 'entry', 'payment',
|
|
487
|
+
'bank_statement', 'bank_transaction', 'fiscal_year', 'tax_filing')
|
|
488
|
+
);
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
-- Ekwo OS — when a declaration is due.
|
|
2
|
+
--
|
|
3
|
+
-- The schema knows how *often* a company files — `company_filing_periods`,
|
|
4
|
+
-- one cadence per form — and has never known **when**. A cadence without a
|
|
5
|
+
-- deadline answers "every quarter" to the question "what do I owe this week",
|
|
6
|
+
-- which is not an answer.
|
|
7
|
+
--
|
|
8
|
+
-- The date is not a fact about the ledger, so it is not in the core: it is a
|
|
9
|
+
-- rule of the country, and it goes where every other country rule goes — into
|
|
10
|
+
-- the pack, in a closed vocabulary, with the text that sets it.
|
|
11
|
+
--
|
|
12
|
+
-- day_of_month_after_period the twentieth of the month that follows
|
|
13
|
+
-- last_day_of_month_after_period the last day of that month
|
|
14
|
+
--
|
|
15
|
+
-- Either may add `plus_days`, which exists because the United Kingdom grants
|
|
16
|
+
-- seven of them to a return filed online while the regulation still says the
|
|
17
|
+
-- last day of the month.
|
|
18
|
+
--
|
|
19
|
+
-- **What the vocabulary deliberately cannot say** is a schedule that depends on
|
|
20
|
+
-- *who* is filing rather than on *what period*: France staggers its dates by
|
|
21
|
+
-- the taxpayer's identification number and legal form, so `packs/fr/` declares
|
|
22
|
+
-- no deadline at all and `filing_deadline()` answers null for it. A null that
|
|
23
|
+
-- means "this pack does not know" is worth more than a date that is wrong for
|
|
24
|
+
-- most filers, and it is written up in docs/international.md.
|
|
25
|
+
|
|
26
|
+
create type filing_deadline_rule as enum (
|
|
27
|
+
'day_of_month_after_period', 'last_day_of_month_after_period'
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
comment on type filing_deadline_rule is
|
|
31
|
+
'How a filing date is worked out from the end of a period. Two values, closed: a fixed day of the month that follows, or the last day of it. A third needs a migration, and the first country that needs one will say so in a gap rather than in a regular expression.';
|
|
32
|
+
|
|
33
|
+
alter table tax_report_templates
|
|
34
|
+
add column if not exists deadline_rule filing_deadline_rule,
|
|
35
|
+
add column if not exists deadline_day smallint,
|
|
36
|
+
add column if not exists deadline_plus_days smallint,
|
|
37
|
+
add column if not exists deadline_reference text,
|
|
38
|
+
add column if not exists deadline_source_key text;
|
|
39
|
+
|
|
40
|
+
comment on column tax_report_templates.deadline_rule is
|
|
41
|
+
'How the filing date follows the end of the period. Null where the pack says nothing, which is the honest answer for a country whose schedule depends on the taxpayer.';
|
|
42
|
+
comment on column tax_report_templates.deadline_day is
|
|
43
|
+
'Day of the month that follows the period. Filled exactly when the rule is day_of_month_after_period.';
|
|
44
|
+
comment on column tax_report_templates.deadline_plus_days is
|
|
45
|
+
'Days added to the date the rule produces — the seven the United Kingdom grants for filing online, while the regulation still says the last day of the month.';
|
|
46
|
+
comment on column tax_report_templates.deadline_reference is
|
|
47
|
+
'The text that sets the date, and the conditions on it: an extension that does not reach every filer is said here.';
|
|
48
|
+
comment on column tax_report_templates.deadline_source_key is
|
|
49
|
+
'Key of the register entry that reference is in.';
|
|
50
|
+
|
|
51
|
+
alter table tax_report_templates
|
|
52
|
+
add constraint tax_report_templates_deadline_shape check (
|
|
53
|
+
case deadline_rule
|
|
54
|
+
when 'day_of_month_after_period' then deadline_day between 1 and 31
|
|
55
|
+
when 'last_day_of_month_after_period' then deadline_day is null
|
|
56
|
+
else deadline_day is null and deadline_plus_days is null
|
|
57
|
+
and deadline_reference is null and deadline_source_key is null
|
|
58
|
+
end
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
-- ---------------------------------------------------------------------------
|
|
62
|
+
-- filing_deadline
|
|
63
|
+
--
|
|
64
|
+
-- The end of a period in, the day it is due out. Null where the pack says
|
|
65
|
+
-- nothing — a caller that wants a date has to face a country that does not
|
|
66
|
+
-- publish one as a rule.
|
|
67
|
+
-- ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
create or replace function filing_deadline(
|
|
70
|
+
p_company_id uuid,
|
|
71
|
+
p_report_code text,
|
|
72
|
+
p_period_end date
|
|
73
|
+
)
|
|
74
|
+
returns date
|
|
75
|
+
language sql
|
|
76
|
+
stable
|
|
77
|
+
security invoker
|
|
78
|
+
as $$
|
|
79
|
+
select case t.deadline_rule
|
|
80
|
+
when 'day_of_month_after_period' then
|
|
81
|
+
-- The day the pack names, in the month that follows. A day the
|
|
82
|
+
-- month does not have lands on its last: `make_date` would raise,
|
|
83
|
+
-- and the thirty-first of a thirty-day month is a date the
|
|
84
|
+
-- administration reads as the end of it.
|
|
85
|
+
least(
|
|
86
|
+
(date_trunc('month', p_period_end + interval '1 month')
|
|
87
|
+
+ make_interval(days => t.deadline_day - 1))::date,
|
|
88
|
+
(date_trunc('month', p_period_end + interval '2 month') - interval '1 day')::date
|
|
89
|
+
)
|
|
90
|
+
when 'last_day_of_month_after_period' then
|
|
91
|
+
(date_trunc('month', p_period_end + interval '2 month') - interval '1 day')::date
|
|
92
|
+
else null
|
|
93
|
+
end
|
|
94
|
+
+ coalesce(t.deadline_plus_days, 0)
|
|
95
|
+
from tax_report_templates t
|
|
96
|
+
join companies c on c.id = p_company_id
|
|
97
|
+
where t.code = p_report_code
|
|
98
|
+
and t.country in (c.country, c.fiscal_country)
|
|
99
|
+
and t.deadline_rule is not null
|
|
100
|
+
limit 1;
|
|
101
|
+
$$;
|
|
102
|
+
|
|
103
|
+
comment on function filing_deadline(uuid, text, date) is
|
|
104
|
+
'When a period closed on this date has to be declared, under the rule the pack carries. Null where the pack declares none — which is a country whose schedule depends on the filer, not a country without deadlines.';
|
|
105
|
+
|
|
106
|
+
revoke execute on function filing_deadline(uuid, text, date) from public, anon;
|
|
107
|
+
grant execute on function filing_deadline(uuid, text, date) to authenticated, service_role;
|
|
108
|
+
|
|
109
|
+
-- ---------------------------------------------------------------------------
|
|
110
|
+
-- upcoming_filings
|
|
111
|
+
--
|
|
112
|
+
-- What is due between two dates, for every declaration this company files,
|
|
113
|
+
-- with what has already been prepared or sent against it. The periods are
|
|
114
|
+
-- generated from the cadence the company recorded — the whole point of
|
|
115
|
+
-- `company_filing_periods` — so a company that files monthly and files a
|
|
116
|
+
-- recapitulative statement quarterly gets both, on their own rhythms.
|
|
117
|
+
-- ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
create or replace function upcoming_filings(
|
|
120
|
+
p_company_id uuid,
|
|
121
|
+
p_from date,
|
|
122
|
+
p_to date
|
|
123
|
+
)
|
|
124
|
+
returns table (
|
|
125
|
+
report_code text,
|
|
126
|
+
period_start date,
|
|
127
|
+
period_end date,
|
|
128
|
+
due_date date,
|
|
129
|
+
state tax_filing_state,
|
|
130
|
+
filing_id uuid
|
|
131
|
+
)
|
|
132
|
+
language sql
|
|
133
|
+
stable
|
|
134
|
+
security invoker
|
|
135
|
+
as $$
|
|
136
|
+
with forms as (
|
|
137
|
+
-- Every form this company has a cadence for, and the periodic return it
|
|
138
|
+
-- files even when nobody has written a cadence down for it.
|
|
139
|
+
select p.report_code, p.period::text as cadence
|
|
140
|
+
from company_filing_periods p
|
|
141
|
+
where p.company_id = p_company_id
|
|
142
|
+
union
|
|
143
|
+
select periodic_return_code(p_company_id),
|
|
144
|
+
coalesce(
|
|
145
|
+
(select p.period::text from company_filing_periods p
|
|
146
|
+
where p.company_id = p_company_id
|
|
147
|
+
and p.report_code = periodic_return_code(p_company_id)),
|
|
148
|
+
(select t.period_default::text from tax_report_templates t
|
|
149
|
+
join companies c on c.id = p_company_id
|
|
150
|
+
where t.code = periodic_return_code(p_company_id)
|
|
151
|
+
and t.country in (c.country, c.fiscal_country)))
|
|
152
|
+
where periodic_return_code(p_company_id) is not null
|
|
153
|
+
),
|
|
154
|
+
periods as (
|
|
155
|
+
select f.report_code,
|
|
156
|
+
g.start::date as period_start,
|
|
157
|
+
(g.start + case f.cadence
|
|
158
|
+
when 'month' then interval '1 month'
|
|
159
|
+
when 'quarter' then interval '3 month'
|
|
160
|
+
else interval '1 year'
|
|
161
|
+
end - interval '1 day')::date as period_end
|
|
162
|
+
from forms f
|
|
163
|
+
cross join lateral generate_series(
|
|
164
|
+
date_trunc(case f.cadence when 'month' then 'month'
|
|
165
|
+
when 'quarter' then 'quarter'
|
|
166
|
+
else 'year' end, p_from::timestamp),
|
|
167
|
+
p_to::timestamp,
|
|
168
|
+
case f.cadence when 'month' then interval '1 month'
|
|
169
|
+
when 'quarter' then interval '3 month'
|
|
170
|
+
else interval '1 year' end
|
|
171
|
+
) as g(start)
|
|
172
|
+
where f.cadence is not null
|
|
173
|
+
)
|
|
174
|
+
select p.report_code,
|
|
175
|
+
p.period_start,
|
|
176
|
+
p.period_end,
|
|
177
|
+
filing_deadline(p_company_id, p.report_code, p.period_end),
|
|
178
|
+
f.state,
|
|
179
|
+
f.id
|
|
180
|
+
from periods p
|
|
181
|
+
left join tax_filings f
|
|
182
|
+
on f.company_id = p_company_id
|
|
183
|
+
and f.report_code = p.report_code
|
|
184
|
+
and f.period_start = p.period_start
|
|
185
|
+
and f.period_end = p.period_end
|
|
186
|
+
and f.state <> 'superseded'
|
|
187
|
+
order by p.period_end, p.report_code;
|
|
188
|
+
$$;
|
|
189
|
+
|
|
190
|
+
comment on function upcoming_filings(uuid, date, date) is
|
|
191
|
+
'What this company has to file between two dates: the periods its cadences produce, the day each is due where the pack says, and the declaration already prepared or sent against it. Read-only; sending the reminder is somebody else''s job, because a reminder needs a channel and somebody to operate it.';
|
|
192
|
+
|
|
193
|
+
revoke execute on function upcoming_filings(uuid, date, date) from public, anon;
|
|
194
|
+
grant execute on function upcoming_filings(uuid, date, date) to authenticated, service_role;
|