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,143 @@
|
|
|
1
|
+
-- Ekwo OS — a country template that wires the financial journals too.
|
|
2
|
+
--
|
|
3
|
+
-- `country_defaults.bank_account_code` was declared from the first release and
|
|
4
|
+
-- read by nothing: `install_country_template` wired the receivable, payable,
|
|
5
|
+
-- suspense, rounding and retained-earnings accounts and the three journals of
|
|
6
|
+
-- the company, and left `journals.default_account_id` null on every one of
|
|
7
|
+
-- them. A country model whose financial journal points at no account is not a
|
|
8
|
+
-- model — the demo seed had to wire it by hand, which was the symptom.
|
|
9
|
+
--
|
|
10
|
+
-- It surfaced on the first payment booked through a client: `post_payment()`
|
|
11
|
+
-- looks for the bank side on the payment's bank account, then on the default
|
|
12
|
+
-- account of its journal, and found neither on a freshly installed company.
|
|
13
|
+
-- The refusal, `no_bank_account`, was correct and the configuration was what
|
|
14
|
+
-- was wrong.
|
|
15
|
+
--
|
|
16
|
+
-- So the bank journal and the cash journal get their account from the country
|
|
17
|
+
-- model, like everything else. `cash_account_code` is added because the cash
|
|
18
|
+
-- journal needs its own — `570000` in the PCMN, `530000` in the PCG — and
|
|
19
|
+
-- these two codes do not derive from one another any more than `550000` and
|
|
20
|
+
-- `512000` do.
|
|
21
|
+
--
|
|
22
|
+
-- Only new columns and a `create or replace`: nothing already published is
|
|
23
|
+
-- edited, and a company that already has a default account keeps it.
|
|
24
|
+
|
|
25
|
+
alter table country_defaults add column if not exists cash_account_code text;
|
|
26
|
+
|
|
27
|
+
comment on column country_defaults.cash_account_code is
|
|
28
|
+
'Ledger account behind the cash journal of this country, from the pack of that country.';
|
|
29
|
+
|
|
30
|
+
-- No backfill here. The value of this column is pack data, and the compiled
|
|
31
|
+
-- seed of every country upserts `country_defaults` — so re-applying the seeds
|
|
32
|
+
-- fills it, on a fresh install and on an existing one alike. A migration that
|
|
33
|
+
-- wrote it would have to name a country and a code, and a country is data.
|
|
34
|
+
|
|
35
|
+
create or replace function install_country_template(
|
|
36
|
+
p_company_id uuid,
|
|
37
|
+
p_country char(2)
|
|
38
|
+
)
|
|
39
|
+
returns void
|
|
40
|
+
language plpgsql
|
|
41
|
+
as $$
|
|
42
|
+
declare
|
|
43
|
+
v_defaults country_defaults%rowtype;
|
|
44
|
+
r record;
|
|
45
|
+
v_tax_id uuid;
|
|
46
|
+
begin
|
|
47
|
+
if not exists (select 1 from companies where id = p_company_id) then
|
|
48
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
49
|
+
end if;
|
|
50
|
+
if not exists (select 1 from account_templates where country = p_country) then
|
|
51
|
+
raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
|
|
52
|
+
end if;
|
|
53
|
+
|
|
54
|
+
-- 1. Accounts, without the hierarchy.
|
|
55
|
+
insert into accounts (company_id, code, name, account_type, reconcilable)
|
|
56
|
+
select p_company_id, t.code, t.name, t.account_type, t.reconcilable
|
|
57
|
+
from account_templates t
|
|
58
|
+
where t.country = p_country
|
|
59
|
+
on conflict (company_id, code) do nothing;
|
|
60
|
+
|
|
61
|
+
-- 2. Hierarchy, now that every code exists.
|
|
62
|
+
update accounts a
|
|
63
|
+
set parent_id = p.id
|
|
64
|
+
from account_templates t
|
|
65
|
+
join accounts p on p.company_id = p_company_id and p.code = t.parent_code
|
|
66
|
+
where a.company_id = p_company_id
|
|
67
|
+
and a.code = t.code
|
|
68
|
+
and t.country = p_country
|
|
69
|
+
and t.parent_code is not null
|
|
70
|
+
and a.parent_id is null;
|
|
71
|
+
|
|
72
|
+
-- 3. Journals.
|
|
73
|
+
insert into journals (company_id, code, name, journal_type)
|
|
74
|
+
select p_company_id, t.code, t.name, t.journal_type
|
|
75
|
+
from journal_templates t
|
|
76
|
+
where t.country = p_country
|
|
77
|
+
on conflict (company_id, code) do nothing;
|
|
78
|
+
|
|
79
|
+
-- 4. Taxes and their postings.
|
|
80
|
+
for r in
|
|
81
|
+
select * from tax_templates where country = p_country order by sequence, code
|
|
82
|
+
loop
|
|
83
|
+
v_tax_id := null;
|
|
84
|
+
insert into taxes (company_id, code, name, description, amount_type, amount,
|
|
85
|
+
applies_to, treatment, country, valid_from, valid_to,
|
|
86
|
+
legal_reference, vat_category, exemption_code, sequence)
|
|
87
|
+
values (p_company_id, r.code, r.name, r.description, r.amount_type, r.amount,
|
|
88
|
+
r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
|
|
89
|
+
r.legal_reference, r.vat_category, r.exemption_code, r.sequence)
|
|
90
|
+
on conflict (company_id, code) do nothing
|
|
91
|
+
returning id into v_tax_id;
|
|
92
|
+
|
|
93
|
+
if v_tax_id is null then
|
|
94
|
+
continue;
|
|
95
|
+
end if;
|
|
96
|
+
|
|
97
|
+
insert into tax_postings (tax_id, company_id, document_kind, posting_type,
|
|
98
|
+
factor_percent, account_id, declaration_box,
|
|
99
|
+
box_factor_percent, sequence)
|
|
100
|
+
select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
|
|
101
|
+
tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
|
|
102
|
+
tp.declaration_box, tp.box_factor_percent, tp.sequence
|
|
103
|
+
from tax_posting_templates tp
|
|
104
|
+
where tp.tax_template_id = r.id
|
|
105
|
+
order by tp.sequence;
|
|
106
|
+
end loop;
|
|
107
|
+
|
|
108
|
+
-- 5. Roles.
|
|
109
|
+
select * into v_defaults from country_defaults where country = p_country;
|
|
110
|
+
if found then
|
|
111
|
+
update companies c
|
|
112
|
+
set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
|
|
113
|
+
payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
|
|
114
|
+
suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
|
|
115
|
+
rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
|
|
116
|
+
retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
|
|
117
|
+
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)),
|
|
118
|
+
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)),
|
|
119
|
+
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))
|
|
120
|
+
where c.id = p_company_id;
|
|
121
|
+
|
|
122
|
+
-- 6. The financial journals point at their account, so money booked
|
|
123
|
+
-- through them lands somewhere without the operator wiring it first.
|
|
124
|
+
-- `default_account_id is null` keeps a company that already chose one.
|
|
125
|
+
update journals j
|
|
126
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
|
|
127
|
+
where j.company_id = p_company_id
|
|
128
|
+
and j.journal_type = 'bank'
|
|
129
|
+
and j.default_account_id is null
|
|
130
|
+
and v_defaults.bank_account_code is not null;
|
|
131
|
+
|
|
132
|
+
update journals j
|
|
133
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
|
|
134
|
+
where j.company_id = p_company_id
|
|
135
|
+
and j.journal_type = 'cash'
|
|
136
|
+
and j.default_account_id is null
|
|
137
|
+
and v_defaults.cash_account_code is not null;
|
|
138
|
+
end if;
|
|
139
|
+
end;
|
|
140
|
+
$$;
|
|
141
|
+
|
|
142
|
+
comment on function install_country_template(uuid, char) is
|
|
143
|
+
'Copies a country chart of accounts, journals and taxes into a company, wires the default roles and points the financial journals at their account.';
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
-- Ekwo OS — the account a document line falls back to.
|
|
2
|
+
--
|
|
3
|
+
-- `country_defaults` shipped with three columns nothing ever read:
|
|
4
|
+
-- `sales_account_code`, `purchase_account_code` and `currency_code`. Keeping
|
|
5
|
+
-- a column "in case" is the state the naming policy forbids, so the choice
|
|
6
|
+
-- was between deleting them and giving them a reader. They get a reader,
|
|
7
|
+
-- because the thing they describe is real: every chart of accounts has one
|
|
8
|
+
-- account a sale lands on by default and one a purchase lands on, and
|
|
9
|
+
-- somebody has to suggest it. `currency_code` gets its reader in the CLI —
|
|
10
|
+
-- `ekwo init` offers it as the currency of the company, before the insert,
|
|
11
|
+
-- because `companies.currency_code` is `not null default 'EUR'` and is
|
|
12
|
+
-- therefore never empty by the time `install_country_template` runs.
|
|
13
|
+
--
|
|
14
|
+
-- The order of resolution for the account of a document line, from the most
|
|
15
|
+
-- specific to the least:
|
|
16
|
+
--
|
|
17
|
+
-- 1. the line itself — `document_lines.account_id`;
|
|
18
|
+
-- 2. the product, once `products` exists (added by a later migration, which
|
|
19
|
+
-- replaces the trigger body to consult it);
|
|
20
|
+
-- 3. the company default — `companies.default_sales_account_id` /
|
|
21
|
+
-- `default_purchase_account_id`, new here;
|
|
22
|
+
-- 4. the country model — `country_defaults.sales_account_code` /
|
|
23
|
+
-- `purchase_account_code`, resolved against the company's own chart.
|
|
24
|
+
--
|
|
25
|
+
-- Nothing is guessed from a code prefix, which is the rule the rest of the
|
|
26
|
+
-- schema already keeps: `411` is *customers* on the French chart and
|
|
27
|
+
-- *recoverable VAT* on the Belgian one.
|
|
28
|
+
--
|
|
29
|
+
-- The resolution runs in a trigger rather than in each client. Lines are
|
|
30
|
+
-- created by whoever holds a token — the MCP server, PostgREST, psql — and
|
|
31
|
+
-- `document_lines_product_has_account` refuses a product line with no
|
|
32
|
+
-- account, so a null account can only ever mean "resolve it". A null *tax*
|
|
33
|
+
-- is a different matter: it means no tax at all, which is a real answer, so
|
|
34
|
+
-- nothing here fills it in.
|
|
35
|
+
|
|
36
|
+
alter table companies
|
|
37
|
+
add column if not exists default_sales_account_id uuid,
|
|
38
|
+
add column if not exists default_purchase_account_id uuid;
|
|
39
|
+
|
|
40
|
+
alter table companies
|
|
41
|
+
add constraint companies_default_sales_account_fkey
|
|
42
|
+
foreign key (default_sales_account_id) references accounts(id) on delete set null,
|
|
43
|
+
add constraint companies_default_purchase_account_fkey
|
|
44
|
+
foreign key (default_purchase_account_id) references accounts(id) on delete set null;
|
|
45
|
+
|
|
46
|
+
comment on column companies.default_sales_account_id is
|
|
47
|
+
'Income account a sales line falls back to when it names none. Wired from country_defaults.sales_account_code at install.';
|
|
48
|
+
comment on column companies.default_purchase_account_id is
|
|
49
|
+
'Expense account a purchase line falls back to when it names none. Wired from country_defaults.purchase_account_code at install.';
|
|
50
|
+
|
|
51
|
+
-- ---------------------------------------------------------------------------
|
|
52
|
+
-- resolve_line_account
|
|
53
|
+
--
|
|
54
|
+
-- `stable` and not `security definer`: it reads `companies` and
|
|
55
|
+
-- `country_defaults`, both of which the caller may read anyway — a member of
|
|
56
|
+
-- the company for the first, anyone signed in for the second. A definer here
|
|
57
|
+
-- would widen nothing and hide the policy that already decides.
|
|
58
|
+
-- ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
create or replace function resolve_line_account(
|
|
61
|
+
p_company_id uuid,
|
|
62
|
+
p_doc_type doc_type,
|
|
63
|
+
p_account_id uuid
|
|
64
|
+
)
|
|
65
|
+
returns uuid
|
|
66
|
+
language plpgsql
|
|
67
|
+
stable
|
|
68
|
+
as $$
|
|
69
|
+
declare
|
|
70
|
+
v_is_sale boolean;
|
|
71
|
+
v_account uuid;
|
|
72
|
+
v_country char(2);
|
|
73
|
+
v_code text;
|
|
74
|
+
begin
|
|
75
|
+
if p_account_id is not null then
|
|
76
|
+
return p_account_id;
|
|
77
|
+
end if;
|
|
78
|
+
|
|
79
|
+
v_is_sale := p_doc_type in ('sale_invoice', 'sale_credit_note', 'sale_quote');
|
|
80
|
+
|
|
81
|
+
select case when v_is_sale then c.default_sales_account_id
|
|
82
|
+
else c.default_purchase_account_id end,
|
|
83
|
+
c.country
|
|
84
|
+
into v_account, v_country
|
|
85
|
+
from companies c
|
|
86
|
+
where c.id = p_company_id;
|
|
87
|
+
|
|
88
|
+
if v_account is not null then
|
|
89
|
+
return v_account;
|
|
90
|
+
end if;
|
|
91
|
+
|
|
92
|
+
select case when v_is_sale then d.sales_account_code else d.purchase_account_code end
|
|
93
|
+
into v_code
|
|
94
|
+
from country_defaults d
|
|
95
|
+
where d.country = v_country;
|
|
96
|
+
|
|
97
|
+
if v_code is null then
|
|
98
|
+
return null;
|
|
99
|
+
end if;
|
|
100
|
+
|
|
101
|
+
return account_id_by_code(p_company_id, v_code);
|
|
102
|
+
end;
|
|
103
|
+
$$;
|
|
104
|
+
|
|
105
|
+
comment on function resolve_line_account(uuid, doc_type, uuid) is
|
|
106
|
+
'Account of a document line: the line, then the company default, then the country model. Never a code prefix.';
|
|
107
|
+
|
|
108
|
+
-- ---------------------------------------------------------------------------
|
|
109
|
+
-- The trigger
|
|
110
|
+
--
|
|
111
|
+
-- One path for every client. A line that names its account keeps it; a line
|
|
112
|
+
-- that names none gets the default, and when there is no default anywhere the
|
|
113
|
+
-- existing check constraint raises, which is the honest outcome.
|
|
114
|
+
-- ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
create or replace function document_lines_resolve_account()
|
|
117
|
+
returns trigger
|
|
118
|
+
language plpgsql
|
|
119
|
+
as $$
|
|
120
|
+
declare
|
|
121
|
+
v_doc_type doc_type;
|
|
122
|
+
begin
|
|
123
|
+
if new.line_type <> 'product' or new.account_id is not null then
|
|
124
|
+
return new;
|
|
125
|
+
end if;
|
|
126
|
+
|
|
127
|
+
select d.doc_type into v_doc_type from documents d where d.id = new.document_id;
|
|
128
|
+
if v_doc_type is null then
|
|
129
|
+
return new;
|
|
130
|
+
end if;
|
|
131
|
+
|
|
132
|
+
new.account_id := resolve_line_account(new.company_id, v_doc_type, null);
|
|
133
|
+
return new;
|
|
134
|
+
end;
|
|
135
|
+
$$;
|
|
136
|
+
|
|
137
|
+
create trigger document_lines_resolve_account
|
|
138
|
+
before insert or update on document_lines
|
|
139
|
+
for each row execute function document_lines_resolve_account();
|
|
140
|
+
|
|
141
|
+
-- ---------------------------------------------------------------------------
|
|
142
|
+
-- The country model now wires the two company defaults as well.
|
|
143
|
+
--
|
|
144
|
+
-- Only step 5 changes; the rest of the function is the one migration
|
|
145
|
+
-- `…183000` published, repeated because Postgres replaces a function whole.
|
|
146
|
+
-- A company that already chose an account keeps it, as everywhere else here.
|
|
147
|
+
-- ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
create or replace function install_country_template(
|
|
150
|
+
p_company_id uuid,
|
|
151
|
+
p_country char(2)
|
|
152
|
+
)
|
|
153
|
+
returns void
|
|
154
|
+
language plpgsql
|
|
155
|
+
as $$
|
|
156
|
+
declare
|
|
157
|
+
v_defaults country_defaults%rowtype;
|
|
158
|
+
r record;
|
|
159
|
+
v_tax_id uuid;
|
|
160
|
+
begin
|
|
161
|
+
if not exists (select 1 from companies where id = p_company_id) then
|
|
162
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
163
|
+
end if;
|
|
164
|
+
if not exists (select 1 from account_templates where country = p_country) then
|
|
165
|
+
raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
|
|
166
|
+
end if;
|
|
167
|
+
|
|
168
|
+
-- 1. Accounts, without the hierarchy.
|
|
169
|
+
insert into accounts (company_id, code, name, account_type, reconcilable)
|
|
170
|
+
select p_company_id, t.code, t.name, t.account_type, t.reconcilable
|
|
171
|
+
from account_templates t
|
|
172
|
+
where t.country = p_country
|
|
173
|
+
on conflict (company_id, code) do nothing;
|
|
174
|
+
|
|
175
|
+
-- 2. Hierarchy, now that every code exists.
|
|
176
|
+
update accounts a
|
|
177
|
+
set parent_id = p.id
|
|
178
|
+
from account_templates t
|
|
179
|
+
join accounts p on p.company_id = p_company_id and p.code = t.parent_code
|
|
180
|
+
where a.company_id = p_company_id
|
|
181
|
+
and a.code = t.code
|
|
182
|
+
and t.country = p_country
|
|
183
|
+
and t.parent_code is not null
|
|
184
|
+
and a.parent_id is null;
|
|
185
|
+
|
|
186
|
+
-- 3. Journals.
|
|
187
|
+
insert into journals (company_id, code, name, journal_type)
|
|
188
|
+
select p_company_id, t.code, t.name, t.journal_type
|
|
189
|
+
from journal_templates t
|
|
190
|
+
where t.country = p_country
|
|
191
|
+
on conflict (company_id, code) do nothing;
|
|
192
|
+
|
|
193
|
+
-- 4. Taxes and their postings.
|
|
194
|
+
for r in
|
|
195
|
+
select * from tax_templates where country = p_country order by sequence, code
|
|
196
|
+
loop
|
|
197
|
+
v_tax_id := null;
|
|
198
|
+
insert into taxes (company_id, code, name, description, amount_type, amount,
|
|
199
|
+
applies_to, treatment, country, valid_from, valid_to,
|
|
200
|
+
legal_reference, vat_category, exemption_code, sequence)
|
|
201
|
+
values (p_company_id, r.code, r.name, r.description, r.amount_type, r.amount,
|
|
202
|
+
r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
|
|
203
|
+
r.legal_reference, r.vat_category, r.exemption_code, r.sequence)
|
|
204
|
+
on conflict (company_id, code) do nothing
|
|
205
|
+
returning id into v_tax_id;
|
|
206
|
+
|
|
207
|
+
if v_tax_id is null then
|
|
208
|
+
continue;
|
|
209
|
+
end if;
|
|
210
|
+
|
|
211
|
+
insert into tax_postings (tax_id, company_id, document_kind, posting_type,
|
|
212
|
+
factor_percent, account_id, declaration_box,
|
|
213
|
+
box_factor_percent, sequence)
|
|
214
|
+
select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
|
|
215
|
+
tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
|
|
216
|
+
tp.declaration_box, tp.box_factor_percent, tp.sequence
|
|
217
|
+
from tax_posting_templates tp
|
|
218
|
+
where tp.tax_template_id = r.id
|
|
219
|
+
order by tp.sequence;
|
|
220
|
+
end loop;
|
|
221
|
+
|
|
222
|
+
-- 5. Roles.
|
|
223
|
+
select * into v_defaults from country_defaults where country = p_country;
|
|
224
|
+
if found then
|
|
225
|
+
update companies c
|
|
226
|
+
set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
|
|
227
|
+
payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
|
|
228
|
+
suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
|
|
229
|
+
rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
|
|
230
|
+
retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
|
|
231
|
+
default_sales_account_id = coalesce(c.default_sales_account_id, account_id_by_code(p_company_id, v_defaults.sales_account_code)),
|
|
232
|
+
default_purchase_account_id = coalesce(c.default_purchase_account_id, account_id_by_code(p_company_id, v_defaults.purchase_account_code)),
|
|
233
|
+
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)),
|
|
234
|
+
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)),
|
|
235
|
+
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))
|
|
236
|
+
where c.id = p_company_id;
|
|
237
|
+
|
|
238
|
+
-- 6. The financial journals point at their account, so money booked
|
|
239
|
+
-- through them lands somewhere without the operator wiring it first.
|
|
240
|
+
update journals j
|
|
241
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
|
|
242
|
+
where j.company_id = p_company_id
|
|
243
|
+
and j.journal_type = 'bank'
|
|
244
|
+
and j.default_account_id is null
|
|
245
|
+
and v_defaults.bank_account_code is not null;
|
|
246
|
+
|
|
247
|
+
update journals j
|
|
248
|
+
set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
|
|
249
|
+
where j.company_id = p_company_id
|
|
250
|
+
and j.journal_type = 'cash'
|
|
251
|
+
and j.default_account_id is null
|
|
252
|
+
and v_defaults.cash_account_code is not null;
|
|
253
|
+
end if;
|
|
254
|
+
end;
|
|
255
|
+
$$;
|
|
256
|
+
|
|
257
|
+
comment on function install_country_template(uuid, char) is
|
|
258
|
+
'Copies a country chart of accounts, journals and taxes into a company, wires the default roles — third parties, sales and purchase imputation, financial journals.';
|
|
259
|
+
|
|
260
|
+
-- Backfill for a company installed before this migration: it already has the
|
|
261
|
+
-- chart, so the two defaults can be filled from its own country model.
|
|
262
|
+
update companies c
|
|
263
|
+
set default_sales_account_id = account_id_by_code(c.id, d.sales_account_code)
|
|
264
|
+
from country_defaults d
|
|
265
|
+
where d.country = c.country
|
|
266
|
+
and c.default_sales_account_id is null
|
|
267
|
+
and d.sales_account_code is not null;
|
|
268
|
+
|
|
269
|
+
update companies c
|
|
270
|
+
set default_purchase_account_id = account_id_by_code(c.id, d.purchase_account_code)
|
|
271
|
+
from country_defaults d
|
|
272
|
+
where d.country = c.country
|
|
273
|
+
and c.default_purchase_account_id is null
|
|
274
|
+
and d.purchase_account_code is not null;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
-- Ekwo OS — products, in the core rather than in a module beside it.
|
|
2
|
+
--
|
|
3
|
+
-- A product here is what a line is filled in from: a code, a name, a
|
|
4
|
+
-- description, a unit, a price, the account it books to and the tax it
|
|
5
|
+
-- carries. It is deliberately not an article of stock — no quantity on hand,
|
|
6
|
+
-- no valuation, no movements — because that is a different piece of software
|
|
7
|
+
-- and merging the two is how a catalogue ends up owning the ledger.
|
|
8
|
+
--
|
|
9
|
+
-- Two rules it keeps, and they are the whole design:
|
|
10
|
+
--
|
|
11
|
+
-- * **A product pre-fills a line; it never constrains one.** The line keeps
|
|
12
|
+
-- its own text, its own price and its own account, and anything typed
|
|
13
|
+
-- over the product's suggestion stays. An invoice is a statement about
|
|
14
|
+
-- what was sold on that day, and a catalogue edited next month must not
|
|
15
|
+
-- be able to change it.
|
|
16
|
+
-- * **A line with no product is still a line.** `product_id` is nullable
|
|
17
|
+
-- and always will be. Free text is how most invoices are written, and a
|
|
18
|
+
-- schema that forces a catalogue row for every line forces the operator
|
|
19
|
+
-- to invent one.
|
|
20
|
+
--
|
|
21
|
+
-- The unit lives where it already lived: `document_lines.unit_code`, UN/ECE
|
|
22
|
+
-- recommendation 20, present since the first release and mapped to BT-130. A
|
|
23
|
+
-- second `unit` column next to it would be two answers to one question, so
|
|
24
|
+
-- the product carries `unit_code` too and fills that one in.
|
|
25
|
+
|
|
26
|
+
create type product_kind as enum ('service', 'goods');
|
|
27
|
+
|
|
28
|
+
comment on type product_kind is
|
|
29
|
+
'What is being sold. Services and goods differ in VAT treatment, in the declaration boxes they feed and in the account they book to.';
|
|
30
|
+
|
|
31
|
+
create table products (
|
|
32
|
+
id uuid primary key default gen_random_uuid(),
|
|
33
|
+
company_id uuid not null references companies(id) on delete cascade,
|
|
34
|
+
-- BT-155, the seller's own identifier for the item.
|
|
35
|
+
code text not null,
|
|
36
|
+
-- BT-153 when the line takes it.
|
|
37
|
+
name text not null,
|
|
38
|
+
-- BT-154.
|
|
39
|
+
description text,
|
|
40
|
+
kind product_kind not null default 'service',
|
|
41
|
+
-- UN/ECE recommendation 20, BT-130. `C62` is "one", `HUR` an hour, `DAY` a
|
|
42
|
+
-- day, `KGM` a kilogram, `LTR` a litre, `MTR` a metre. The check is on the
|
|
43
|
+
-- shape only: the recommendation has some eighteen hundred codes and a
|
|
44
|
+
-- short list in the database would refuse legitimate ones.
|
|
45
|
+
unit_code text not null default 'C62',
|
|
46
|
+
currency_code char(3) not null default 'EUR' references currencies(code),
|
|
47
|
+
-- Six decimals, like `document_lines.unit_price`: a unit price is not an
|
|
48
|
+
-- amount of money, it is what an amount is computed from.
|
|
49
|
+
sale_price numeric(16, 6),
|
|
50
|
+
purchase_price numeric(16, 6),
|
|
51
|
+
sale_account_id uuid references accounts(id) on delete restrict,
|
|
52
|
+
purchase_account_id uuid references accounts(id) on delete restrict,
|
|
53
|
+
sale_tax_id uuid references taxes(id) on delete restrict,
|
|
54
|
+
purchase_tax_id uuid references taxes(id) on delete restrict,
|
|
55
|
+
active boolean not null default true,
|
|
56
|
+
created_at timestamptz not null default now(),
|
|
57
|
+
updated_at timestamptz not null default now(),
|
|
58
|
+
unique (company_id, code),
|
|
59
|
+
constraint products_code_not_blank check (length(btrim(code)) > 0),
|
|
60
|
+
constraint products_unit_code_format check (unit_code ~ '^[A-Z0-9]{1,3}$'),
|
|
61
|
+
constraint products_sale_price_positive check (sale_price is null or sale_price >= 0),
|
|
62
|
+
constraint products_purchase_price_positive check (purchase_price is null or purchase_price >= 0),
|
|
63
|
+
foreign key (sale_account_id, company_id) references accounts(id, company_id),
|
|
64
|
+
foreign key (purchase_account_id, company_id) references accounts(id, company_id),
|
|
65
|
+
foreign key (sale_tax_id, company_id) references taxes(id, company_id),
|
|
66
|
+
foreign key (purchase_tax_id, company_id) references taxes(id, company_id)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
comment on table products is
|
|
70
|
+
'What a document line is filled in from: code, name, unit, price, account and tax. Not stock: no quantity on hand and no valuation.';
|
|
71
|
+
comment on column products.code is 'The seller''s own item identifier, EN 16931 BT-155. Unique in the company.';
|
|
72
|
+
comment on column products.name is 'Item name, EN 16931 BT-153, copied onto the line it fills in.';
|
|
73
|
+
comment on column products.description is 'Item description, EN 16931 BT-154.';
|
|
74
|
+
comment on column products.unit_code is 'Unit of measure, UN/ECE recommendation 20 (BT-130). C62 is "one".';
|
|
75
|
+
comment on column products.sale_price is 'Suggested net unit price on a sale. A line may carry another.';
|
|
76
|
+
|
|
77
|
+
create unique index products_id_company_idx on products (id, company_id);
|
|
78
|
+
create index products_company_active_idx on products (company_id, active);
|
|
79
|
+
create index products_company_name_idx on products (company_id, lower(name));
|
|
80
|
+
|
|
81
|
+
create trigger products_set_updated_at
|
|
82
|
+
before update on products
|
|
83
|
+
for each row execute function set_updated_at();
|
|
84
|
+
|
|
85
|
+
-- ---------------------------------------------------------------------------
|
|
86
|
+
-- The line points at the product, and keeps everything it was given.
|
|
87
|
+
-- ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
alter table document_lines
|
|
90
|
+
add column if not exists product_id uuid,
|
|
91
|
+
add column if not exists description text;
|
|
92
|
+
|
|
93
|
+
alter table document_lines
|
|
94
|
+
add constraint document_lines_product_fkey
|
|
95
|
+
foreign key (product_id) references products(id) on delete restrict,
|
|
96
|
+
add constraint document_lines_product_company_fkey
|
|
97
|
+
foreign key (product_id, company_id) references products(id, company_id);
|
|
98
|
+
|
|
99
|
+
comment on column document_lines.product_id is
|
|
100
|
+
'The catalogue row this line was filled in from, when there was one. Nullable for ever: free text is how most invoices are written.';
|
|
101
|
+
comment on column document_lines.description is
|
|
102
|
+
'Item description, EN 16931 BT-154. `name` is BT-153.';
|
|
103
|
+
|
|
104
|
+
create index document_lines_product_idx on document_lines (product_id);
|
|
105
|
+
|
|
106
|
+
-- `on delete restrict` rather than `set null`: a product referenced by a
|
|
107
|
+
-- posted invoice is part of what that invoice said, and deleting it out from
|
|
108
|
+
-- under the line would quietly rewrite a document. Retiring one is `active`.
|
|
109
|
+
|
|
110
|
+
-- ---------------------------------------------------------------------------
|
|
111
|
+
-- Row level security, in the same migration as the table.
|
|
112
|
+
-- ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
alter table products enable row level security;
|
|
115
|
+
|
|
116
|
+
create policy products_select on products
|
|
117
|
+
for select using (is_company_member(company_id));
|
|
118
|
+
create policy products_write on products
|
|
119
|
+
for all using (can_write_company(company_id)) with check (can_write_company(company_id));
|
|
120
|
+
|
|
121
|
+
-- ---------------------------------------------------------------------------
|
|
122
|
+
-- The product joins the resolution of the line's account.
|
|
123
|
+
--
|
|
124
|
+
-- Order, most specific first: the line, the product, the company default, the
|
|
125
|
+
-- country model. The three-argument form published in `…193853` is kept and
|
|
126
|
+
-- called by this one, so there is one place where the last two steps live.
|
|
127
|
+
-- ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
create or replace function resolve_line_account(
|
|
130
|
+
p_company_id uuid,
|
|
131
|
+
p_doc_type doc_type,
|
|
132
|
+
p_product_id uuid,
|
|
133
|
+
p_account_id uuid
|
|
134
|
+
)
|
|
135
|
+
returns uuid
|
|
136
|
+
language plpgsql
|
|
137
|
+
stable
|
|
138
|
+
as $$
|
|
139
|
+
declare
|
|
140
|
+
v_is_sale boolean;
|
|
141
|
+
v_account uuid;
|
|
142
|
+
begin
|
|
143
|
+
if p_account_id is not null then
|
|
144
|
+
return p_account_id;
|
|
145
|
+
end if;
|
|
146
|
+
|
|
147
|
+
if p_product_id is not null then
|
|
148
|
+
v_is_sale := p_doc_type in ('sale_invoice', 'sale_credit_note', 'sale_quote');
|
|
149
|
+
select case when v_is_sale then p.sale_account_id else p.purchase_account_id end
|
|
150
|
+
into v_account
|
|
151
|
+
from products p
|
|
152
|
+
where p.id = p_product_id;
|
|
153
|
+
if v_account is not null then
|
|
154
|
+
return v_account;
|
|
155
|
+
end if;
|
|
156
|
+
end if;
|
|
157
|
+
|
|
158
|
+
return resolve_line_account(p_company_id, p_doc_type, null::uuid);
|
|
159
|
+
end;
|
|
160
|
+
$$;
|
|
161
|
+
|
|
162
|
+
comment on function resolve_line_account(uuid, doc_type, uuid, uuid) is
|
|
163
|
+
'Account of a document line: the line, the product, the company default, the country model. Never a code prefix.';
|
|
164
|
+
|
|
165
|
+
create or replace function document_lines_resolve_account()
|
|
166
|
+
returns trigger
|
|
167
|
+
language plpgsql
|
|
168
|
+
as $$
|
|
169
|
+
declare
|
|
170
|
+
v_doc_type doc_type;
|
|
171
|
+
begin
|
|
172
|
+
if new.line_type <> 'product' or new.account_id is not null then
|
|
173
|
+
return new;
|
|
174
|
+
end if;
|
|
175
|
+
|
|
176
|
+
select d.doc_type into v_doc_type from documents d where d.id = new.document_id;
|
|
177
|
+
if v_doc_type is null then
|
|
178
|
+
return new;
|
|
179
|
+
end if;
|
|
180
|
+
|
|
181
|
+
new.account_id := resolve_line_account(new.company_id, v_doc_type, new.product_id, null::uuid);
|
|
182
|
+
return new;
|
|
183
|
+
end;
|
|
184
|
+
$$;
|
|
185
|
+
|
|
186
|
+
-- ---------------------------------------------------------------------------
|
|
187
|
+
-- The invoice line as EN 16931 describes it.
|
|
188
|
+
--
|
|
189
|
+
-- BT-153 is the item name, BT-154 its description, BT-155 the seller's own
|
|
190
|
+
-- identifier — which is `products.code` and lives nowhere else, so a line
|
|
191
|
+
-- that names no product has none, and that is the correct answer rather than
|
|
192
|
+
-- a gap. `security_invoker` so the view is read under the policies of
|
|
193
|
+
-- `document_lines`, exactly like `document_tax_summary`.
|
|
194
|
+
-- ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
create or replace view document_line_items
|
|
197
|
+
with (security_invoker = true) as
|
|
198
|
+
select l.id as document_line_id,
|
|
199
|
+
l.document_id,
|
|
200
|
+
l.company_id,
|
|
201
|
+
l.sequence,
|
|
202
|
+
l.line_type,
|
|
203
|
+
l.name as item_name, -- BT-153
|
|
204
|
+
l.description as item_description, -- BT-154
|
|
205
|
+
p.code as seller_item_identifier, -- BT-155
|
|
206
|
+
l.product_id,
|
|
207
|
+
p.kind as product_kind,
|
|
208
|
+
l.quantity, -- BT-129
|
|
209
|
+
l.unit_code, -- BT-130
|
|
210
|
+
l.unit_price, -- BT-146
|
|
211
|
+
l.discount_percent,
|
|
212
|
+
l.amount_untaxed, -- BT-131
|
|
213
|
+
l.tax_id,
|
|
214
|
+
l.vat_category, -- BT-151
|
|
215
|
+
l.vat_rate, -- BT-152
|
|
216
|
+
l.account_id
|
|
217
|
+
from document_lines l
|
|
218
|
+
left join products p on p.id = l.product_id;
|
|
219
|
+
|
|
220
|
+
comment on view document_line_items is
|
|
221
|
+
'Document lines with the EN 16931 item terms: BT-153 name, BT-154 description, BT-155 the seller identifier, which is the product code.';
|