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,85 @@
|
|
|
1
|
+
-- Ekwo OS — a smaller surface for the anonymous role and for strangers.
|
|
2
|
+
--
|
|
3
|
+
-- Two findings of the security audit of 11 September 2026, both about what
|
|
4
|
+
-- someone who is *not* a member of any company can reach.
|
|
5
|
+
--
|
|
6
|
+
-- 1. Every function in `public` was executable by `anon`, because Postgres
|
|
7
|
+
-- grants EXECUTE to PUBLIC on creation and Supabase exposes `public`
|
|
8
|
+
-- functions as RPC. Row level security made every call return nothing,
|
|
9
|
+
-- but nothing is not the same as no. `anon` keeps EXECUTE on the eight
|
|
10
|
+
-- helpers that row level security policies call — they answer about
|
|
11
|
+
-- `auth.uid()` only, and without them an anonymous SELECT would error
|
|
12
|
+
-- instead of returning an empty set — and loses it on everything else.
|
|
13
|
+
-- The default privileges are changed too, so a function added tomorrow
|
|
14
|
+
-- starts closed.
|
|
15
|
+
--
|
|
16
|
+
-- 2. `instance_admins` was readable by any signed-in user, member or not.
|
|
17
|
+
-- Supabase projects accept self sign-up by default, so "signed in" is a
|
|
18
|
+
-- weaker claim than it sounds. Administrators are now visible to members
|
|
19
|
+
-- of at least one company, to administrators, and to oneself.
|
|
20
|
+
--
|
|
21
|
+
-- Nothing already published is edited: revokes, grants and one policy.
|
|
22
|
+
|
|
23
|
+
-- 1. Functions ------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
revoke execute on all functions in schema public from public, anon;
|
|
26
|
+
grant execute on all functions in schema public to authenticated, service_role;
|
|
27
|
+
|
|
28
|
+
-- The helpers a policy may evaluate on behalf of an anonymous request.
|
|
29
|
+
grant execute on function company_role(uuid) to anon;
|
|
30
|
+
grant execute on function is_company_member(uuid) to anon;
|
|
31
|
+
grant execute on function can_write_company(uuid) to anon;
|
|
32
|
+
grant execute on function is_company_owner(uuid) to anon;
|
|
33
|
+
grant execute on function is_instance_admin() to anon;
|
|
34
|
+
grant execute on function is_any_company_member() to anon;
|
|
35
|
+
grant execute on function company_has_no_member(uuid) to anon;
|
|
36
|
+
grant execute on function instance_has_no_admin() to anon;
|
|
37
|
+
|
|
38
|
+
alter default privileges in schema public revoke execute on functions from public, anon;
|
|
39
|
+
alter default privileges in schema public grant execute on functions to authenticated, service_role;
|
|
40
|
+
|
|
41
|
+
-- 2. Who may see the administrators ---------------------------------------
|
|
42
|
+
|
|
43
|
+
drop policy if exists instance_admins_select on instance_admins;
|
|
44
|
+
create policy instance_admins_select on instance_admins
|
|
45
|
+
for select
|
|
46
|
+
using (user_id = auth.uid() or is_any_company_member() or is_instance_admin());
|
|
47
|
+
|
|
48
|
+
comment on policy instance_admins_select on instance_admins is
|
|
49
|
+
'Members, administrators, and oneself. A signed-in stranger sees no one.';
|
|
50
|
+
|
|
51
|
+
-- 3. The claim keeps its clear refusal ------------------------------------
|
|
52
|
+
--
|
|
53
|
+
-- `claim_instance_admin` used to learn whether the seat was taken by reading
|
|
54
|
+
-- `instance_admins` as the caller. A stranger can no longer read it, so the
|
|
55
|
+
-- same call would have fallen through to the policy and answered with a bare
|
|
56
|
+
-- row-level-security error. `instance_has_no_admin()` is the definer helper
|
|
57
|
+
-- made for exactly this question, and it answers it for everyone.
|
|
58
|
+
|
|
59
|
+
create or replace function claim_instance_admin(p_user_id uuid default auth.uid())
|
|
60
|
+
returns instance_admins
|
|
61
|
+
language plpgsql
|
|
62
|
+
as $$
|
|
63
|
+
declare
|
|
64
|
+
v_row instance_admins%rowtype;
|
|
65
|
+
begin
|
|
66
|
+
if p_user_id is null then
|
|
67
|
+
raise exception 'no_user: claim_instance_admin needs a signed-in user or an explicit id';
|
|
68
|
+
end if;
|
|
69
|
+
|
|
70
|
+
if not instance_has_no_admin() and not is_instance_admin() then
|
|
71
|
+
raise exception 'instance_already_claimed: only an instance administrator can appoint another';
|
|
72
|
+
end if;
|
|
73
|
+
|
|
74
|
+
insert into instance_admins (user_id)
|
|
75
|
+
values (p_user_id)
|
|
76
|
+
on conflict (user_id) do nothing
|
|
77
|
+
returning * into v_row;
|
|
78
|
+
|
|
79
|
+
if v_row.user_id is null then
|
|
80
|
+
select * into v_row from instance_admins where user_id = p_user_id;
|
|
81
|
+
end if;
|
|
82
|
+
|
|
83
|
+
return v_row;
|
|
84
|
+
end;
|
|
85
|
+
$$;
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
-- Ekwo OS — a country is a pack, and an installation knows which one it has.
|
|
2
|
+
--
|
|
3
|
+
-- `packs/<cc>/` became the source of a country in the previous change, and
|
|
4
|
+
-- the seeds are now compiled from it. What the schema still could not say is
|
|
5
|
+
-- *which version* of a pack an instance holds and which version a company
|
|
6
|
+
-- copied — so nothing could ever tell an operator that their chart of
|
|
7
|
+
-- accounts is a year behind the pack, and `ekwo pack upgrade` would have had
|
|
8
|
+
-- nothing to compare.
|
|
9
|
+
--
|
|
10
|
+
-- Two tables answer that, and nothing else changes hands:
|
|
11
|
+
--
|
|
12
|
+
-- `country_packs` one row per pack loaded here, written by the generated
|
|
13
|
+
-- seed: version, checksum, and the certification status
|
|
14
|
+
-- that `ekwo init` prints, so an operator knows whether a
|
|
15
|
+
-- professional has ever read the pack they installed.
|
|
16
|
+
-- `company_packs` what each company copied, and when. A company can hold
|
|
17
|
+
-- two packs — a foreign VAT registration is exactly that —
|
|
18
|
+
-- which is why the key is (company, country).
|
|
19
|
+
--
|
|
20
|
+
-- The rest is the translated labels the pack format already carries.
|
|
21
|
+
-- `account_templates.name_i18n` holds every language of the pack;
|
|
22
|
+
-- `install_country_template` copies the one the company asked for into
|
|
23
|
+
-- `name` and keeps the whole object beside it, so renaming a chart into
|
|
24
|
+
-- another language is a data change and never an import. Translations live in
|
|
25
|
+
-- `jsonb` rather than in a table, which is where Odoo landed in v16 for the
|
|
26
|
+
-- same reason: a table would cost a join per label on six tables.
|
|
27
|
+
--
|
|
28
|
+
-- Additive throughout: new tables, `add column if not exists`, and one
|
|
29
|
+
-- function replaced. `install_country_template` gains a third argument, so
|
|
30
|
+
-- the two-argument version is dropped first — an overload with a default
|
|
31
|
+
-- would make a two-argument `install_country_template` call ambiguous, and
|
|
32
|
+
-- Postgres would refuse the call that works today.
|
|
33
|
+
|
|
34
|
+
-- ---------------------------------------------------------------------------
|
|
35
|
+
-- country_packs — what this instance holds
|
|
36
|
+
-- ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
do $$
|
|
39
|
+
begin
|
|
40
|
+
if not exists (select 1 from pg_type where typname = 'pack_certification') then
|
|
41
|
+
create type pack_certification as enum ('community', 'reviewed', 'ekwo');
|
|
42
|
+
end if;
|
|
43
|
+
end;
|
|
44
|
+
$$;
|
|
45
|
+
|
|
46
|
+
comment on type pack_certification is
|
|
47
|
+
'How much a pack has been read: contributed, read by a named professional, or maintained by Ekwo.';
|
|
48
|
+
|
|
49
|
+
create table if not exists country_packs (
|
|
50
|
+
country char(2) primary key,
|
|
51
|
+
name text not null,
|
|
52
|
+
version text not null,
|
|
53
|
+
released_at date,
|
|
54
|
+
schema_min text,
|
|
55
|
+
certification_status pack_certification not null default 'community',
|
|
56
|
+
certified_by text,
|
|
57
|
+
certified_at date,
|
|
58
|
+
checksum text,
|
|
59
|
+
installed_at timestamptz not null default now(),
|
|
60
|
+
constraint country_packs_country_format check (country ~ '^[A-Z]{2}$'),
|
|
61
|
+
constraint country_packs_version_semver check (version ~ '^[0-9]+\.[0-9]+\.[0-9]+$')
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
comment on table country_packs is 'Country packs loaded in this installation, with their version and certification.';
|
|
65
|
+
comment on column country_packs.checksum is 'sha256 of the pack files, so a changed pack is visible without a diff.';
|
|
66
|
+
comment on column country_packs.certification_status is 'Printed by `ekwo init`: a community pack has not been read by an accountant.';
|
|
67
|
+
|
|
68
|
+
-- ---------------------------------------------------------------------------
|
|
69
|
+
-- company_packs — what each company copied
|
|
70
|
+
-- ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
create table if not exists company_packs (
|
|
73
|
+
company_id uuid not null references companies(id) on delete cascade,
|
|
74
|
+
country char(2) not null,
|
|
75
|
+
version text not null,
|
|
76
|
+
installed_at timestamptz not null default now(),
|
|
77
|
+
upgraded_at timestamptz,
|
|
78
|
+
primary key (company_id, country),
|
|
79
|
+
constraint company_packs_country_format check (country ~ '^[A-Z]{2}$'),
|
|
80
|
+
constraint company_packs_version_semver check (version ~ '^[0-9]+\.[0-9]+\.[0-9]+$')
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
comment on table company_packs is 'Which version of which country pack a company copied. A company may hold two: a foreign VAT registration is one.';
|
|
84
|
+
comment on column company_packs.upgraded_at is 'Last time `install_country_template` or `ekwo pack upgrade` moved this company to another version.';
|
|
85
|
+
|
|
86
|
+
create index if not exists company_packs_country_idx on company_packs (country);
|
|
87
|
+
|
|
88
|
+
-- ---------------------------------------------------------------------------
|
|
89
|
+
-- Translated labels, and the line an account reports on
|
|
90
|
+
-- ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
alter table account_templates
|
|
93
|
+
add column if not exists name_i18n jsonb not null default '{}'::jsonb,
|
|
94
|
+
add column if not exists statement_hint text;
|
|
95
|
+
|
|
96
|
+
alter table accounts
|
|
97
|
+
add column if not exists name_i18n jsonb not null default '{}'::jsonb,
|
|
98
|
+
add column if not exists statement_hint text;
|
|
99
|
+
|
|
100
|
+
comment on column account_templates.name_i18n is 'Label by language, from packs/<cc>/i18n/. The pack''s own language stays in `name`.';
|
|
101
|
+
comment on column accounts.name_i18n is 'Label by language, copied from the template at install. `name` holds the language the company chose.';
|
|
102
|
+
comment on column account_templates.statement_hint is 'Statement line this account falls under when no rule catches it. Read by financial_statement() (P0-4).';
|
|
103
|
+
comment on column accounts.statement_hint is 'Statement line this account falls under when no rule catches it. Read by financial_statement() (P0-4).';
|
|
104
|
+
|
|
105
|
+
alter table companies
|
|
106
|
+
add column if not exists language char(2) not null default 'fr';
|
|
107
|
+
|
|
108
|
+
comment on column companies.language is
|
|
109
|
+
'Language this company keeps its books in. Chosen at install; decides which label of name_i18n lands in accounts.name.';
|
|
110
|
+
|
|
111
|
+
alter table country_defaults
|
|
112
|
+
add column if not exists language_default char(2);
|
|
113
|
+
|
|
114
|
+
comment on column country_defaults.language_default is
|
|
115
|
+
'Language `ekwo init` offers for a company of this country, before the company row exists — like currency_code, and for the same reason.';
|
|
116
|
+
|
|
117
|
+
-- No backfill here. A pack fills this column, and the compiled seed of every
|
|
118
|
+
-- country upserts `country_defaults`, so re-applying the seeds is what gives
|
|
119
|
+
-- an existing installation the value. Naming a language for a country in a
|
|
120
|
+
-- migration would put a country back into the core.
|
|
121
|
+
|
|
122
|
+
-- ---------------------------------------------------------------------------
|
|
123
|
+
-- install_country_template, in a language, recording what it copied
|
|
124
|
+
-- ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
drop function if exists install_country_template(uuid, char);
|
|
127
|
+
|
|
128
|
+
create or replace function install_country_template(
|
|
129
|
+
p_company_id uuid,
|
|
130
|
+
p_country char(2),
|
|
131
|
+
p_language char(2) default null
|
|
132
|
+
)
|
|
133
|
+
returns void
|
|
134
|
+
language plpgsql
|
|
135
|
+
as $$
|
|
136
|
+
declare
|
|
137
|
+
v_defaults country_defaults%rowtype;
|
|
138
|
+
r record;
|
|
139
|
+
v_tax_id uuid;
|
|
140
|
+
v_language char(2);
|
|
141
|
+
v_version text;
|
|
142
|
+
begin
|
|
143
|
+
if not exists (select 1 from companies where id = p_company_id) then
|
|
144
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
145
|
+
end if;
|
|
146
|
+
if not exists (select 1 from account_templates where country = p_country) then
|
|
147
|
+
raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
|
|
148
|
+
end if;
|
|
149
|
+
|
|
150
|
+
-- The language of the copy: what the caller asked for, else what the
|
|
151
|
+
-- company keeps its books in. `companies.language` is never null, so there
|
|
152
|
+
-- is always an answer, and a label missing from a pack falls back to the
|
|
153
|
+
-- pack's own language rather than to nothing.
|
|
154
|
+
select coalesce(p_language, c.language) into v_language
|
|
155
|
+
from companies c where c.id = p_company_id;
|
|
156
|
+
|
|
157
|
+
-- 1. Accounts, without the hierarchy.
|
|
158
|
+
insert into accounts (company_id, code, name, name_i18n, statement_hint,
|
|
159
|
+
account_type, reconcilable)
|
|
160
|
+
select p_company_id, t.code,
|
|
161
|
+
coalesce(nullif(t.name_i18n ->> v_language, ''), t.name),
|
|
162
|
+
t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
|
|
163
|
+
from account_templates t
|
|
164
|
+
where t.country = p_country
|
|
165
|
+
on conflict (company_id, code) do nothing;
|
|
166
|
+
|
|
167
|
+
-- 2. Hierarchy, now that every code exists.
|
|
168
|
+
update accounts a
|
|
169
|
+
set parent_id = p.id
|
|
170
|
+
from account_templates t
|
|
171
|
+
join accounts p on p.company_id = p_company_id and p.code = t.parent_code
|
|
172
|
+
where a.company_id = p_company_id
|
|
173
|
+
and a.code = t.code
|
|
174
|
+
and t.country = p_country
|
|
175
|
+
and t.parent_code is not null
|
|
176
|
+
and a.parent_id is null;
|
|
177
|
+
|
|
178
|
+
-- 3. Journals.
|
|
179
|
+
insert into journals (company_id, code, name, journal_type)
|
|
180
|
+
select p_company_id, t.code, t.name, t.journal_type
|
|
181
|
+
from journal_templates t
|
|
182
|
+
where t.country = p_country
|
|
183
|
+
on conflict (company_id, code) do nothing;
|
|
184
|
+
|
|
185
|
+
-- 4. Taxes and their postings.
|
|
186
|
+
for r in
|
|
187
|
+
select * from tax_templates where country = p_country order by sequence, code
|
|
188
|
+
loop
|
|
189
|
+
v_tax_id := null;
|
|
190
|
+
insert into taxes (company_id, code, name, description, amount_type, amount,
|
|
191
|
+
applies_to, treatment, country, valid_from, valid_to,
|
|
192
|
+
legal_reference, vat_category, exemption_code, sequence)
|
|
193
|
+
values (p_company_id, r.code, r.name, r.description, r.amount_type, r.amount,
|
|
194
|
+
r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
|
|
195
|
+
r.legal_reference, r.vat_category, r.exemption_code, r.sequence)
|
|
196
|
+
on conflict (company_id, code) do nothing
|
|
197
|
+
returning id into v_tax_id;
|
|
198
|
+
|
|
199
|
+
if v_tax_id is null then
|
|
200
|
+
continue;
|
|
201
|
+
end if;
|
|
202
|
+
|
|
203
|
+
insert into tax_postings (tax_id, company_id, document_kind, posting_type,
|
|
204
|
+
factor_percent, account_id, declaration_box,
|
|
205
|
+
box_factor_percent, sequence)
|
|
206
|
+
select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
|
|
207
|
+
tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
|
|
208
|
+
tp.declaration_box, tp.box_factor_percent, tp.sequence
|
|
209
|
+
from tax_posting_templates tp
|
|
210
|
+
where tp.tax_template_id = r.id
|
|
211
|
+
order by tp.sequence;
|
|
212
|
+
end loop;
|
|
213
|
+
|
|
214
|
+
-- 5. Roles.
|
|
215
|
+
select * into v_defaults from country_defaults where country = p_country;
|
|
216
|
+
if found then
|
|
217
|
+
update companies c
|
|
218
|
+
set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
|
|
219
|
+
payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
|
|
220
|
+
suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
|
|
221
|
+
rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
|
|
222
|
+
retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
|
|
223
|
+
default_sales_account_id = coalesce(c.default_sales_account_id, account_id_by_code(p_company_id, v_defaults.sales_account_code)),
|
|
224
|
+
default_purchase_account_id = coalesce(c.default_purchase_account_id, account_id_by_code(p_company_id, v_defaults.purchase_account_code)),
|
|
225
|
+
sales_journal_id = coalesce(c.sales_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.sales_journal_code)),
|
|
226
|
+
purchase_journal_id = coalesce(c.purchase_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.purchase_journal_code)),
|
|
227
|
+
miscellaneous_journal_id = coalesce(c.miscellaneous_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.misc_journal_code))
|
|
228
|
+
where c.id = p_company_id;
|
|
229
|
+
|
|
230
|
+
-- 6. The financial journals point at their account.
|
|
231
|
+
update journals j
|
|
232
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
|
|
233
|
+
where j.company_id = p_company_id
|
|
234
|
+
and j.journal_type = 'bank'
|
|
235
|
+
and j.default_account_id is null
|
|
236
|
+
and v_defaults.bank_account_code is not null;
|
|
237
|
+
|
|
238
|
+
update journals j
|
|
239
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
|
|
240
|
+
where j.company_id = p_company_id
|
|
241
|
+
and j.journal_type = 'cash'
|
|
242
|
+
and j.default_account_id is null
|
|
243
|
+
and v_defaults.cash_account_code is not null;
|
|
244
|
+
end if;
|
|
245
|
+
|
|
246
|
+
-- 7. What was copied, and from which version. A pack that has not declared
|
|
247
|
+
-- itself — an instance seeded before this migration — counts as 1.0.0,
|
|
248
|
+
-- which is what every pack of this release is.
|
|
249
|
+
v_version := coalesce((select version from country_packs where country = p_country), '1.0.0');
|
|
250
|
+
|
|
251
|
+
insert into company_packs (company_id, country, version)
|
|
252
|
+
values (p_company_id, p_country, v_version)
|
|
253
|
+
on conflict (company_id, country) do update
|
|
254
|
+
set version = excluded.version,
|
|
255
|
+
upgraded_at = now()
|
|
256
|
+
where company_packs.version is distinct from excluded.version;
|
|
257
|
+
end;
|
|
258
|
+
$$;
|
|
259
|
+
|
|
260
|
+
comment on function install_country_template(uuid, char, char) is
|
|
261
|
+
'Copies a country pack into a company in one language, wires the default roles and the financial journals, and records the pack version in company_packs.';
|
|
262
|
+
|
|
263
|
+
-- A function created today is *not* closed, and migration `20260911210131`
|
|
264
|
+
-- promised it would be. `alter default privileges ... revoke execute on
|
|
265
|
+
-- functions from public` does not delete the built-in world default on
|
|
266
|
+
-- PostgreSQL 15: `get_user_default_acl` merges the stored default with it, so
|
|
267
|
+
-- a function created afterwards still comes out with `=X` — EXECUTE for
|
|
268
|
+
-- PUBLIC, which on Supabase means an anonymous RPC endpoint. This migration
|
|
269
|
+
-- is the first to create a function since that one, and
|
|
270
|
+
-- `install_country_template` came out open; row level security would have
|
|
271
|
+
-- refused every write it attempts, but the surface should be closed and not
|
|
272
|
+
-- merely empty.
|
|
273
|
+
--
|
|
274
|
+
-- So the revoke is repeated here, and from PUBLIC only: `anon` holds explicit
|
|
275
|
+
-- grants on the eight policy helpers, and revoking from `anon` would take
|
|
276
|
+
-- those away and break every anonymous SELECT the policies evaluate. Any
|
|
277
|
+
-- migration that adds a function has to end with this line — the rule is in
|
|
278
|
+
-- `supabase/migrations/README.md`.
|
|
279
|
+
|
|
280
|
+
revoke execute on all functions in schema public from public;
|
|
281
|
+
|
|
282
|
+
-- ---------------------------------------------------------------------------
|
|
283
|
+
-- Companies installed before this migration
|
|
284
|
+
--
|
|
285
|
+
-- They hold a chart, so they hold a pack; only its version was never written
|
|
286
|
+
-- down. Every pack of this release is 1.0.0, so that is what they copied.
|
|
287
|
+
-- ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
insert into company_packs (company_id, country, version, installed_at)
|
|
290
|
+
select c.id, c.country, '1.0.0', c.created_at
|
|
291
|
+
from companies c
|
|
292
|
+
where exists (select 1 from accounts a where a.company_id = c.id)
|
|
293
|
+
on conflict (company_id, country) do nothing;
|
|
294
|
+
|
|
295
|
+
-- ---------------------------------------------------------------------------
|
|
296
|
+
-- Row level security
|
|
297
|
+
--
|
|
298
|
+
-- `country_packs` is reference data about the installation, not about a
|
|
299
|
+
-- company: a member of any company may read it, an administrator may read it
|
|
300
|
+
-- before any company exists. `company_packs` follows its company, and the
|
|
301
|
+
-- right to write it is the right that installs a chart of accounts in the
|
|
302
|
+
-- first place. Neither table has a policy that lets a signed-in stranger see
|
|
303
|
+
-- anything.
|
|
304
|
+
-- ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
alter table country_packs enable row level security;
|
|
307
|
+
alter table company_packs enable row level security;
|
|
308
|
+
|
|
309
|
+
create policy country_packs_select on country_packs
|
|
310
|
+
for select using (is_any_company_member() or is_instance_admin());
|
|
311
|
+
|
|
312
|
+
create policy company_packs_select on company_packs
|
|
313
|
+
for select using (is_company_member(company_id));
|
|
314
|
+
create policy company_packs_write on company_packs
|
|
315
|
+
for all using (can_write_company(company_id)) with check (can_write_company(company_id));
|
|
316
|
+
|
|
317
|
+
comment on policy country_packs_select on country_packs is
|
|
318
|
+
'Members of any company and instance administrators. Written by the generated seed, which runs as the owner.';
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
-- Ekwo OS — which declaration a box belongs to, and which province a party is in.
|
|
2
|
+
--
|
|
3
|
+
-- Two columns the Canadian pack will need and that cost nothing to add now.
|
|
4
|
+
-- Both are phase-0 additions on purpose: adding them with the pack would mean
|
|
5
|
+
-- migrating `tax_postings` and `entry_lines` twice, and the second time they
|
|
6
|
+
-- would already hold years of postings.
|
|
7
|
+
--
|
|
8
|
+
-- **`report_code`.** `declaration_box` is a box number, and a box number is
|
|
9
|
+
-- only unique inside one form. Belgium and France each file one periodic
|
|
10
|
+
-- return, so '59' has never been ambiguous. A Canadian company files two at
|
|
11
|
+
-- once — the GST/HST return to the CRA and, in Québec, the combined return to
|
|
12
|
+
-- Revenu Québec — and line '101' of one is not line '101' of the other.
|
|
13
|
+
-- `report_code` says which form. Null keeps today's meaning: the periodic
|
|
14
|
+
-- return of the company's `fiscal_country`.
|
|
15
|
+
--
|
|
16
|
+
-- **`region`.** Canadian tax follows the *buyer's* province, not the seller's
|
|
17
|
+
-- (place of supply). The core deliberately chooses no tax for anyone; it can
|
|
18
|
+
-- only ever suggest one, and to suggest it has to know where the parties are.
|
|
19
|
+
-- ISO 3166-2 without the country prefix — `QC`, `BC`, `ON` — because the
|
|
20
|
+
-- country is already on the row next to it. The declarative rules that turn a
|
|
21
|
+
-- region into a suggested tax (`tax_rule_templates`, `suggest_tax()`) are
|
|
22
|
+
-- phase 1, with the pack that needs them; nothing reads `region` yet, and
|
|
23
|
+
-- that is the one deviation from "no column without a reader" this schema
|
|
24
|
+
-- makes, taken knowingly and written down here.
|
|
25
|
+
--
|
|
26
|
+
-- Additive: four `add column if not exists`, a backfill of the rows that
|
|
27
|
+
-- exist, and `install_country_template` replaced so it carries the new column
|
|
28
|
+
-- into a company.
|
|
29
|
+
|
|
30
|
+
-- ---------------------------------------------------------------------------
|
|
31
|
+
-- report_code
|
|
32
|
+
-- ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
alter table tax_posting_templates add column if not exists report_code text;
|
|
35
|
+
alter table tax_postings add column if not exists report_code text;
|
|
36
|
+
|
|
37
|
+
comment on column tax_posting_templates.report_code is
|
|
38
|
+
'Declaration form the box belongs to (BE-VAT-PERIODIC, FR-CA3, CA-GST34…). Null means the periodic return of the country.';
|
|
39
|
+
comment on column tax_postings.report_code is
|
|
40
|
+
'Declaration form the box belongs to. Copied from the template; read by vat_return(company, from, to, report_code) from P0-3.';
|
|
41
|
+
|
|
42
|
+
-- Nothing is backfilled here, and that is the whole point of the column being
|
|
43
|
+
-- nullable. On the template side the packs carry the code and the compiled
|
|
44
|
+
-- seeds upsert it, so re-applying the seeds names every template posting. On
|
|
45
|
+
-- the company side a null keeps its documented meaning — the periodic return
|
|
46
|
+
-- of the company's country — and `vat_return()` reads it exactly that way, so
|
|
47
|
+
-- a posting an older installation holds lands on the right form without
|
|
48
|
+
-- anything being rewritten. A backfill would have had to name a country and
|
|
49
|
+
-- the code of its form, which is the thing this schema refuses to do.
|
|
50
|
+
|
|
51
|
+
-- ---------------------------------------------------------------------------
|
|
52
|
+
-- region
|
|
53
|
+
-- ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
alter table companies add column if not exists region text;
|
|
56
|
+
alter table contacts add column if not exists region text;
|
|
57
|
+
|
|
58
|
+
comment on column companies.region is
|
|
59
|
+
'Province or state, ISO 3166-2 without the country prefix: QC, BC, CA. Null in a country that taxes uniformly.';
|
|
60
|
+
comment on column contacts.region is
|
|
61
|
+
'Province or state of the party, ISO 3166-2 without the country prefix. Canadian tax follows the buyer''s province, not the seller''s.';
|
|
62
|
+
|
|
63
|
+
-- ---------------------------------------------------------------------------
|
|
64
|
+
-- install_country_template carries report_code into the company
|
|
65
|
+
-- ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
create or replace function install_country_template(
|
|
68
|
+
p_company_id uuid,
|
|
69
|
+
p_country char(2),
|
|
70
|
+
p_language char(2) default null
|
|
71
|
+
)
|
|
72
|
+
returns void
|
|
73
|
+
language plpgsql
|
|
74
|
+
as $$
|
|
75
|
+
declare
|
|
76
|
+
v_defaults country_defaults%rowtype;
|
|
77
|
+
r record;
|
|
78
|
+
v_tax_id uuid;
|
|
79
|
+
v_language char(2);
|
|
80
|
+
v_version text;
|
|
81
|
+
begin
|
|
82
|
+
if not exists (select 1 from companies where id = p_company_id) then
|
|
83
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
84
|
+
end if;
|
|
85
|
+
if not exists (select 1 from account_templates where country = p_country) then
|
|
86
|
+
raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
|
|
87
|
+
end if;
|
|
88
|
+
|
|
89
|
+
select coalesce(p_language, c.language) into v_language
|
|
90
|
+
from companies c where c.id = p_company_id;
|
|
91
|
+
|
|
92
|
+
-- 1. Accounts, without the hierarchy.
|
|
93
|
+
insert into accounts (company_id, code, name, name_i18n, statement_hint,
|
|
94
|
+
account_type, reconcilable)
|
|
95
|
+
select p_company_id, t.code,
|
|
96
|
+
coalesce(nullif(t.name_i18n ->> v_language, ''), t.name),
|
|
97
|
+
t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
|
|
98
|
+
from account_templates t
|
|
99
|
+
where t.country = p_country
|
|
100
|
+
on conflict (company_id, code) do nothing;
|
|
101
|
+
|
|
102
|
+
-- 2. Hierarchy, now that every code exists.
|
|
103
|
+
update accounts a
|
|
104
|
+
set parent_id = p.id
|
|
105
|
+
from account_templates t
|
|
106
|
+
join accounts p on p.company_id = p_company_id and p.code = t.parent_code
|
|
107
|
+
where a.company_id = p_company_id
|
|
108
|
+
and a.code = t.code
|
|
109
|
+
and t.country = p_country
|
|
110
|
+
and t.parent_code is not null
|
|
111
|
+
and a.parent_id is null;
|
|
112
|
+
|
|
113
|
+
-- 3. Journals.
|
|
114
|
+
insert into journals (company_id, code, name, journal_type)
|
|
115
|
+
select p_company_id, t.code, t.name, t.journal_type
|
|
116
|
+
from journal_templates t
|
|
117
|
+
where t.country = p_country
|
|
118
|
+
on conflict (company_id, code) do nothing;
|
|
119
|
+
|
|
120
|
+
-- 4. Taxes and their postings, each posting keeping the form its box is on.
|
|
121
|
+
for r in
|
|
122
|
+
select * from tax_templates where country = p_country order by sequence, code
|
|
123
|
+
loop
|
|
124
|
+
v_tax_id := null;
|
|
125
|
+
insert into taxes (company_id, code, name, description, amount_type, amount,
|
|
126
|
+
applies_to, treatment, country, valid_from, valid_to,
|
|
127
|
+
legal_reference, vat_category, exemption_code, sequence)
|
|
128
|
+
values (p_company_id, r.code, r.name, r.description, r.amount_type, r.amount,
|
|
129
|
+
r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
|
|
130
|
+
r.legal_reference, r.vat_category, r.exemption_code, r.sequence)
|
|
131
|
+
on conflict (company_id, code) do nothing
|
|
132
|
+
returning id into v_tax_id;
|
|
133
|
+
|
|
134
|
+
if v_tax_id is null then
|
|
135
|
+
continue;
|
|
136
|
+
end if;
|
|
137
|
+
|
|
138
|
+
insert into tax_postings (tax_id, company_id, document_kind, posting_type,
|
|
139
|
+
factor_percent, account_id, declaration_box,
|
|
140
|
+
box_factor_percent, report_code, sequence)
|
|
141
|
+
select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
|
|
142
|
+
tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
|
|
143
|
+
tp.declaration_box, tp.box_factor_percent, tp.report_code, tp.sequence
|
|
144
|
+
from tax_posting_templates tp
|
|
145
|
+
where tp.tax_template_id = r.id
|
|
146
|
+
order by tp.sequence;
|
|
147
|
+
end loop;
|
|
148
|
+
|
|
149
|
+
-- 5. Roles.
|
|
150
|
+
select * into v_defaults from country_defaults where country = p_country;
|
|
151
|
+
if found then
|
|
152
|
+
update companies c
|
|
153
|
+
set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
|
|
154
|
+
payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
|
|
155
|
+
suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
|
|
156
|
+
rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
|
|
157
|
+
retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
|
|
158
|
+
default_sales_account_id = coalesce(c.default_sales_account_id, account_id_by_code(p_company_id, v_defaults.sales_account_code)),
|
|
159
|
+
default_purchase_account_id = coalesce(c.default_purchase_account_id, account_id_by_code(p_company_id, v_defaults.purchase_account_code)),
|
|
160
|
+
sales_journal_id = coalesce(c.sales_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.sales_journal_code)),
|
|
161
|
+
purchase_journal_id = coalesce(c.purchase_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.purchase_journal_code)),
|
|
162
|
+
miscellaneous_journal_id = coalesce(c.miscellaneous_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.misc_journal_code))
|
|
163
|
+
where c.id = p_company_id;
|
|
164
|
+
|
|
165
|
+
update journals j
|
|
166
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
|
|
167
|
+
where j.company_id = p_company_id
|
|
168
|
+
and j.journal_type = 'bank'
|
|
169
|
+
and j.default_account_id is null
|
|
170
|
+
and v_defaults.bank_account_code is not null;
|
|
171
|
+
|
|
172
|
+
update journals j
|
|
173
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
|
|
174
|
+
where j.company_id = p_company_id
|
|
175
|
+
and j.journal_type = 'cash'
|
|
176
|
+
and j.default_account_id is null
|
|
177
|
+
and v_defaults.cash_account_code is not null;
|
|
178
|
+
end if;
|
|
179
|
+
|
|
180
|
+
-- 6. What was copied, and from which version.
|
|
181
|
+
v_version := coalesce((select version from country_packs where country = p_country), '1.0.0');
|
|
182
|
+
|
|
183
|
+
insert into company_packs (company_id, country, version)
|
|
184
|
+
values (p_company_id, p_country, v_version)
|
|
185
|
+
on conflict (company_id, country) do update
|
|
186
|
+
set version = excluded.version,
|
|
187
|
+
upgraded_at = now()
|
|
188
|
+
where company_packs.version is distinct from excluded.version;
|
|
189
|
+
end;
|
|
190
|
+
$$;
|
|
191
|
+
|
|
192
|
+
comment on function install_country_template(uuid, char, char) is
|
|
193
|
+
'Copies a country pack into a company in one language, wires the default roles and the financial journals, and records the pack version in company_packs.';
|
|
194
|
+
|
|
195
|
+
-- The rule of `20260911210131`, which its own default privileges cannot keep:
|
|
196
|
+
-- a function created now comes out executable by PUBLIC unless this runs.
|
|
197
|
+
-- From PUBLIC only — `anon` holds explicit grants on the policy helpers.
|
|
198
|
+
revoke execute on all functions in schema public from public;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- Ekwo OS — Ekwo maintains a pack; only a professional reviews one.
|
|
2
|
+
--
|
|
3
|
+
-- `pack_certification` shipped with three values, and one of them said the
|
|
4
|
+
-- wrong thing. `ekwo` read as "certified by Ekwo", which is a claim nobody
|
|
5
|
+
-- here can make: we write the pack, we test that it is internally coherent,
|
|
6
|
+
-- and none of that is an accountant reading it against the law. The word
|
|
7
|
+
-- *certified* should only ever describe a review by a named professional.
|
|
8
|
+
--
|
|
9
|
+
-- So the scale becomes:
|
|
10
|
+
--
|
|
11
|
+
-- community contributed, not read by an accountant
|
|
12
|
+
-- maintained maintained by Ekwo, not yet reviewed by an accountant
|
|
13
|
+
-- reviewed read by a named professional, with `certified_by`,
|
|
14
|
+
-- `certified_at` and the sources they worked from
|
|
15
|
+
--
|
|
16
|
+
-- `ekwo` stays in the type because a value is never removed from an enum a
|
|
17
|
+
-- published column uses, and it is deprecated: nothing writes it any more,
|
|
18
|
+
-- the JSON schema of a pack refuses it, and the next migration moves the rows
|
|
19
|
+
-- that hold it. A value cannot be added and used in the same transaction,
|
|
20
|
+
-- which is why the update is its own file rather than ten lines below.
|
|
21
|
+
|
|
22
|
+
alter type pack_certification add value if not exists 'maintained' after 'community';
|
|
23
|
+
|
|
24
|
+
comment on type pack_certification is
|
|
25
|
+
'How much a pack has been read: community (contributed, unread), maintained (by Ekwo, not yet reviewed), reviewed (by a named professional). The value `ekwo` is deprecated and nothing writes it.';
|
|
26
|
+
|
|
27
|
+
comment on column country_packs.certification_status is
|
|
28
|
+
'How much a pack has been read, printed by `ekwo init`: community (contributed, unread), maintained (by Ekwo, not yet reviewed), reviewed (by the professional named in certified_by).';
|
|
29
|
+
|
|
30
|
+
comment on column country_packs.certified_by is
|
|
31
|
+
'The professional who reviewed the pack. Only on a reviewed pack: maintaining is not reviewing.';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Ekwo OS — the packs that called themselves certified are maintained.
|
|
2
|
+
--
|
|
3
|
+
-- Its own file because PostgreSQL refuses a new enum value in the transaction
|
|
4
|
+
-- that added it, and the migration runner gives each file one transaction.
|
|
5
|
+
--
|
|
6
|
+
-- `certified_by` is emptied along with it: on a maintained pack it held
|
|
7
|
+
-- "Ekwo AI", which is the claim this pair of migrations exists to stop
|
|
8
|
+
-- making. On a reviewed pack it holds the professional who read it, and this
|
|
9
|
+
-- touches nothing of the sort. The generated seeds carry the same values, so
|
|
10
|
+
-- an installation that re-applies them lands in the same place.
|
|
11
|
+
|
|
12
|
+
update country_packs
|
|
13
|
+
set certification_status = 'maintained',
|
|
14
|
+
certified_by = null,
|
|
15
|
+
certified_at = null
|
|
16
|
+
where certification_status = 'ekwo';
|