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,209 @@
|
|
|
1
|
+
-- Ekwo OS — one evaluator, called by both reports.
|
|
2
|
+
--
|
|
3
|
+
-- A declaration form and a financial statement derive their totals the same
|
|
4
|
+
-- way: a list of lines to add, a list to subtract, and a floor at zero on the
|
|
5
|
+
-- pairs that split a balance. `20260912090407` wrote that evaluation inside
|
|
6
|
+
-- `vat_return()`, and `20260912100412` was about to write it a second time
|
|
7
|
+
-- inside `financial_statement()`. One calculation, two places, is the rule
|
|
8
|
+
-- this repository is built against.
|
|
9
|
+
--
|
|
10
|
+
-- The two differences between the callers are parameters, not engines:
|
|
11
|
+
--
|
|
12
|
+
-- `p_keep_zero` a return omits a box that comes to nothing; a statement
|
|
13
|
+
-- prints its whole frame, zeros and all.
|
|
14
|
+
-- `factor` a statement multiplies a line by the sign the scheme reads
|
|
15
|
+
-- it with; a declaration has no sign, so it passes nothing.
|
|
16
|
+
--
|
|
17
|
+
-- `evaluate_totals()` is created in `20260912100412`, where
|
|
18
|
+
-- `financial_statement()` already calls it. This file is separate for the one
|
|
19
|
+
-- reason that matters: `vat_return()` was published before, so it is replaced
|
|
20
|
+
-- in a file of its own rather than by editing the one that shipped it.
|
|
21
|
+
--
|
|
22
|
+
-- Two things the rewrite gives `vat_return()` beyond having one evaluator.
|
|
23
|
+
-- Its totals are now worked out **in the order they depend on each other**
|
|
24
|
+
-- rather than in the order the form declares them, which is a superset of what
|
|
25
|
+
-- it did — a forward reference used to read a silent zero, and
|
|
26
|
+
-- `ekwo pack check` refuses one anyway. And two totals that depend only on
|
|
27
|
+
-- each other now raise `formula_cycle` instead of both reading zero.
|
|
28
|
+
|
|
29
|
+
drop function if exists vat_return(uuid, date, date, text);
|
|
30
|
+
|
|
31
|
+
create or replace function vat_return(
|
|
32
|
+
p_company_id uuid,
|
|
33
|
+
p_from date,
|
|
34
|
+
p_to date,
|
|
35
|
+
p_report_code text default null
|
|
36
|
+
)
|
|
37
|
+
returns table (
|
|
38
|
+
box text,
|
|
39
|
+
kind text,
|
|
40
|
+
amount numeric,
|
|
41
|
+
computed boolean,
|
|
42
|
+
name text,
|
|
43
|
+
sequence integer,
|
|
44
|
+
hidden boolean,
|
|
45
|
+
report_code text
|
|
46
|
+
)
|
|
47
|
+
language plpgsql
|
|
48
|
+
stable
|
|
49
|
+
as $$
|
|
50
|
+
declare
|
|
51
|
+
-- 'box|kind' -> amount, for every box summed from the ledger, then the
|
|
52
|
+
-- totals `evaluate_totals()` derives from them. The key carries the kind
|
|
53
|
+
-- because the French CA3 puts a base and a tax on line 08 and a formula has
|
|
54
|
+
-- to be able to name one of them.
|
|
55
|
+
v_values jsonb := '{}'::jsonb;
|
|
56
|
+
v_formulas jsonb := '[]'::jsonb;
|
|
57
|
+
v_totals jsonb := '{}'::jsonb;
|
|
58
|
+
v_rows jsonb := '[]'::jsonb;
|
|
59
|
+
v_country char(2);
|
|
60
|
+
v_report text;
|
|
61
|
+
v_in char(2);
|
|
62
|
+
v_count integer;
|
|
63
|
+
v_codes text;
|
|
64
|
+
r record;
|
|
65
|
+
begin
|
|
66
|
+
select c.fiscal_country into v_country from companies c where c.id = p_company_id;
|
|
67
|
+
if not found then
|
|
68
|
+
raise exception 'unknown_company: %', p_company_id;
|
|
69
|
+
end if;
|
|
70
|
+
|
|
71
|
+
-- Which form. The caller names one, or the country files exactly one on
|
|
72
|
+
-- that date. Two and no name is a question only the caller can answer — a
|
|
73
|
+
-- Canadian company files the federal return and the Québec one at once — so
|
|
74
|
+
-- this asks instead of guessing.
|
|
75
|
+
if p_report_code is not null then
|
|
76
|
+
select t.country, t.code into v_in, v_report
|
|
77
|
+
from tax_report_templates t
|
|
78
|
+
where t.code = p_report_code
|
|
79
|
+
and t.valid_from <= p_to
|
|
80
|
+
and (t.valid_to is null or t.valid_to >= p_to)
|
|
81
|
+
order by t.country
|
|
82
|
+
limit 1;
|
|
83
|
+
if v_report is null then
|
|
84
|
+
raise exception 'unknown_tax_report: % is not a declaration form in force on %',
|
|
85
|
+
p_report_code, p_to;
|
|
86
|
+
end if;
|
|
87
|
+
else
|
|
88
|
+
select count(*), min(t.code), string_agg(t.code, ', ' order by t.code)
|
|
89
|
+
into v_count, v_report, v_codes
|
|
90
|
+
from tax_report_templates t
|
|
91
|
+
where t.country = v_country
|
|
92
|
+
and t.is_periodic_return
|
|
93
|
+
and t.valid_from <= p_to
|
|
94
|
+
and (t.valid_to is null or t.valid_to >= p_to);
|
|
95
|
+
if v_count > 1 then
|
|
96
|
+
raise exception 'ambiguous_tax_report: % files several declarations on % (%); name one',
|
|
97
|
+
v_country, p_to, v_codes;
|
|
98
|
+
end if;
|
|
99
|
+
v_in := v_country;
|
|
100
|
+
if v_count = 0 then
|
|
101
|
+
v_report := null; -- no pack for this country: the ledger boxes, and no total.
|
|
102
|
+
end if;
|
|
103
|
+
end if;
|
|
104
|
+
|
|
105
|
+
-- 1. What the tax postings wrote on the ledger. Unchanged: this half never
|
|
106
|
+
-- knew a country.
|
|
107
|
+
for r in
|
|
108
|
+
with ledger as (
|
|
109
|
+
select l.declaration_box as lbox,
|
|
110
|
+
case when l.tax_line then 'tax' else 'base' end as lkind,
|
|
111
|
+
round(sum(l.box_amount), 2) as lamount
|
|
112
|
+
from entry_lines l
|
|
113
|
+
join entries e on e.id = l.entry_id
|
|
114
|
+
where l.company_id = p_company_id
|
|
115
|
+
and e.state = 'posted'
|
|
116
|
+
and e.entry_date between p_from and p_to
|
|
117
|
+
and l.declaration_box is not null
|
|
118
|
+
-- A box number belongs to one form. A line whose posting names
|
|
119
|
+
-- another form is not on this declaration; one that names none is
|
|
120
|
+
-- the single-return case every European company is in.
|
|
121
|
+
and (v_report is null or coalesce((
|
|
122
|
+
select min(tp.report_code)
|
|
123
|
+
from tax_postings tp
|
|
124
|
+
where tp.tax_id = l.tax_id
|
|
125
|
+
and tp.declaration_box = l.declaration_box
|
|
126
|
+
and tp.posting_type =
|
|
127
|
+
(case when l.tax_line then 'tax' else 'base' end)::tax_posting_type
|
|
128
|
+
), v_report) = v_report)
|
|
129
|
+
group by 1, 2
|
|
130
|
+
having round(sum(l.box_amount), 2) <> 0
|
|
131
|
+
)
|
|
132
|
+
select g.lbox, g.lkind, g.lamount, b.name as lname,
|
|
133
|
+
b.sequence as lsequence, coalesce(b.hidden, false) as lhidden
|
|
134
|
+
from ledger g
|
|
135
|
+
left join tax_report_box_templates b
|
|
136
|
+
on b.country = v_in and b.report_code = v_report
|
|
137
|
+
and b.box = g.lbox and b.kind = g.lkind
|
|
138
|
+
order by coalesce(b.sequence, 2147483647), g.lbox, g.lkind
|
|
139
|
+
loop
|
|
140
|
+
v_values := v_values || jsonb_build_object(r.lbox || '|' || r.lkind, r.lamount);
|
|
141
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
142
|
+
'box', r.lbox, 'kind', r.lkind, 'amount', r.lamount, 'computed', false,
|
|
143
|
+
'name', r.lname, 'sequence', r.lsequence, 'hidden', r.lhidden,
|
|
144
|
+
'report_code', v_report));
|
|
145
|
+
end loop;
|
|
146
|
+
|
|
147
|
+
-- 2. The totals of the form, through the evaluator the statements use. A
|
|
148
|
+
-- return prints what it has, so a nil total is left out of the answer —
|
|
149
|
+
-- and kept in the working set, so a later total that names it reads a
|
|
150
|
+
-- zero rather than a gap.
|
|
151
|
+
if v_report is not null then
|
|
152
|
+
select coalesce(jsonb_agg(jsonb_build_object(
|
|
153
|
+
'key', b.box || '|total', 'plus', to_jsonb(b.plus_boxes),
|
|
154
|
+
'minus', to_jsonb(b.minus_boxes), 'floor_zero', b.floor_zero,
|
|
155
|
+
'sequence', b.sequence
|
|
156
|
+
) order by b.sequence, b.box), '[]'::jsonb)
|
|
157
|
+
into v_formulas
|
|
158
|
+
from tax_report_box_templates b
|
|
159
|
+
where b.country = v_in
|
|
160
|
+
and b.report_code = v_report
|
|
161
|
+
and b.kind = 'total'
|
|
162
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
163
|
+
and (b.valid_to is null or b.valid_to >= p_to);
|
|
164
|
+
|
|
165
|
+
v_totals := evaluate_totals(v_values, v_formulas, false);
|
|
166
|
+
|
|
167
|
+
for r in
|
|
168
|
+
select b.box as tbox, b.name as tname, b.sequence as tsequence, b.hidden as thidden
|
|
169
|
+
from tax_report_box_templates b
|
|
170
|
+
where b.country = v_in
|
|
171
|
+
and b.report_code = v_report
|
|
172
|
+
and b.kind = 'total'
|
|
173
|
+
and (b.valid_from is null or b.valid_from <= p_to)
|
|
174
|
+
and (b.valid_to is null or b.valid_to >= p_to)
|
|
175
|
+
and v_totals ? (b.box || '|total')
|
|
176
|
+
order by b.sequence, b.box
|
|
177
|
+
loop
|
|
178
|
+
v_rows := v_rows || jsonb_build_array(jsonb_build_object(
|
|
179
|
+
'box', r.tbox, 'kind', 'total',
|
|
180
|
+
'amount', (v_totals ->> (r.tbox || '|total'))::numeric, 'computed', true,
|
|
181
|
+
'name', r.tname, 'sequence', r.tsequence, 'hidden', r.thidden,
|
|
182
|
+
'report_code', v_report));
|
|
183
|
+
end loop;
|
|
184
|
+
end if;
|
|
185
|
+
|
|
186
|
+
return query
|
|
187
|
+
select (x ->> 'box')::text,
|
|
188
|
+
(x ->> 'kind')::text,
|
|
189
|
+
(x ->> 'amount')::numeric,
|
|
190
|
+
(x ->> 'computed')::boolean,
|
|
191
|
+
(x ->> 'name')::text,
|
|
192
|
+
(x ->> 'sequence')::integer,
|
|
193
|
+
(x ->> 'hidden')::boolean,
|
|
194
|
+
(x ->> 'report_code')::text
|
|
195
|
+
from jsonb_array_elements(v_rows) as x;
|
|
196
|
+
end;
|
|
197
|
+
$$;
|
|
198
|
+
|
|
199
|
+
comment on function vat_return(uuid, date, date, text) is
|
|
200
|
+
'Declaration boxes for a period: summed from the ledger, then the totals of the country''s form worked out by evaluate_totals(), the same evaluator financial_statement() uses. No country rule lives in this function.';
|
|
201
|
+
|
|
202
|
+
-- Rule 6 of supabase/migrations/README.md: a function created here comes out
|
|
203
|
+
-- executable by PUBLIC otherwise.
|
|
204
|
+
revoke execute on all functions in schema public from public;
|
|
205
|
+
|
|
206
|
+
-- And the evaluator by name, from `anon` too. The blanket revoke above is
|
|
207
|
+
-- never aimed at `anon`, which holds explicit grants on the eight policy
|
|
208
|
+
-- helpers and would lose them; naming one function takes nothing else away.
|
|
209
|
+
revoke execute on function evaluate_totals(jsonb, jsonb, boolean) from public, anon;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- Ekwo OS — the appropriation entry is not the entry that closes the books.
|
|
2
|
+
--
|
|
3
|
+
-- `close_fiscal_year()` writes two entries where a country appropriates
|
|
4
|
+
-- through accounts of its own income statement — Belgium, 693 for a profit and
|
|
5
|
+
-- 793 for a loss. The first moves the result into those accounts and on to
|
|
6
|
+
-- retained earnings; the second takes every income and expense account back to
|
|
7
|
+
-- zero, that pair included. Both were marked `closing`, so they cancelled each
|
|
8
|
+
-- other out and the "Affectations et prélèvements" section of the Belgian
|
|
9
|
+
-- annual accounts read nil the moment a year was closed.
|
|
10
|
+
--
|
|
11
|
+
-- They are two different acts and they get two names. `appropriation` is the
|
|
12
|
+
-- entry that says where the result went; `closing` is the entry that empties
|
|
13
|
+
-- the income statement. An allocation section reads the first and leaves out
|
|
14
|
+
-- the second, an income statement leaves out both — the appropriation touches
|
|
15
|
+
-- no line of it, but a report should not depend on that — and a balance sheet
|
|
16
|
+
-- keeps them, because together they are what puts the result on the line it
|
|
17
|
+
-- shows.
|
|
18
|
+
--
|
|
19
|
+
-- The value lands in its own file: PostgreSQL refuses a new enum value in the
|
|
20
|
+
-- transaction that added it, which is why `20260912081014` and `081015` are
|
|
21
|
+
-- two files as well.
|
|
22
|
+
|
|
23
|
+
alter type entry_kind add value if not exists 'appropriation';
|
|
24
|
+
|
|
25
|
+
comment on type entry_kind is
|
|
26
|
+
'What an entry is for: normal, the opening of a year, the appropriation of its result, or the entry that closes it. A report of a closed year leaves out what it is not answerable for.';
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
-- Ekwo OS — the appropriation entry says where the result went.
|
|
2
|
+
--
|
|
3
|
+
-- The value was added in the migration before this one; this file is what uses
|
|
4
|
+
-- it. Three functions change and nothing else:
|
|
5
|
+
--
|
|
6
|
+
-- `close_fiscal_year()` marks the appropriation entry `appropriation`.
|
|
7
|
+
-- The closing entry keeps `closing`.
|
|
8
|
+
-- `reopen_fiscal_year()` undoes both kinds, as it undid one.
|
|
9
|
+
-- `statement_account_matches()` leaves `closing` out of an income statement
|
|
10
|
+
-- and out of an allocation section, and keeps
|
|
11
|
+
-- `appropriation` in both.
|
|
12
|
+
--
|
|
13
|
+
-- What that buys: after a Belgian year-end, `financial_statement(...,
|
|
14
|
+
-- 'BE-BNB-ABBR-AF', ...)` reads the result on the line the scheme prints it
|
|
15
|
+
-- on, instead of the nil two entries that cancel each other produce. The
|
|
16
|
+
-- income statement is unchanged — it was already right, because 693 and 793
|
|
17
|
+
-- reach no line of it — and so is every balance sheet.
|
|
18
|
+
--
|
|
19
|
+
-- The guard on `entries.kind` needs nothing: it already admits any value under
|
|
20
|
+
-- the session flag the three year-end functions set, and refuses every value
|
|
21
|
+
-- but `normal` outside it.
|
|
22
|
+
|
|
23
|
+
-- ---------------------------------------------------------------------------
|
|
24
|
+
-- close_fiscal_year — the appropriation entry, by its own name
|
|
25
|
+
-- ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
create or replace function close_fiscal_year(p_fiscal_year_id uuid)
|
|
28
|
+
returns jsonb
|
|
29
|
+
language plpgsql
|
|
30
|
+
as $$
|
|
31
|
+
declare
|
|
32
|
+
v_year fiscal_years%rowtype;
|
|
33
|
+
v_company companies%rowtype;
|
|
34
|
+
v_defaults country_defaults%rowtype;
|
|
35
|
+
v_journal uuid;
|
|
36
|
+
v_result numeric(16, 2);
|
|
37
|
+
v_profit boolean;
|
|
38
|
+
v_kind text;
|
|
39
|
+
v_cyr uuid; -- current-year result account, the side that applies
|
|
40
|
+
v_retained uuid; -- retained earnings, the side that applies
|
|
41
|
+
v_carries boolean;
|
|
42
|
+
v_appropriate uuid;
|
|
43
|
+
v_closing uuid;
|
|
44
|
+
v_entry entries%rowtype;
|
|
45
|
+
v_lines integer;
|
|
46
|
+
v_debits numeric(16, 2);
|
|
47
|
+
v_credits numeric(16, 2);
|
|
48
|
+
begin
|
|
49
|
+
select * into v_year from fiscal_years where id = p_fiscal_year_id for update;
|
|
50
|
+
if not found then
|
|
51
|
+
raise exception 'unknown_fiscal_year: fiscal year % does not exist', p_fiscal_year_id;
|
|
52
|
+
end if;
|
|
53
|
+
if v_year.is_closed then
|
|
54
|
+
raise exception 'fiscal_year_already_closed: % was closed on %', v_year.name, v_year.closed_at;
|
|
55
|
+
end if;
|
|
56
|
+
|
|
57
|
+
select * into v_company from companies where id = v_year.company_id;
|
|
58
|
+
|
|
59
|
+
-- 1. What has to be true before a year can be closed.
|
|
60
|
+
|
|
61
|
+
if exists (
|
|
62
|
+
select 1 from entries e
|
|
63
|
+
where e.company_id = v_year.company_id
|
|
64
|
+
and e.entry_date between v_year.start_date and v_year.end_date
|
|
65
|
+
and e.state = 'draft'
|
|
66
|
+
) then
|
|
67
|
+
raise exception 'fiscal_year_has_drafts: % still holds draft entries; post or cancel them first', v_year.name;
|
|
68
|
+
end if;
|
|
69
|
+
|
|
70
|
+
if exists (
|
|
71
|
+
select 1 from fiscal_years f
|
|
72
|
+
where f.company_id = v_year.company_id
|
|
73
|
+
and f.start_date < v_year.start_date
|
|
74
|
+
and not f.is_closed
|
|
75
|
+
and exists (select 1 from entries e where e.fiscal_year_id = f.id and e.state = 'posted')
|
|
76
|
+
) then
|
|
77
|
+
raise exception 'earlier_fiscal_year_open: a year before % holds posted entries and is not closed', v_year.name;
|
|
78
|
+
end if;
|
|
79
|
+
|
|
80
|
+
if exists (
|
|
81
|
+
select 1 from fiscal_years f
|
|
82
|
+
where f.company_id = v_year.company_id
|
|
83
|
+
and f.start_date > v_year.start_date
|
|
84
|
+
and f.is_closed
|
|
85
|
+
) then
|
|
86
|
+
raise exception 'later_fiscal_year_closed: a year after % is already closed; re-open it first', v_year.name;
|
|
87
|
+
end if;
|
|
88
|
+
|
|
89
|
+
-- 2. The accounts the result travels through, from the country model.
|
|
90
|
+
|
|
91
|
+
select * into v_defaults from country_defaults where country = v_company.country;
|
|
92
|
+
if not found then
|
|
93
|
+
raise exception 'unknown_country_template: no country model for %; close_fiscal_year reads its accounts from the pack',
|
|
94
|
+
v_company.country;
|
|
95
|
+
end if;
|
|
96
|
+
|
|
97
|
+
if v_defaults.closing_style is null then
|
|
98
|
+
raise exception 'no_closing_defaults: the pack of this company says nothing about how a year is closed. Set defaults.closing_style, and the account roles it needs, in the pack.'
|
|
99
|
+
using errcode = '55006';
|
|
100
|
+
end if;
|
|
101
|
+
|
|
102
|
+
v_journal := opening_journal_id(v_year.company_id);
|
|
103
|
+
if v_journal is null then
|
|
104
|
+
raise exception 'no_opening_journal: the pack of this company names no journal of type opening. Set defaults.journal_roles.opening in the pack, which fills country_defaults.opening_journal_code.'
|
|
105
|
+
using errcode = '55006';
|
|
106
|
+
end if;
|
|
107
|
+
|
|
108
|
+
-- The result of the year: income less expense, over the accounts that do
|
|
109
|
+
-- not carry forward. A credit balance is a profit.
|
|
110
|
+
select coalesce(-sum(l.balance), 0) into v_result
|
|
111
|
+
from entry_lines l
|
|
112
|
+
join entries e on e.id = l.entry_id and e.state = 'posted'
|
|
113
|
+
join accounts a on a.id = l.account_id
|
|
114
|
+
where l.company_id = v_year.company_id
|
|
115
|
+
and e.entry_date between v_year.start_date and v_year.end_date
|
|
116
|
+
and not a.carries_forward;
|
|
117
|
+
|
|
118
|
+
v_profit := v_result >= 0;
|
|
119
|
+
v_kind := case when v_result > 0 then 'profit'
|
|
120
|
+
when v_result < 0 then 'loss'
|
|
121
|
+
else 'nil' end;
|
|
122
|
+
|
|
123
|
+
v_retained := coalesce(
|
|
124
|
+
case when v_profit then v_company.retained_earnings_account_id end,
|
|
125
|
+
account_id_by_code(v_year.company_id,
|
|
126
|
+
case when v_profit then v_defaults.retained_earnings_code
|
|
127
|
+
else coalesce(v_defaults.retained_earnings_loss_code, v_defaults.retained_earnings_code) end));
|
|
128
|
+
|
|
129
|
+
v_cyr := account_id_by_code(v_year.company_id,
|
|
130
|
+
case when v_profit then v_defaults.current_year_result_profit_code
|
|
131
|
+
else v_defaults.current_year_result_loss_code end);
|
|
132
|
+
|
|
133
|
+
if v_defaults.closing_style = 'retained_earnings' then
|
|
134
|
+
if v_retained is null then
|
|
135
|
+
raise exception 'no_retained_earnings_account: the country model of % names none, and the company has none',
|
|
136
|
+
v_company.country;
|
|
137
|
+
end if;
|
|
138
|
+
v_cyr := v_retained;
|
|
139
|
+
else
|
|
140
|
+
if v_cyr is null then
|
|
141
|
+
raise exception 'no_current_year_result_account: the country model of % names no account for a %',
|
|
142
|
+
v_company.country, v_kind;
|
|
143
|
+
end if;
|
|
144
|
+
end if;
|
|
145
|
+
|
|
146
|
+
-- A style is a promise about where the result sits at the end. Assert it
|
|
147
|
+
-- rather than trust it: a pack that names an income account where the
|
|
148
|
+
-- balance sheet is expected would carry nothing forward, silently.
|
|
149
|
+
select a.carries_forward into v_carries from accounts a where a.id = v_cyr;
|
|
150
|
+
if v_defaults.closing_style = 'appropriation_accounts' and v_carries then
|
|
151
|
+
raise exception 'closing_style_mismatch: % expects an appropriation account inside the income statement, and % carries forward',
|
|
152
|
+
v_defaults.closing_style, v_cyr;
|
|
153
|
+
end if;
|
|
154
|
+
if v_defaults.closing_style <> 'appropriation_accounts' and not v_carries then
|
|
155
|
+
raise exception 'closing_style_mismatch: % expects an account on the balance sheet, and % does not carry forward',
|
|
156
|
+
v_defaults.closing_style, v_cyr;
|
|
157
|
+
end if;
|
|
158
|
+
|
|
159
|
+
-- 3. The appropriation entry: the result leaves the income statement
|
|
160
|
+
-- through an account that is itself part of it, and lands on retained
|
|
161
|
+
-- earnings. Only the third style has one, and only when there is a
|
|
162
|
+
-- result to move.
|
|
163
|
+
|
|
164
|
+
if v_defaults.closing_style = 'appropriation_accounts' and v_result <> 0 then
|
|
165
|
+
if v_retained is null then
|
|
166
|
+
raise exception 'no_retained_earnings_account: the country model of % names none, and the company has none',
|
|
167
|
+
v_company.country;
|
|
168
|
+
end if;
|
|
169
|
+
|
|
170
|
+
perform set_config('ekwo.year_end_entry', 'on', true);
|
|
171
|
+
insert into entries (company_id, journal_id, fiscal_year_id, entry_date,
|
|
172
|
+
description, state, kind)
|
|
173
|
+
values (v_year.company_id, v_journal, v_year.id, v_year.end_date,
|
|
174
|
+
'Result of the year', 'draft', 'appropriation')
|
|
175
|
+
returning * into v_entry;
|
|
176
|
+
perform set_config('ekwo.year_end_entry', 'off', true);
|
|
177
|
+
v_appropriate := v_entry.id;
|
|
178
|
+
|
|
179
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name, debit, credit)
|
|
180
|
+
values (v_appropriate, v_year.company_id, v_cyr, 10, 'Result of the year',
|
|
181
|
+
case when v_profit then abs(v_result) else 0 end,
|
|
182
|
+
case when v_profit then 0 else abs(v_result) end),
|
|
183
|
+
(v_appropriate, v_year.company_id, v_retained, 20, 'Result of the year',
|
|
184
|
+
case when v_profit then 0 else abs(v_result) end,
|
|
185
|
+
case when v_profit then abs(v_result) else 0 end);
|
|
186
|
+
|
|
187
|
+
perform post_entry(v_appropriate);
|
|
188
|
+
end if;
|
|
189
|
+
|
|
190
|
+
-- 4. The closing entry: every income and expense account back to zero.
|
|
191
|
+
-- In the first two styles the difference is the result and goes to the
|
|
192
|
+
-- account chosen above; in the third the accounts already net to zero,
|
|
193
|
+
-- because the appropriation entry put the result among them.
|
|
194
|
+
|
|
195
|
+
perform set_config('ekwo.year_end_entry', 'on', true);
|
|
196
|
+
insert into entries (company_id, journal_id, fiscal_year_id, entry_date,
|
|
197
|
+
description, state, kind)
|
|
198
|
+
values (v_year.company_id, v_journal, v_year.id, v_year.end_date,
|
|
199
|
+
'Closing entry', 'draft', 'closing')
|
|
200
|
+
returning * into v_entry;
|
|
201
|
+
perform set_config('ekwo.year_end_entry', 'off', true);
|
|
202
|
+
v_closing := v_entry.id;
|
|
203
|
+
|
|
204
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name, debit, credit)
|
|
205
|
+
select v_closing, v_year.company_id, s.account_id,
|
|
206
|
+
row_number() over (order by s.code) * 10,
|
|
207
|
+
'Closing entry',
|
|
208
|
+
case when s.balance < 0 then -s.balance else 0 end,
|
|
209
|
+
case when s.balance > 0 then s.balance else 0 end
|
|
210
|
+
from (
|
|
211
|
+
select a.id as account_id, a.code,
|
|
212
|
+
round(sum(l.balance), 2) as balance
|
|
213
|
+
from entry_lines l
|
|
214
|
+
join entries e on e.id = l.entry_id and e.state = 'posted'
|
|
215
|
+
join accounts a on a.id = l.account_id
|
|
216
|
+
where l.company_id = v_year.company_id
|
|
217
|
+
and e.entry_date between v_year.start_date and v_year.end_date
|
|
218
|
+
and not a.carries_forward
|
|
219
|
+
group by a.id, a.code
|
|
220
|
+
having round(sum(l.balance), 2) <> 0
|
|
221
|
+
) s;
|
|
222
|
+
|
|
223
|
+
select count(*) into v_lines from entry_lines where entry_id = v_closing;
|
|
224
|
+
|
|
225
|
+
if v_lines = 0 then
|
|
226
|
+
delete from entries where id = v_closing;
|
|
227
|
+
v_closing := null;
|
|
228
|
+
else
|
|
229
|
+
if v_defaults.closing_style <> 'appropriation_accounts' and v_result <> 0 then
|
|
230
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name, debit, credit)
|
|
231
|
+
values (v_closing, v_year.company_id, v_cyr, 1000000, 'Result of the year',
|
|
232
|
+
case when v_profit then 0 else abs(v_result) end,
|
|
233
|
+
case when v_profit then abs(v_result) else 0 end);
|
|
234
|
+
end if;
|
|
235
|
+
|
|
236
|
+
select total_debit, total_credit into v_debits, v_credits from entries where id = v_closing;
|
|
237
|
+
if v_debits <> v_credits then
|
|
238
|
+
raise exception 'closing_unbalanced: the closing entry has debit % and credit %. The income statement of % does not net to its result.',
|
|
239
|
+
v_debits, v_credits, v_year.name;
|
|
240
|
+
end if;
|
|
241
|
+
|
|
242
|
+
perform post_entry(v_closing);
|
|
243
|
+
end if;
|
|
244
|
+
|
|
245
|
+
-- 5. The year is closed, and only from here.
|
|
246
|
+
|
|
247
|
+
perform set_config('ekwo.closing_fiscal_year', 'on', true);
|
|
248
|
+
update fiscal_years
|
|
249
|
+
set is_closed = true,
|
|
250
|
+
closed_at = now()
|
|
251
|
+
where id = p_fiscal_year_id;
|
|
252
|
+
perform set_config('ekwo.closing_fiscal_year', 'off', true);
|
|
253
|
+
|
|
254
|
+
return jsonb_build_object(
|
|
255
|
+
'fiscal_year_id', p_fiscal_year_id,
|
|
256
|
+
'closing_style', v_defaults.closing_style,
|
|
257
|
+
'result', to_char(v_result, 'FM9999999999999990.00'),
|
|
258
|
+
'result_kind', v_kind,
|
|
259
|
+
'appropriation_entry_id', v_appropriate,
|
|
260
|
+
'closing_entry_id', v_closing
|
|
261
|
+
);
|
|
262
|
+
end;
|
|
263
|
+
$$;
|
|
264
|
+
|
|
265
|
+
comment on function close_fiscal_year(uuid) is
|
|
266
|
+
'Closes a fiscal year: the result leaves the income statement the way the country model says, and every income and expense account goes back to zero. The entry that moves the result is `appropriation`, the one that empties the income statement is `closing`. The balance sheet needs no entry — the reports read the ledger from the beginning. The allocation decided by a meeting is never part of it.';
|
|
267
|
+
|
|
268
|
+
-- ---------------------------------------------------------------------------
|
|
269
|
+
-- reopen_fiscal_year — undoes both of them
|
|
270
|
+
-- ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
create or replace function reopen_fiscal_year(p_fiscal_year_id uuid)
|
|
273
|
+
returns jsonb
|
|
274
|
+
language plpgsql
|
|
275
|
+
as $$
|
|
276
|
+
declare
|
|
277
|
+
v_year fiscal_years%rowtype;
|
|
278
|
+
v_original entries%rowtype;
|
|
279
|
+
v_reversal entries%rowtype;
|
|
280
|
+
v_reversed uuid[] := '{}';
|
|
281
|
+
begin
|
|
282
|
+
select * into v_year from fiscal_years where id = p_fiscal_year_id for update;
|
|
283
|
+
if not found then
|
|
284
|
+
raise exception 'unknown_fiscal_year: fiscal year % does not exist', p_fiscal_year_id;
|
|
285
|
+
end if;
|
|
286
|
+
if not v_year.is_closed then
|
|
287
|
+
raise exception 'fiscal_year_not_closed: % is already open', v_year.name;
|
|
288
|
+
end if;
|
|
289
|
+
|
|
290
|
+
-- Re-opening a year changes its result, and the result stands on the
|
|
291
|
+
-- balance sheet every later year reads. So a year that has been built on is
|
|
292
|
+
-- not a year that can be quietly restated.
|
|
293
|
+
if exists (
|
|
294
|
+
select 1 from fiscal_years f
|
|
295
|
+
where f.company_id = v_year.company_id
|
|
296
|
+
and f.start_date > v_year.end_date
|
|
297
|
+
and (f.is_closed or exists (
|
|
298
|
+
select 1 from entries e where e.fiscal_year_id = f.id and e.state = 'posted'))
|
|
299
|
+
) then
|
|
300
|
+
raise exception 'next_fiscal_year_in_use: a year after % is closed or already holds posted entries', v_year.name;
|
|
301
|
+
end if;
|
|
302
|
+
|
|
303
|
+
-- The flag first: the reversals are dated inside the year being re-opened,
|
|
304
|
+
-- and the period guard would refuse them while it is closed.
|
|
305
|
+
perform set_config('ekwo.closing_fiscal_year', 'on', true);
|
|
306
|
+
update fiscal_years
|
|
307
|
+
set is_closed = false,
|
|
308
|
+
closed_at = null
|
|
309
|
+
where id = p_fiscal_year_id;
|
|
310
|
+
perform set_config('ekwo.closing_fiscal_year', 'off', true);
|
|
311
|
+
|
|
312
|
+
for v_original in
|
|
313
|
+
select e.*
|
|
314
|
+
from entries e
|
|
315
|
+
where e.company_id = v_year.company_id
|
|
316
|
+
and e.state = 'posted'
|
|
317
|
+
and e.kind in ('closing', 'appropriation')
|
|
318
|
+
and e.fiscal_year_id = p_fiscal_year_id
|
|
319
|
+
and e.reversed_entry_id is null
|
|
320
|
+
and not exists (
|
|
321
|
+
select 1 from entries r where r.reversed_entry_id = e.id and r.state = 'posted'
|
|
322
|
+
)
|
|
323
|
+
order by e.entry_date, e.number
|
|
324
|
+
loop
|
|
325
|
+
-- A reversal carries the kind of what it undoes: it belongs to the same
|
|
326
|
+
-- report exclusion, and a closing entry undone by a `normal` one would
|
|
327
|
+
-- reappear in the income statement on its own.
|
|
328
|
+
perform set_config('ekwo.year_end_entry', 'on', true);
|
|
329
|
+
insert into entries (company_id, journal_id, fiscal_year_id, entry_date,
|
|
330
|
+
description, state, reversed_entry_id, currency_code, kind)
|
|
331
|
+
values (v_original.company_id, v_original.journal_id, v_original.fiscal_year_id,
|
|
332
|
+
v_original.entry_date, 'Reversal of ' || v_original.number, 'draft',
|
|
333
|
+
v_original.id, v_original.currency_code, v_original.kind)
|
|
334
|
+
returning * into v_reversal;
|
|
335
|
+
perform set_config('ekwo.year_end_entry', 'off', true);
|
|
336
|
+
|
|
337
|
+
insert into entry_lines (entry_id, company_id, account_id, sequence, name,
|
|
338
|
+
debit, credit, contact_id, date_maturity, currency_code)
|
|
339
|
+
select v_reversal.id, l.company_id, l.account_id, l.sequence, l.name,
|
|
340
|
+
l.credit, l.debit, l.contact_id, l.date_maturity, l.currency_code
|
|
341
|
+
from entry_lines l
|
|
342
|
+
where l.entry_id = v_original.id
|
|
343
|
+
order by l.sequence;
|
|
344
|
+
|
|
345
|
+
perform post_entry(v_reversal.id);
|
|
346
|
+
v_reversed := v_reversed || v_reversal.id;
|
|
347
|
+
end loop;
|
|
348
|
+
|
|
349
|
+
return jsonb_build_object(
|
|
350
|
+
'fiscal_year_id', p_fiscal_year_id,
|
|
351
|
+
'reversal_entry_ids', to_jsonb(v_reversed)
|
|
352
|
+
);
|
|
353
|
+
end;
|
|
354
|
+
$$;
|
|
355
|
+
|
|
356
|
+
comment on function reopen_fiscal_year(uuid) is
|
|
357
|
+
'Undoes a close: reverses the appropriation and closing entries it wrote and clears is_closed. Refused once a later year is closed or holds entries of its own.';
|
|
358
|
+
|
|
359
|
+
-- ---------------------------------------------------------------------------
|
|
360
|
+
-- statement_account_matches — which entries a statement is answerable for
|
|
361
|
+
-- ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
create or replace function statement_account_matches(
|
|
364
|
+
p_company_id uuid,
|
|
365
|
+
p_statement_code text,
|
|
366
|
+
p_from date,
|
|
367
|
+
p_to date
|
|
368
|
+
)
|
|
369
|
+
returns table (
|
|
370
|
+
account_id uuid,
|
|
371
|
+
account_code text,
|
|
372
|
+
account_name text,
|
|
373
|
+
account_type account_type,
|
|
374
|
+
balance numeric,
|
|
375
|
+
line_code text
|
|
376
|
+
)
|
|
377
|
+
language sql
|
|
378
|
+
stable
|
|
379
|
+
as $$
|
|
380
|
+
with statement as (
|
|
381
|
+
select s.code, s.kind from statement_templates s where s.code = p_statement_code
|
|
382
|
+
),
|
|
383
|
+
balances as (
|
|
384
|
+
select a.id, a.code, a.name, a.account_type,
|
|
385
|
+
round(coalesce(sum(l.balance), 0), 2) as balance
|
|
386
|
+
from accounts a
|
|
387
|
+
join entry_lines l on l.account_id = a.id
|
|
388
|
+
join entries e on e.id = l.entry_id
|
|
389
|
+
cross join statement st
|
|
390
|
+
where a.company_id = p_company_id
|
|
391
|
+
and e.state = 'posted'
|
|
392
|
+
and case when st.kind = 'balance_sheet'
|
|
393
|
+
then e.entry_date <= p_to
|
|
394
|
+
else e.entry_date between p_from and p_to
|
|
395
|
+
end
|
|
396
|
+
-- An income statement is what the period earned, and the entry that
|
|
397
|
+
-- closes a year is not that: it books the mirror image of every income
|
|
398
|
+
-- and expense account so they start the next year at nil. Left in, a
|
|
399
|
+
-- closed year reads as a result of zero. An allocation section leaves
|
|
400
|
+
-- it out for the same reason and keeps the appropriation entry, which
|
|
401
|
+
-- is the movement it exists to show. A balance sheet keeps both, and
|
|
402
|
+
-- must: together they are what puts the result on the line it shows.
|
|
403
|
+
and (st.kind not in ('income_statement', 'allocation') or e.kind <> 'closing')
|
|
404
|
+
group by a.id, a.code, a.name, a.account_type
|
|
405
|
+
),
|
|
406
|
+
ranked as (
|
|
407
|
+
select b.id, b.code, b.name, b.account_type, b.balance, r.line_code,
|
|
408
|
+
row_number() over (
|
|
409
|
+
partition by b.id
|
|
410
|
+
order by case r.rule_kind
|
|
411
|
+
when 'account_code' then 1
|
|
412
|
+
when 'code_range' then 2
|
|
413
|
+
when 'code_prefix' then 2
|
|
414
|
+
else 3
|
|
415
|
+
end,
|
|
416
|
+
length(coalesce(r.code_from, '')) desc,
|
|
417
|
+
r.sequence, r.line_code
|
|
418
|
+
) as rank
|
|
419
|
+
from balances b
|
|
420
|
+
join statement_line_rules r on r.statement_code = p_statement_code
|
|
421
|
+
where case r.rule_kind
|
|
422
|
+
when 'account_code' then b.code = r.code_from
|
|
423
|
+
when 'code_prefix' then left(b.code, length(r.code_from)) = r.code_from
|
|
424
|
+
when 'code_range' then left(b.code, length(r.code_from)) >= r.code_from
|
|
425
|
+
and left(b.code, length(r.code_to)) <= r.code_to
|
|
426
|
+
else b.account_type = r.account_type
|
|
427
|
+
end
|
|
428
|
+
and case r.balance_side
|
|
429
|
+
when 'debit' then b.balance > 0
|
|
430
|
+
when 'credit' then b.balance < 0
|
|
431
|
+
else true
|
|
432
|
+
end
|
|
433
|
+
)
|
|
434
|
+
select b.id, b.code, b.name, b.account_type, b.balance, r.line_code
|
|
435
|
+
from balances b
|
|
436
|
+
left join ranked r on r.id = b.id and r.rank = 1
|
|
437
|
+
order by b.code;
|
|
438
|
+
$$;
|
|
439
|
+
|
|
440
|
+
comment on function statement_account_matches(uuid, text, date, date) is
|
|
441
|
+
'Every account of a company with a balance in the period, and the statement line it falls on — null when no rule catches it. An income statement and an allocation section leave the closing entry out; a balance sheet keeps it. The single decision financial_statement() and unmapped_accounts() both read.';
|
|
442
|
+
|
|
443
|
+
-- Rule 6 of supabase/migrations/README.md.
|
|
444
|
+
revoke execute on all functions in schema public from public;
|