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,437 @@
|
|
|
1
|
+
-- Ekwo OS — what produced an entry stops moving when the entry exists.
|
|
2
|
+
--
|
|
3
|
+
-- A document is posted once: `post_document()` reads its lines, builds the
|
|
4
|
+
-- entry, and refuses to do it twice. From then on the ledger holds the figures
|
|
5
|
+
-- of that day — and the document did not. Nothing guarded it. Established
|
|
6
|
+
-- under row level security, as a member holding `documents.write` and nothing
|
|
7
|
+
-- more, on a posted invoice in an open period:
|
|
8
|
+
--
|
|
9
|
+
-- * every column of `document_lines` could be rewritten — quantity, price,
|
|
10
|
+
-- discount, tax, account, name — a line could be added and a line could be
|
|
11
|
+
-- deleted, and the totals of the document followed: an invoice posted at
|
|
12
|
+
-- 150 read 750, 1 149 or 50 a statement later, against a ledger still at
|
|
13
|
+
-- 150;
|
|
14
|
+
-- * every column of `documents` too: the three totals, the number, the three
|
|
15
|
+
-- dates, the customer, the currency and its rate, the type, the journal;
|
|
16
|
+
-- `entry_id` could be nulled, which unhooks the document from its entry;
|
|
17
|
+
-- `state` could go back to `draft`, after which `post_document()` books it
|
|
18
|
+
-- a second time; it could be set to `cancelled`, which reverses nothing;
|
|
19
|
+
-- and the row could be deleted, leaving its entry with no document;
|
|
20
|
+
-- * what was refused: the language (`document_language_frozen`), the
|
|
21
|
+
-- category and the rate of a line (`document_line_tax_frozen`, a day old),
|
|
22
|
+
-- and a second `post_document()`. The period locks guard `entries` and
|
|
23
|
+
-- `entry_lines` and say nothing of a document.
|
|
24
|
+
--
|
|
25
|
+
-- So an issued invoice was immutable by convention. This file makes it so by
|
|
26
|
+
-- the database, in the shape `tax_filing_boxes_are_frozen` has: a trigger that
|
|
27
|
+
-- asks one question — has the state left `draft`? — and refuses by name.
|
|
28
|
+
--
|
|
29
|
+
-- **The rule is a closed list of what may still move, not a list of what may
|
|
30
|
+
-- not.** A column added next month is frozen the day it is added, without
|
|
31
|
+
-- anybody remembering this file. What may move on a document that is no longer
|
|
32
|
+
-- a draft:
|
|
33
|
+
--
|
|
34
|
+
-- * `amount_paid`, and only to the figure the matching gives — so the guard
|
|
35
|
+
-- judges the value and not the path: `reconcile()`, `unreconcile()` and
|
|
36
|
+
-- whatever settles a document tomorrow all pass, and a figure keyed by
|
|
37
|
+
-- hand does not. `document_amount_paid()` is that figure, taken out of
|
|
38
|
+
-- `documents_refresh_amount_paid()` so that the two cannot disagree.
|
|
39
|
+
-- `payment_state` and `amount_residual` follow from it and are never
|
|
40
|
+
-- written: the first by trigger, the second by the column's own
|
|
41
|
+
-- definition;
|
|
42
|
+
-- * `sent_at`, `peppol_status`, `peppol_message_id`: what happened to the
|
|
43
|
+
-- document *after* it was issued, which by definition is written after;
|
|
44
|
+
-- * `updated_at`.
|
|
45
|
+
--
|
|
46
|
+
-- Nothing else, and in particular not `due_date` (it is BT-9 on the invoice
|
|
47
|
+
-- and the maturity of the receivable in the ledger), not `note` (BT-22 is
|
|
48
|
+
-- printed; there is no internal note on this table, and the day there is one
|
|
49
|
+
-- it joins the list by name), not `reversed_document_id` (a credit note names
|
|
50
|
+
-- what it credits while it is a draft). Attachments and share links are rows
|
|
51
|
+
-- of other tables and are not touched: filing a scan under an invoice, or
|
|
52
|
+
-- sending its link, changes nothing the customer was issued.
|
|
53
|
+
--
|
|
54
|
+
-- **The state goes one way.** `draft` to `posted` is the transition
|
|
55
|
+
-- `post_document()` makes, in one statement, and it passes untouched because
|
|
56
|
+
-- the row it updates is still a draft; the guard adds only that a document
|
|
57
|
+
-- does not become posted without an entry that is itself posted. `draft` to `cancelled` is a draft
|
|
58
|
+
-- abandoned. Out of `posted` there is no way: not back to `draft`, and not to
|
|
59
|
+
-- `cancelled` either — no function of this schema cancels a posted document,
|
|
60
|
+
-- and an `update` that says `cancelled` reverses nothing in the ledger. A
|
|
61
|
+
-- posted invoice is corrected by a credit note: a `sale_credit_note` or
|
|
62
|
+
-- `purchase_credit_note` naming it in `reversed_document_id`, posted by the
|
|
63
|
+
-- same `post_document()`, whose entry is the mirror of the first. That path
|
|
64
|
+
-- exists, is tested below, and is the only one.
|
|
65
|
+
--
|
|
66
|
+
-- **Nobody is exempt, because nobody needs to be.** Nothing in this file asks
|
|
67
|
+
-- who the caller is. `post_document()` passes because of *when* it writes, the
|
|
68
|
+
-- matching because of *what* it writes, `taxes_reach_draft_lines` and
|
|
69
|
+
-- `contacts_language_reaches_drafts` because they only ever touched drafts.
|
|
70
|
+
-- One exemption is a fact rather than a privilege: when the *company* is being
|
|
71
|
+
-- deleted the cascade removes its documents, and a guard that refused would
|
|
72
|
+
-- make a company undeletable. The company's row is already gone when the
|
|
73
|
+
-- cascade arrives, which is what the guard looks at.
|
|
74
|
+
--
|
|
75
|
+
-- **Nobody is born posted either.** A document inserted with a state that is
|
|
76
|
+
-- not `draft`, or a line inserted under one, is refused to everybody —
|
|
77
|
+
-- `document_born_posted` — the installer and an administrator of the instance
|
|
78
|
+
-- included. Holding `documents.post` is the right to call `post_document()`,
|
|
79
|
+
-- not to write an invoice it never saw.
|
|
80
|
+
--
|
|
81
|
+
-- One thing does bring documents that are already posted: `import_company()`,
|
|
82
|
+
-- loading a company from an archive. An exemption for it was considered — the
|
|
83
|
+
-- insert, and the insert only, open to whoever may load a company — and then
|
|
84
|
+
-- withdrawn, because the import does not need one: it switches the triggers of
|
|
85
|
+
-- the tables it fills off, by name, inside its own transaction, behind its own
|
|
86
|
+
-- guard, and switches them back on. That is the rule this file sets for a
|
|
87
|
+
-- backfill, applied by a function: the guard is stepped around in one place,
|
|
88
|
+
-- where a reviewer reads it, and asks nobody who they are. Two other ways were
|
|
89
|
+
-- looked at and dropped. Going through a draft — the order the books were
|
|
90
|
+
-- written in — is not faithful to the cent: a draft line takes its snapshot and
|
|
91
|
+
-- its amounts from the tax *as it stands*. And a criterion of fact, "the line
|
|
92
|
+
-- arrives in the transaction that created its document", reads `xmin` and stops
|
|
93
|
+
-- being true at the first savepoint.
|
|
94
|
+
--
|
|
95
|
+
-- What arrives that way is then frozen like everything else, for the
|
|
96
|
+
-- administrator who loaded it too.
|
|
97
|
+
--
|
|
98
|
+
-- **The guards read as the schema, not as the caller.** Both are `security
|
|
99
|
+
-- definer`, and the reason was found by a test rather than foreseen: a guard
|
|
100
|
+
-- that asks "is this document still a draft?" or "is the company still there?"
|
|
101
|
+
-- under the caller's row level security is answered *no row* by whoever cannot
|
|
102
|
+
-- read the table — a machine key cannot read `companies` — and "no row" was
|
|
103
|
+
-- the answer that let a delete through. What a guard decides must not depend
|
|
104
|
+
-- on what the person it is guarding against is allowed to see. They write
|
|
105
|
+
-- nothing; `auth.uid()`, the key and the installer's setting are the session's
|
|
106
|
+
-- and read the same either way.
|
|
107
|
+
--
|
|
108
|
+
-- A later migration that must restate posted rows — a backfill — will be
|
|
109
|
+
-- refused by this guard, and that is intended: it has to disable the trigger
|
|
110
|
+
-- by name, in its own file, where a reviewer reads it.
|
|
111
|
+
--
|
|
112
|
+
-- `document_line_tax_frozen` is folded into this: one guard on a line, not
|
|
113
|
+
-- two. `document_lines_snapshot_tax()` keeps writing the snapshot of a draft
|
|
114
|
+
-- and no longer refuses anything, since nothing reaches it on a posted line.
|
|
115
|
+
-- `documents_guard_language` stays as it is — it fires first, and its sentence
|
|
116
|
+
-- is the better one for what it refuses.
|
|
117
|
+
--
|
|
118
|
+
-- What this does not close, and it is the larger half: **a posted entry can
|
|
119
|
+
-- still be deleted, emptied of its lines, or set back to `draft` while its
|
|
120
|
+
-- period is open.** `entries_guard_period` protects a locked period and
|
|
121
|
+
-- nothing protects an open one. An entry produced by a document is now held
|
|
122
|
+
-- by it — deleting it would null `documents.entry_id`, which this guard
|
|
123
|
+
-- refuses — but an entry keyed by hand, or a payment's, is not.
|
|
124
|
+
|
|
125
|
+
-- ---------------------------------------------------------------------------
|
|
126
|
+
-- 1. What a document has been settled by, as a figure that can be asked for
|
|
127
|
+
-- ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
create or replace function document_amount_paid(p_document_id uuid)
|
|
130
|
+
returns numeric
|
|
131
|
+
language sql
|
|
132
|
+
stable
|
|
133
|
+
set search_path = public, pg_temp
|
|
134
|
+
as $$
|
|
135
|
+
select coalesce((
|
|
136
|
+
select sum(case
|
|
137
|
+
when l.amount_currency is null then l.matched_amount
|
|
138
|
+
else round_amount(l.matched_amount * l.amount_currency
|
|
139
|
+
/ nullif(abs(l.debit - l.credit), 0),
|
|
140
|
+
rounding_of(d.company_id, d.currency_code))
|
|
141
|
+
end)
|
|
142
|
+
from entry_lines l
|
|
143
|
+
join accounts a on a.id = l.account_id
|
|
144
|
+
where l.entry_id = d.entry_id
|
|
145
|
+
and a.reconcilable
|
|
146
|
+
and a.account_type in ('asset_receivable', 'liability_payable')
|
|
147
|
+
), 0)
|
|
148
|
+
from documents d
|
|
149
|
+
where d.id = p_document_id;
|
|
150
|
+
$$;
|
|
151
|
+
|
|
152
|
+
comment on function document_amount_paid(uuid) is
|
|
153
|
+
'What a document has been settled by: the matched amounts on the third-party lines of its entry, in the document''s currency. The one definition — documents_refresh_amount_paid() writes it and documents_guard_posted() accepts no other figure.';
|
|
154
|
+
|
|
155
|
+
revoke execute on function document_amount_paid(uuid) from public, anon;
|
|
156
|
+
grant execute on function document_amount_paid(uuid) to authenticated, service_role;
|
|
157
|
+
|
|
158
|
+
create or replace function documents_refresh_amount_paid(p_document_id uuid)
|
|
159
|
+
returns void
|
|
160
|
+
language plpgsql
|
|
161
|
+
as $$
|
|
162
|
+
begin
|
|
163
|
+
-- The statement names amount_paid so that documents_refresh_payment_state,
|
|
164
|
+
-- which watches that column, fires in turn.
|
|
165
|
+
update documents d
|
|
166
|
+
set amount_paid = document_amount_paid(d.id)
|
|
167
|
+
where d.id = p_document_id
|
|
168
|
+
and d.entry_id is not null;
|
|
169
|
+
end;
|
|
170
|
+
$$;
|
|
171
|
+
|
|
172
|
+
comment on column documents.amount_paid is
|
|
173
|
+
'Derived from reconciliations on the third-party lines of the document''s entry: document_amount_paid(). Once the document is posted, any other figure is refused.';
|
|
174
|
+
|
|
175
|
+
-- ---------------------------------------------------------------------------
|
|
176
|
+
-- 2. The document
|
|
177
|
+
-- ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
create or replace function documents_guard_posted()
|
|
180
|
+
returns trigger
|
|
181
|
+
language plpgsql
|
|
182
|
+
security definer
|
|
183
|
+
set search_path = public, pg_temp
|
|
184
|
+
as $$
|
|
185
|
+
declare
|
|
186
|
+
-- What may still move once the state has left `draft`. A closed list: a
|
|
187
|
+
-- column that is not named here is frozen, including one that does not
|
|
188
|
+
-- exist yet.
|
|
189
|
+
c_still_moves constant text[] := array[
|
|
190
|
+
'amount_paid', 'amount_residual', 'payment_state',
|
|
191
|
+
'sent_at', 'peppol_status', 'peppol_message_id',
|
|
192
|
+
'updated_at'
|
|
193
|
+
];
|
|
194
|
+
v_moved text[];
|
|
195
|
+
begin
|
|
196
|
+
if tg_op = 'INSERT' then
|
|
197
|
+
if new.state <> 'draft' then
|
|
198
|
+
raise exception 'document_born_posted: a document is written as a draft and posted by post_document(). One that is already % arrives with a company being loaded, by import_company(), and in no other way.',
|
|
199
|
+
new.state
|
|
200
|
+
using errcode = '55006';
|
|
201
|
+
end if;
|
|
202
|
+
return new;
|
|
203
|
+
end if;
|
|
204
|
+
|
|
205
|
+
if tg_op = 'DELETE' then
|
|
206
|
+
if old.state = 'draft'
|
|
207
|
+
or not exists (select 1 from companies c where c.id = old.company_id) then
|
|
208
|
+
return old;
|
|
209
|
+
end if;
|
|
210
|
+
raise exception 'document_posted: % % was issued and cannot be deleted. It is undone by a credit note that names it.',
|
|
211
|
+
old.doc_type, coalesce(old.number, old.id::text)
|
|
212
|
+
using errcode = '55006';
|
|
213
|
+
end if;
|
|
214
|
+
|
|
215
|
+
if old.state = 'draft' then
|
|
216
|
+
-- The one transition that produces an entry has to have produced one.
|
|
217
|
+
if new.state = 'posted'
|
|
218
|
+
and not exists (select 1 from entries e where e.id = new.entry_id and e.state = 'posted') then
|
|
219
|
+
raise exception 'document_posted_without_entry: % has no posted entry. A document is posted by post_document(), which builds its entry and posts it first.',
|
|
220
|
+
coalesce(new.number, new.id::text)
|
|
221
|
+
using errcode = '55006';
|
|
222
|
+
end if;
|
|
223
|
+
return new;
|
|
224
|
+
end if;
|
|
225
|
+
|
|
226
|
+
if new.state is distinct from old.state then
|
|
227
|
+
raise exception 'document_posted: % % is % and stays so. It is undone by a credit note that names it, never by changing its state to %.',
|
|
228
|
+
old.doc_type, coalesce(old.number, old.id::text), old.state, new.state
|
|
229
|
+
using errcode = '55006';
|
|
230
|
+
end if;
|
|
231
|
+
|
|
232
|
+
select array_agg(n.key order by n.key)
|
|
233
|
+
into v_moved
|
|
234
|
+
from jsonb_each(to_jsonb(new)) n
|
|
235
|
+
join jsonb_each(to_jsonb(old)) o on o.key = n.key
|
|
236
|
+
where n.value is distinct from o.value
|
|
237
|
+
and n.key <> all (c_still_moves);
|
|
238
|
+
|
|
239
|
+
if v_moved is not null then
|
|
240
|
+
raise exception 'document_posted: % % was issued and keeps its % (%). What produced an entry does not change after it; credit it and issue another.',
|
|
241
|
+
old.doc_type, coalesce(old.number, old.id::text),
|
|
242
|
+
case when array_length(v_moved, 1) = 1 then 'value of' else 'values of' end,
|
|
243
|
+
array_to_string(v_moved, ', ')
|
|
244
|
+
using errcode = '55006';
|
|
245
|
+
end if;
|
|
246
|
+
|
|
247
|
+
if new.amount_paid is distinct from old.amount_paid
|
|
248
|
+
and new.amount_paid is distinct from document_amount_paid(new.id) then
|
|
249
|
+
raise exception 'document_amount_paid_is_derived: % has been settled by %, which is what its matching says. The figure is never keyed.',
|
|
250
|
+
coalesce(old.number, old.id::text), document_amount_paid(new.id)
|
|
251
|
+
using errcode = '55006';
|
|
252
|
+
end if;
|
|
253
|
+
|
|
254
|
+
if new.payment_state is distinct from old.payment_state
|
|
255
|
+
and new.amount_paid is not distinct from old.amount_paid then
|
|
256
|
+
raise exception 'document_payment_state_is_derived: the settlement of % follows from what was matched against it, and is never keyed.',
|
|
257
|
+
coalesce(old.number, old.id::text)
|
|
258
|
+
using errcode = '55006';
|
|
259
|
+
end if;
|
|
260
|
+
|
|
261
|
+
return new;
|
|
262
|
+
end;
|
|
263
|
+
$$;
|
|
264
|
+
|
|
265
|
+
comment on function documents_guard_posted() is
|
|
266
|
+
'Once a document has left draft: its state does not change, it is not deleted, and no column moves but the closed list of what happens to a document after it is issued — amount_paid (to the figure the matching gives, and no other), payment_state, sent_at, peppol_status, peppol_message_id. Refuses document_posted, by name, to everybody. Becoming posted requires an entry, and nobody inserts a document that is already posted (document_born_posted): one arrives that way only with a company loaded by import_company(), which switches this guard off by name for the time of the load.';
|
|
267
|
+
|
|
268
|
+
create trigger documents_guard_posted
|
|
269
|
+
before insert or update or delete on documents
|
|
270
|
+
for each row execute function documents_guard_posted();
|
|
271
|
+
|
|
272
|
+
-- A trigger body is invoked by its table, never called.
|
|
273
|
+
revoke execute on function documents_guard_posted() from public, anon, authenticated, service_role;
|
|
274
|
+
|
|
275
|
+
-- ---------------------------------------------------------------------------
|
|
276
|
+
-- 3. Its lines
|
|
277
|
+
--
|
|
278
|
+
-- Every column, an insert and a delete: a line of an issued document is the
|
|
279
|
+
-- line that was issued. `updated_at` is the one column a trigger of this
|
|
280
|
+
-- table writes on its own, and an update that changes nothing else is not a
|
|
281
|
+
-- change.
|
|
282
|
+
-- ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
create or replace function document_lines_guard_posted()
|
|
285
|
+
returns trigger
|
|
286
|
+
language plpgsql
|
|
287
|
+
security definer
|
|
288
|
+
set search_path = public, pg_temp
|
|
289
|
+
as $$
|
|
290
|
+
declare
|
|
291
|
+
v_state doc_state;
|
|
292
|
+
v_number text;
|
|
293
|
+
v_type doc_type;
|
|
294
|
+
v_moved text[];
|
|
295
|
+
begin
|
|
296
|
+
select d.state, d.number, d.doc_type into v_state, v_number, v_type
|
|
297
|
+
from documents d
|
|
298
|
+
where d.id = coalesce(new.document_id, old.document_id);
|
|
299
|
+
|
|
300
|
+
-- No document: it is being deleted, and its lines with it. Whether *that*
|
|
301
|
+
-- was allowed is the question documents_guard_posted() answered.
|
|
302
|
+
if not found or v_state = 'draft' then
|
|
303
|
+
-- A line does not leave a posted document for a draft either.
|
|
304
|
+
if tg_op = 'UPDATE' and new.document_id is distinct from old.document_id then
|
|
305
|
+
select d.state, d.number, d.doc_type into v_state, v_number, v_type
|
|
306
|
+
from documents d where d.id = old.document_id;
|
|
307
|
+
if found and v_state <> 'draft' then
|
|
308
|
+
raise exception 'document_posted: a line of % % was issued with it and stays on it.',
|
|
309
|
+
v_type, coalesce(v_number, old.document_id::text)
|
|
310
|
+
using errcode = '55006';
|
|
311
|
+
end if;
|
|
312
|
+
end if;
|
|
313
|
+
return coalesce(new, old);
|
|
314
|
+
end if;
|
|
315
|
+
|
|
316
|
+
if tg_op = 'DELETE' then
|
|
317
|
+
if not exists (select 1 from companies c where c.id = old.company_id) then
|
|
318
|
+
return old;
|
|
319
|
+
end if;
|
|
320
|
+
raise exception 'document_posted: line % of % % was issued and cannot be deleted. Credit the document and issue another.',
|
|
321
|
+
old.sequence, v_type, coalesce(v_number, old.document_id::text)
|
|
322
|
+
using errcode = '55006';
|
|
323
|
+
end if;
|
|
324
|
+
|
|
325
|
+
if tg_op = 'INSERT' then
|
|
326
|
+
raise exception 'document_posted: % % was issued with the lines it has, and takes no other. Credit it and issue another.',
|
|
327
|
+
v_type, coalesce(v_number, new.document_id::text)
|
|
328
|
+
using errcode = '55006';
|
|
329
|
+
end if;
|
|
330
|
+
|
|
331
|
+
select array_agg(n.key order by n.key)
|
|
332
|
+
into v_moved
|
|
333
|
+
from jsonb_each(to_jsonb(new)) n
|
|
334
|
+
join jsonb_each(to_jsonb(old)) o on o.key = n.key
|
|
335
|
+
where n.value is distinct from o.value
|
|
336
|
+
and n.key <> 'updated_at';
|
|
337
|
+
|
|
338
|
+
if v_moved is not null then
|
|
339
|
+
raise exception 'document_posted: line % of % % was issued and keeps its % (%). What produced an entry does not change after it; credit the document and issue another.',
|
|
340
|
+
old.sequence, v_type, coalesce(v_number, old.document_id::text),
|
|
341
|
+
case when array_length(v_moved, 1) = 1 then 'value of' else 'values of' end,
|
|
342
|
+
array_to_string(v_moved, ', ')
|
|
343
|
+
using errcode = '55006';
|
|
344
|
+
end if;
|
|
345
|
+
return new;
|
|
346
|
+
end;
|
|
347
|
+
$$;
|
|
348
|
+
|
|
349
|
+
comment on function document_lines_guard_posted() is
|
|
350
|
+
'Once a document has left draft, its lines are the lines that were issued: no insert, no delete, no column changed. Refuses document_posted, by name, to everybody.';
|
|
351
|
+
|
|
352
|
+
-- Named to fire before the other `before` triggers of the table, which fire in
|
|
353
|
+
-- the order of their names: a posted line is refused by this guard, by name,
|
|
354
|
+
-- and not by whatever an older trigger trips over on the way — and a derived
|
|
355
|
+
-- column keyed on a posted line is judged as it was keyed, before the line's
|
|
356
|
+
-- own trigger writes the right figure back over it.
|
|
357
|
+
create trigger document_lines_00_guard_posted
|
|
358
|
+
before insert or update or delete on document_lines
|
|
359
|
+
for each row execute function document_lines_guard_posted();
|
|
360
|
+
|
|
361
|
+
-- ---------------------------------------------------------------------------
|
|
362
|
+
-- 3b. The totals of a posted document are not recomputed
|
|
363
|
+
--
|
|
364
|
+
-- They were derived from the lines for as long as the lines could move, by a
|
|
365
|
+
-- trigger that fired on every write of a line whatever the state. The lines of
|
|
366
|
+
-- a posted document no longer move, so there is nothing left for it to do
|
|
367
|
+
-- there — and what it did do, before this file, was restate the totals of a
|
|
368
|
+
-- sent invoice from whatever its lines had been changed to. It now acts on
|
|
369
|
+
-- drafts. `documents_refresh_totals()` itself is unchanged and still answers
|
|
370
|
+
-- for whoever calls it.
|
|
371
|
+
-- ---------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
create or replace function document_lines_refresh_totals()
|
|
374
|
+
returns trigger
|
|
375
|
+
language plpgsql
|
|
376
|
+
as $$
|
|
377
|
+
declare
|
|
378
|
+
v_document uuid := coalesce(new.document_id, old.document_id);
|
|
379
|
+
begin
|
|
380
|
+
if not exists (select 1 from documents d where d.id = v_document and d.state = 'draft') then
|
|
381
|
+
return null;
|
|
382
|
+
end if;
|
|
383
|
+
-- The allocator writes its shares back through this same trigger, and what it
|
|
384
|
+
-- wrote is already the answer. Only the outermost write allocates: a second
|
|
385
|
+
-- pass would be work for nothing, and in a currency whose decimals the column
|
|
386
|
+
-- cannot hold — `amount_untaxed` is `numeric(16, 2)`, which is a gap of its
|
|
387
|
+
-- own — a share that comes back rounded would never compare equal and the
|
|
388
|
+
-- two would call each other for ever.
|
|
389
|
+
if pg_trigger_depth() <= 1 then
|
|
390
|
+
perform documents_allocate_included_tax(v_document);
|
|
391
|
+
end if;
|
|
392
|
+
perform documents_refresh_totals(v_document);
|
|
393
|
+
return null;
|
|
394
|
+
end;
|
|
395
|
+
$$;
|
|
396
|
+
|
|
397
|
+
comment on function document_lines_refresh_totals() is
|
|
398
|
+
'After a line of a draft moves: shares out the base of every tax group quoted with its tax in it, then refreshes the three totals of the document. A document that is no longer a draft keeps the totals it was posted, or loaded, with.';
|
|
399
|
+
|
|
400
|
+
revoke execute on function document_lines_guard_posted() from public, anon, authenticated, service_role;
|
|
401
|
+
|
|
402
|
+
-- ---------------------------------------------------------------------------
|
|
403
|
+
-- 4. One guard on a line, not two
|
|
404
|
+
--
|
|
405
|
+
-- `20260918141107` as it was, less the branch that refused: nothing reaches it
|
|
406
|
+
-- on a posted line any more. It writes the snapshot of a draft, and of a line
|
|
407
|
+
-- that arrives.
|
|
408
|
+
-- ---------------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
create or replace function document_lines_snapshot_tax()
|
|
411
|
+
returns trigger
|
|
412
|
+
language plpgsql
|
|
413
|
+
as $$
|
|
414
|
+
declare
|
|
415
|
+
v_state doc_state;
|
|
416
|
+
begin
|
|
417
|
+
select d.state into v_state from documents d where d.id = new.document_id;
|
|
418
|
+
if tg_op = 'UPDATE' and v_state <> 'draft' then
|
|
419
|
+
return new;
|
|
420
|
+
end if;
|
|
421
|
+
|
|
422
|
+
if new.line_type <> 'product' or new.tax_id is null then
|
|
423
|
+
new.vat_category := null;
|
|
424
|
+
new.vat_rate := null;
|
|
425
|
+
return new;
|
|
426
|
+
end if;
|
|
427
|
+
|
|
428
|
+
select t.vat_category,
|
|
429
|
+
case when t.amount_type = 'percent' then t.amount end
|
|
430
|
+
into new.vat_category, new.vat_rate
|
|
431
|
+
from taxes t where t.id = new.tax_id;
|
|
432
|
+
return new;
|
|
433
|
+
end;
|
|
434
|
+
$$;
|
|
435
|
+
|
|
436
|
+
comment on function document_lines_snapshot_tax() is
|
|
437
|
+
'Writes BT-151 and BT-152 on a line from its tax for as long as the document is a draft. Once it is not, document_lines_guard_posted() refuses any change to the line, these two columns included.';
|