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,260 @@
|
|
|
1
|
+
-- Ekwo OS — core: companies, membership, fiscal years.
|
|
2
|
+
--
|
|
3
|
+
-- One Ekwo instance belongs to one customer, so there is no `tenant_id`.
|
|
4
|
+
-- Isolation inside an instance is carried by `company_id` plus row level
|
|
5
|
+
-- security driven by `company_members`.
|
|
6
|
+
|
|
7
|
+
-- gen_random_uuid() is in core Postgres since 13; no extension is needed.
|
|
8
|
+
|
|
9
|
+
-- ---------------------------------------------------------------------------
|
|
10
|
+
-- Shared helpers
|
|
11
|
+
-- ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
create or replace function set_updated_at()
|
|
14
|
+
returns trigger
|
|
15
|
+
language plpgsql
|
|
16
|
+
as $$
|
|
17
|
+
begin
|
|
18
|
+
new.updated_at := now();
|
|
19
|
+
return new;
|
|
20
|
+
end;
|
|
21
|
+
$$;
|
|
22
|
+
|
|
23
|
+
comment on function set_updated_at() is
|
|
24
|
+
'Generic BEFORE UPDATE trigger keeping updated_at honest.';
|
|
25
|
+
|
|
26
|
+
-- ---------------------------------------------------------------------------
|
|
27
|
+
-- Enumerations
|
|
28
|
+
-- ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
create type member_role as enum ('owner', 'accountant', 'viewer');
|
|
31
|
+
|
|
32
|
+
-- ---------------------------------------------------------------------------
|
|
33
|
+
-- companies
|
|
34
|
+
-- ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
create table companies (
|
|
37
|
+
id uuid primary key default gen_random_uuid(),
|
|
38
|
+
name text not null,
|
|
39
|
+
legal_name text,
|
|
40
|
+
legal_form text,
|
|
41
|
+
country char(2) not null,
|
|
42
|
+
fiscal_country char(2) not null,
|
|
43
|
+
vat_number text,
|
|
44
|
+
registration_number text,
|
|
45
|
+
address_line1 text,
|
|
46
|
+
address_line2 text,
|
|
47
|
+
postal_code text,
|
|
48
|
+
city text,
|
|
49
|
+
email text,
|
|
50
|
+
phone text,
|
|
51
|
+
website text,
|
|
52
|
+
currency_code char(3) not null default 'EUR',
|
|
53
|
+
-- Period locks. No entry may be created, changed or deleted on or before
|
|
54
|
+
-- these dates. `tax_lock_date` additionally freezes anything carrying a
|
|
55
|
+
-- VAT declaration box.
|
|
56
|
+
lock_date date,
|
|
57
|
+
tax_lock_date date,
|
|
58
|
+
-- Default accounts, resolved by ROLE and never by code prefix.
|
|
59
|
+
-- Foreign keys are added once `accounts` exists.
|
|
60
|
+
receivable_account_id uuid,
|
|
61
|
+
payable_account_id uuid,
|
|
62
|
+
suspense_account_id uuid,
|
|
63
|
+
rounding_account_id uuid,
|
|
64
|
+
retained_earnings_account_id uuid,
|
|
65
|
+
sales_journal_id uuid,
|
|
66
|
+
purchase_journal_id uuid,
|
|
67
|
+
miscellaneous_journal_id uuid,
|
|
68
|
+
created_at timestamptz not null default now(),
|
|
69
|
+
updated_at timestamptz not null default now(),
|
|
70
|
+
constraint companies_country_format check (country ~ '^[A-Z]{2}$'),
|
|
71
|
+
constraint companies_fiscal_country_format check (fiscal_country ~ '^[A-Z]{2}$'),
|
|
72
|
+
constraint companies_currency_format check (currency_code ~ '^[A-Z]{3}$')
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
comment on table companies is 'Legal entities kept in this instance. One instance may hold several.';
|
|
76
|
+
comment on column companies.fiscal_country is 'Country whose VAT rules apply; differs from `country` for a foreign VAT registration.';
|
|
77
|
+
comment on column companies.lock_date is 'Accounting lock: nothing may be booked on or before this date.';
|
|
78
|
+
|
|
79
|
+
create trigger companies_set_updated_at
|
|
80
|
+
before update on companies
|
|
81
|
+
for each row execute function set_updated_at();
|
|
82
|
+
|
|
83
|
+
-- ---------------------------------------------------------------------------
|
|
84
|
+
-- company_members
|
|
85
|
+
-- ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
create table company_members (
|
|
88
|
+
company_id uuid not null references companies(id) on delete cascade,
|
|
89
|
+
user_id uuid not null,
|
|
90
|
+
role member_role not null default 'viewer',
|
|
91
|
+
created_at timestamptz not null default now(),
|
|
92
|
+
updated_at timestamptz not null default now(),
|
|
93
|
+
primary key (company_id, user_id)
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
comment on table company_members is 'Who may read or write a company. `owner` administers, `accountant` books, `viewer` reads.';
|
|
97
|
+
|
|
98
|
+
create index company_members_user_id_idx on company_members (user_id);
|
|
99
|
+
|
|
100
|
+
create trigger company_members_set_updated_at
|
|
101
|
+
before update on company_members
|
|
102
|
+
for each row execute function set_updated_at();
|
|
103
|
+
|
|
104
|
+
-- ---------------------------------------------------------------------------
|
|
105
|
+
-- Access helpers
|
|
106
|
+
--
|
|
107
|
+
-- SECURITY DEFINER so that the policy on `company_members` itself does not
|
|
108
|
+
-- recurse into the table it protects.
|
|
109
|
+
-- ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
create or replace function company_role(p_company_id uuid)
|
|
112
|
+
returns member_role
|
|
113
|
+
language sql
|
|
114
|
+
stable
|
|
115
|
+
security definer
|
|
116
|
+
set search_path = public, pg_temp
|
|
117
|
+
as $$
|
|
118
|
+
select m.role
|
|
119
|
+
from company_members m
|
|
120
|
+
where m.company_id = p_company_id
|
|
121
|
+
and m.user_id = auth.uid();
|
|
122
|
+
$$;
|
|
123
|
+
|
|
124
|
+
create or replace function is_company_member(p_company_id uuid)
|
|
125
|
+
returns boolean
|
|
126
|
+
language sql
|
|
127
|
+
stable
|
|
128
|
+
as $$
|
|
129
|
+
select company_role(p_company_id) is not null;
|
|
130
|
+
$$;
|
|
131
|
+
|
|
132
|
+
create or replace function can_write_company(p_company_id uuid)
|
|
133
|
+
returns boolean
|
|
134
|
+
language sql
|
|
135
|
+
stable
|
|
136
|
+
as $$
|
|
137
|
+
select company_role(p_company_id) in ('owner', 'accountant');
|
|
138
|
+
$$;
|
|
139
|
+
|
|
140
|
+
create or replace function is_company_owner(p_company_id uuid)
|
|
141
|
+
returns boolean
|
|
142
|
+
language sql
|
|
143
|
+
stable
|
|
144
|
+
as $$
|
|
145
|
+
select company_role(p_company_id) = 'owner';
|
|
146
|
+
$$;
|
|
147
|
+
|
|
148
|
+
comment on function company_role(uuid) is 'Role of the current user on a company, or NULL when they are not a member.';
|
|
149
|
+
|
|
150
|
+
-- Bootstrap helper: the first member of a fresh company claims it. SECURITY
|
|
151
|
+
-- DEFINER so the policy on company_members does not recurse into itself.
|
|
152
|
+
create or replace function company_has_no_member(p_company_id uuid)
|
|
153
|
+
returns boolean
|
|
154
|
+
language sql
|
|
155
|
+
stable
|
|
156
|
+
security definer
|
|
157
|
+
set search_path = public, pg_temp
|
|
158
|
+
as $$
|
|
159
|
+
select not exists (select 1 from company_members m where m.company_id = p_company_id);
|
|
160
|
+
$$;
|
|
161
|
+
|
|
162
|
+
-- ---------------------------------------------------------------------------
|
|
163
|
+
-- fiscal_years
|
|
164
|
+
-- ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
create table fiscal_years (
|
|
167
|
+
id uuid primary key default gen_random_uuid(),
|
|
168
|
+
company_id uuid not null references companies(id) on delete cascade,
|
|
169
|
+
name text not null,
|
|
170
|
+
start_date date not null,
|
|
171
|
+
end_date date not null,
|
|
172
|
+
is_closed boolean not null default false,
|
|
173
|
+
closed_at timestamptz,
|
|
174
|
+
created_at timestamptz not null default now(),
|
|
175
|
+
updated_at timestamptz not null default now(),
|
|
176
|
+
constraint fiscal_years_dates check (end_date > start_date),
|
|
177
|
+
unique (company_id, start_date)
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
comment on table fiscal_years is 'Accounting periods. An exercise is an object, not two integers on the company.';
|
|
181
|
+
|
|
182
|
+
create index fiscal_years_company_range_idx on fiscal_years (company_id, start_date, end_date);
|
|
183
|
+
|
|
184
|
+
create trigger fiscal_years_set_updated_at
|
|
185
|
+
before update on fiscal_years
|
|
186
|
+
for each row execute function set_updated_at();
|
|
187
|
+
|
|
188
|
+
-- Overlap guard. An exclusion constraint would need btree_gist, which is not
|
|
189
|
+
-- available on every Postgres build (PGlite included), so this is a trigger.
|
|
190
|
+
create or replace function fiscal_years_no_overlap()
|
|
191
|
+
returns trigger
|
|
192
|
+
language plpgsql
|
|
193
|
+
as $$
|
|
194
|
+
begin
|
|
195
|
+
if exists (
|
|
196
|
+
select 1
|
|
197
|
+
from fiscal_years f
|
|
198
|
+
where f.company_id = new.company_id
|
|
199
|
+
and f.id <> new.id
|
|
200
|
+
and f.start_date <= new.end_date
|
|
201
|
+
and f.end_date >= new.start_date
|
|
202
|
+
) then
|
|
203
|
+
raise exception 'fiscal_year_overlap: % .. % overlaps an existing fiscal year of this company',
|
|
204
|
+
new.start_date, new.end_date
|
|
205
|
+
using errcode = '23505';
|
|
206
|
+
end if;
|
|
207
|
+
return new;
|
|
208
|
+
end;
|
|
209
|
+
$$;
|
|
210
|
+
|
|
211
|
+
create trigger fiscal_years_no_overlap
|
|
212
|
+
before insert or update of start_date, end_date, company_id on fiscal_years
|
|
213
|
+
for each row execute function fiscal_years_no_overlap();
|
|
214
|
+
|
|
215
|
+
create or replace function fiscal_year_at(p_company_id uuid, p_date date)
|
|
216
|
+
returns uuid
|
|
217
|
+
language sql
|
|
218
|
+
stable
|
|
219
|
+
as $$
|
|
220
|
+
select f.id
|
|
221
|
+
from fiscal_years f
|
|
222
|
+
where f.company_id = p_company_id
|
|
223
|
+
and p_date between f.start_date and f.end_date
|
|
224
|
+
limit 1;
|
|
225
|
+
$$;
|
|
226
|
+
|
|
227
|
+
comment on function fiscal_year_at(uuid, date) is 'Fiscal year covering a date, or NULL.';
|
|
228
|
+
|
|
229
|
+
-- ---------------------------------------------------------------------------
|
|
230
|
+
-- Row level security
|
|
231
|
+
-- ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
alter table companies enable row level security;
|
|
234
|
+
alter table company_members enable row level security;
|
|
235
|
+
alter table fiscal_years enable row level security;
|
|
236
|
+
|
|
237
|
+
create policy companies_select on companies
|
|
238
|
+
for select using (is_company_member(id));
|
|
239
|
+
create policy companies_insert on companies
|
|
240
|
+
for insert with check (auth.uid() is not null);
|
|
241
|
+
create policy companies_update on companies
|
|
242
|
+
for update using (is_company_owner(id)) with check (is_company_owner(id));
|
|
243
|
+
create policy companies_delete on companies
|
|
244
|
+
for delete using (is_company_owner(id));
|
|
245
|
+
|
|
246
|
+
create policy company_members_select on company_members
|
|
247
|
+
for select using (user_id = auth.uid() or is_company_member(company_id));
|
|
248
|
+
create policy company_members_insert on company_members
|
|
249
|
+
for insert with check (
|
|
250
|
+
is_company_owner(company_id) or company_has_no_member(company_id)
|
|
251
|
+
);
|
|
252
|
+
create policy company_members_update on company_members
|
|
253
|
+
for update using (is_company_owner(company_id)) with check (is_company_owner(company_id));
|
|
254
|
+
create policy company_members_delete on company_members
|
|
255
|
+
for delete using (is_company_owner(company_id));
|
|
256
|
+
|
|
257
|
+
create policy fiscal_years_select on fiscal_years
|
|
258
|
+
for select using (is_company_member(company_id));
|
|
259
|
+
create policy fiscal_years_write on fiscal_years
|
|
260
|
+
for all using (can_write_company(company_id)) with check (can_write_company(company_id));
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
-- Ekwo OS — chart of accounts, journals, currencies.
|
|
2
|
+
|
|
3
|
+
-- ---------------------------------------------------------------------------
|
|
4
|
+
-- currencies (instance-wide reference data)
|
|
5
|
+
-- ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
create table currencies (
|
|
8
|
+
code char(3) primary key,
|
|
9
|
+
name text not null,
|
|
10
|
+
symbol text,
|
|
11
|
+
decimal_places smallint not null default 2,
|
|
12
|
+
active boolean not null default true,
|
|
13
|
+
constraint currencies_code_format check (code ~ '^[A-Z]{3}$'),
|
|
14
|
+
constraint currencies_decimals check (decimal_places between 0 and 6)
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
comment on table currencies is 'ISO 4217 currencies known to this instance.';
|
|
18
|
+
|
|
19
|
+
create table currency_rates (
|
|
20
|
+
id uuid primary key default gen_random_uuid(),
|
|
21
|
+
currency_code char(3) not null references currencies(code) on delete cascade,
|
|
22
|
+
rate_date date not null,
|
|
23
|
+
-- Units of `currency_code` for one unit of the instance reference currency.
|
|
24
|
+
rate numeric(18, 8) not null,
|
|
25
|
+
source text,
|
|
26
|
+
created_at timestamptz not null default now(),
|
|
27
|
+
unique (currency_code, rate_date),
|
|
28
|
+
constraint currency_rates_positive check (rate > 0)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
comment on table currency_rates is 'Dated exchange rates. A document stores the rate it used; this table is the history.';
|
|
32
|
+
|
|
33
|
+
-- ---------------------------------------------------------------------------
|
|
34
|
+
-- accounts
|
|
35
|
+
-- ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
-- Eighteen account types. The prefix before the first underscore is the
|
|
38
|
+
-- balance-sheet group, which is what makes the aged balance, reconcilability
|
|
39
|
+
-- and the statement mapping computable instead of pattern-matched on codes.
|
|
40
|
+
create type account_type as enum (
|
|
41
|
+
'asset_receivable',
|
|
42
|
+
'asset_cash',
|
|
43
|
+
'asset_current',
|
|
44
|
+
'asset_prepayments',
|
|
45
|
+
'asset_fixed',
|
|
46
|
+
'asset_non_current',
|
|
47
|
+
'liability_payable',
|
|
48
|
+
'liability_credit_card',
|
|
49
|
+
'liability_current',
|
|
50
|
+
'liability_non_current',
|
|
51
|
+
'equity',
|
|
52
|
+
'equity_retained',
|
|
53
|
+
'income',
|
|
54
|
+
'income_other',
|
|
55
|
+
'expense',
|
|
56
|
+
'expense_direct_cost',
|
|
57
|
+
'expense_depreciation',
|
|
58
|
+
'off_balance'
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
create table accounts (
|
|
62
|
+
id uuid primary key default gen_random_uuid(),
|
|
63
|
+
company_id uuid not null references companies(id) on delete cascade,
|
|
64
|
+
code text not null,
|
|
65
|
+
name text not null,
|
|
66
|
+
account_type account_type not null,
|
|
67
|
+
internal_group text generated always as (
|
|
68
|
+
case
|
|
69
|
+
when account_type in ('asset_receivable', 'asset_cash', 'asset_current',
|
|
70
|
+
'asset_prepayments', 'asset_fixed', 'asset_non_current')
|
|
71
|
+
then 'asset'
|
|
72
|
+
when account_type in ('liability_payable', 'liability_credit_card',
|
|
73
|
+
'liability_current', 'liability_non_current')
|
|
74
|
+
then 'liability'
|
|
75
|
+
when account_type in ('equity', 'equity_retained') then 'equity'
|
|
76
|
+
when account_type in ('income', 'income_other') then 'income'
|
|
77
|
+
when account_type in ('expense', 'expense_direct_cost', 'expense_depreciation')
|
|
78
|
+
then 'expense'
|
|
79
|
+
else 'off_balance'
|
|
80
|
+
end
|
|
81
|
+
) stored,
|
|
82
|
+
-- Balance-sheet accounts carry over from one fiscal year to the next.
|
|
83
|
+
carries_forward boolean generated always as (
|
|
84
|
+
account_type not in ('income', 'income_other', 'expense',
|
|
85
|
+
'expense_direct_cost', 'expense_depreciation')
|
|
86
|
+
) stored,
|
|
87
|
+
reconcilable boolean not null default false,
|
|
88
|
+
currency_code char(3) references currencies(code),
|
|
89
|
+
parent_id uuid references accounts(id) on delete set null,
|
|
90
|
+
deprecated boolean not null default false,
|
|
91
|
+
notes text,
|
|
92
|
+
created_at timestamptz not null default now(),
|
|
93
|
+
updated_at timestamptz not null default now(),
|
|
94
|
+
unique (company_id, code),
|
|
95
|
+
-- A third-party account that cannot be matched makes the aged balance and
|
|
96
|
+
-- the FEC letters impossible. Refuse it at the door.
|
|
97
|
+
constraint accounts_third_party_reconcilable check (
|
|
98
|
+
account_type not in ('asset_receivable', 'liability_payable') or reconcilable
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
comment on table accounts is 'Chart of accounts, one per company.';
|
|
103
|
+
comment on column accounts.internal_group is 'asset | liability | equity | income | expense | off_balance, derived from account_type.';
|
|
104
|
+
comment on column accounts.reconcilable is 'Whether entry lines on this account may be matched against each other.';
|
|
105
|
+
|
|
106
|
+
create index accounts_company_type_idx on accounts (company_id, account_type);
|
|
107
|
+
create index accounts_parent_idx on accounts (parent_id);
|
|
108
|
+
-- Composite target so children can inherit company_id by foreign key.
|
|
109
|
+
create unique index accounts_id_company_idx on accounts (id, company_id);
|
|
110
|
+
|
|
111
|
+
create trigger accounts_set_updated_at
|
|
112
|
+
before update on accounts
|
|
113
|
+
for each row execute function set_updated_at();
|
|
114
|
+
|
|
115
|
+
-- ---------------------------------------------------------------------------
|
|
116
|
+
-- journals
|
|
117
|
+
-- ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
create type journal_type as enum ('sales', 'purchase', 'bank', 'cash', 'general', 'opening');
|
|
120
|
+
|
|
121
|
+
create table journals (
|
|
122
|
+
id uuid primary key default gen_random_uuid(),
|
|
123
|
+
company_id uuid not null references companies(id) on delete cascade,
|
|
124
|
+
code text not null,
|
|
125
|
+
name text not null,
|
|
126
|
+
journal_type journal_type not null,
|
|
127
|
+
default_account_id uuid references accounts(id) on delete set null,
|
|
128
|
+
suspense_account_id uuid references accounts(id) on delete set null,
|
|
129
|
+
bank_account_id uuid,
|
|
130
|
+
currency_code char(3) references currencies(code),
|
|
131
|
+
active boolean not null default true,
|
|
132
|
+
created_at timestamptz not null default now(),
|
|
133
|
+
updated_at timestamptz not null default now(),
|
|
134
|
+
unique (company_id, code),
|
|
135
|
+
constraint journals_code_format check (code ~ '^[A-Z0-9]{2,8}$')
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
comment on table journals is 'Books of entry. The code is the first segment of every entry number.';
|
|
139
|
+
comment on column journals.suspense_account_id is 'Where a bank line lands before it is allocated.';
|
|
140
|
+
|
|
141
|
+
create unique index journals_id_company_idx on journals (id, company_id);
|
|
142
|
+
|
|
143
|
+
create trigger journals_set_updated_at
|
|
144
|
+
before update on journals
|
|
145
|
+
for each row execute function set_updated_at();
|
|
146
|
+
|
|
147
|
+
-- Per journal AND per year counter. A single counter per journal would make
|
|
148
|
+
-- the year in the number decorative; here the number means what it says.
|
|
149
|
+
create table journal_sequences (
|
|
150
|
+
journal_id uuid not null references journals(id) on delete cascade,
|
|
151
|
+
year smallint not null,
|
|
152
|
+
last_number integer not null default 0,
|
|
153
|
+
primary key (journal_id, year),
|
|
154
|
+
constraint journal_sequences_positive check (last_number >= 0)
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
comment on table journal_sequences is 'Counter behind next_entry_number(). One row per journal and year.';
|
|
158
|
+
|
|
159
|
+
-- ---------------------------------------------------------------------------
|
|
160
|
+
-- Company default accounts and journals, now that the targets exist.
|
|
161
|
+
-- ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
alter table companies
|
|
164
|
+
add constraint companies_receivable_account_fkey
|
|
165
|
+
foreign key (receivable_account_id) references accounts(id) on delete set null,
|
|
166
|
+
add constraint companies_payable_account_fkey
|
|
167
|
+
foreign key (payable_account_id) references accounts(id) on delete set null,
|
|
168
|
+
add constraint companies_suspense_account_fkey
|
|
169
|
+
foreign key (suspense_account_id) references accounts(id) on delete set null,
|
|
170
|
+
add constraint companies_rounding_account_fkey
|
|
171
|
+
foreign key (rounding_account_id) references accounts(id) on delete set null,
|
|
172
|
+
add constraint companies_retained_earnings_account_fkey
|
|
173
|
+
foreign key (retained_earnings_account_id) references accounts(id) on delete set null,
|
|
174
|
+
add constraint companies_sales_journal_fkey
|
|
175
|
+
foreign key (sales_journal_id) references journals(id) on delete set null,
|
|
176
|
+
add constraint companies_purchase_journal_fkey
|
|
177
|
+
foreign key (purchase_journal_id) references journals(id) on delete set null,
|
|
178
|
+
add constraint companies_miscellaneous_journal_fkey
|
|
179
|
+
foreign key (miscellaneous_journal_id) references journals(id) on delete set null;
|
|
180
|
+
|
|
181
|
+
-- ---------------------------------------------------------------------------
|
|
182
|
+
-- Row level security
|
|
183
|
+
-- ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
alter table currencies enable row level security;
|
|
186
|
+
alter table currency_rates enable row level security;
|
|
187
|
+
alter table accounts enable row level security;
|
|
188
|
+
alter table journals enable row level security;
|
|
189
|
+
alter table journal_sequences enable row level security;
|
|
190
|
+
|
|
191
|
+
-- Reference data: readable by anyone signed in, written by nobody through the
|
|
192
|
+
-- API (seeds and migrations run as the table owner and bypass RLS).
|
|
193
|
+
create policy currencies_select on currencies
|
|
194
|
+
for select using (auth.uid() is not null);
|
|
195
|
+
create policy currency_rates_select on currency_rates
|
|
196
|
+
for select using (auth.uid() is not null);
|
|
197
|
+
|
|
198
|
+
create policy accounts_select on accounts
|
|
199
|
+
for select using (is_company_member(company_id));
|
|
200
|
+
create policy accounts_write on accounts
|
|
201
|
+
for all using (can_write_company(company_id)) with check (can_write_company(company_id));
|
|
202
|
+
|
|
203
|
+
create policy journals_select on journals
|
|
204
|
+
for select using (is_company_member(company_id));
|
|
205
|
+
create policy journals_write on journals
|
|
206
|
+
for all using (can_write_company(company_id)) with check (can_write_company(company_id));
|
|
207
|
+
|
|
208
|
+
create policy journal_sequences_select on journal_sequences
|
|
209
|
+
for select using (
|
|
210
|
+
exists (select 1 from journals j where j.id = journal_id and is_company_member(j.company_id))
|
|
211
|
+
);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
-- Ekwo OS — contacts (customers, suppliers, employees).
|
|
2
|
+
|
|
3
|
+
create type contact_type as enum ('customer', 'supplier', 'both', 'employee', 'other');
|
|
4
|
+
|
|
5
|
+
create table contacts (
|
|
6
|
+
id uuid primary key default gen_random_uuid(),
|
|
7
|
+
company_id uuid not null references companies(id) on delete cascade,
|
|
8
|
+
name text not null,
|
|
9
|
+
contact_type contact_type not null default 'customer',
|
|
10
|
+
is_company boolean not null default true,
|
|
11
|
+
-- The entity actually invoiced, when this contact is a site or a person of
|
|
12
|
+
-- a larger group. Postings use the root of the chain.
|
|
13
|
+
parent_id uuid references contacts(id) on delete set null,
|
|
14
|
+
vat_number text,
|
|
15
|
+
registration_number text,
|
|
16
|
+
-- Sub-ledger code. This is `CompAuxNum` of the French FEC.
|
|
17
|
+
auxiliary_code text,
|
|
18
|
+
email text,
|
|
19
|
+
phone text,
|
|
20
|
+
address_line1 text,
|
|
21
|
+
address_line2 text,
|
|
22
|
+
postal_code text,
|
|
23
|
+
city text,
|
|
24
|
+
country char(2),
|
|
25
|
+
language char(2),
|
|
26
|
+
currency_code char(3) references currencies(code),
|
|
27
|
+
payment_terms_days smallint not null default 30,
|
|
28
|
+
receivable_account_id uuid references accounts(id) on delete set null,
|
|
29
|
+
payable_account_id uuid references accounts(id) on delete set null,
|
|
30
|
+
iban text,
|
|
31
|
+
bic text,
|
|
32
|
+
-- Peppol participant identifier, e.g. scheme 0208 + Belgian enterprise number.
|
|
33
|
+
peppol_scheme text,
|
|
34
|
+
peppol_identifier text,
|
|
35
|
+
active boolean not null default true,
|
|
36
|
+
notes text,
|
|
37
|
+
created_at timestamptz not null default now(),
|
|
38
|
+
updated_at timestamptz not null default now(),
|
|
39
|
+
constraint contacts_country_format check (country is null or country ~ '^[A-Z]{2}$'),
|
|
40
|
+
constraint contacts_payment_terms check (payment_terms_days >= 0),
|
|
41
|
+
constraint contacts_not_own_parent check (parent_id is null or parent_id <> id)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
comment on table contacts is 'Third parties. `contact_type` is explicit rather than two hidden counters.';
|
|
45
|
+
comment on column contacts.auxiliary_code is 'Sub-ledger code, exported as CompAuxNum in the FEC.';
|
|
46
|
+
comment on column contacts.parent_id is 'Billing parent; commercial_entity() walks to the root.';
|
|
47
|
+
|
|
48
|
+
create unique index contacts_company_auxiliary_code_idx
|
|
49
|
+
on contacts (company_id, auxiliary_code)
|
|
50
|
+
where auxiliary_code is not null;
|
|
51
|
+
create index contacts_company_name_idx on contacts (company_id, name);
|
|
52
|
+
create index contacts_parent_idx on contacts (parent_id);
|
|
53
|
+
create unique index contacts_id_company_idx on contacts (id, company_id);
|
|
54
|
+
|
|
55
|
+
create trigger contacts_set_updated_at
|
|
56
|
+
before update on contacts
|
|
57
|
+
for each row execute function set_updated_at();
|
|
58
|
+
|
|
59
|
+
-- The billable entity: the root of the parent chain.
|
|
60
|
+
create or replace function commercial_entity(p_contact_id uuid)
|
|
61
|
+
returns uuid
|
|
62
|
+
language plpgsql
|
|
63
|
+
stable
|
|
64
|
+
as $$
|
|
65
|
+
declare
|
|
66
|
+
v_id uuid := p_contact_id;
|
|
67
|
+
v_parent uuid;
|
|
68
|
+
v_guard int := 0;
|
|
69
|
+
begin
|
|
70
|
+
loop
|
|
71
|
+
select parent_id into v_parent from contacts where id = v_id;
|
|
72
|
+
exit when v_parent is null;
|
|
73
|
+
v_id := v_parent;
|
|
74
|
+
v_guard := v_guard + 1;
|
|
75
|
+
if v_guard > 32 then
|
|
76
|
+
raise exception 'contact_parent_cycle: contact % has a cyclic or too deep parent chain', p_contact_id;
|
|
77
|
+
end if;
|
|
78
|
+
end loop;
|
|
79
|
+
return v_id;
|
|
80
|
+
end;
|
|
81
|
+
$$;
|
|
82
|
+
|
|
83
|
+
comment on function commercial_entity(uuid) is 'Root of the contact parent chain; the entity a document is booked against.';
|
|
84
|
+
|
|
85
|
+
alter table contacts enable row level security;
|
|
86
|
+
|
|
87
|
+
create policy contacts_select on contacts
|
|
88
|
+
for select using (is_company_member(company_id));
|
|
89
|
+
create policy contacts_write on contacts
|
|
90
|
+
for all using (can_write_company(company_id)) with check (can_write_company(company_id));
|