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.
Files changed (361) hide show
  1. package/README.md +873 -0
  2. package/dist/args.d.ts +57 -0
  3. package/dist/args.d.ts.map +1 -0
  4. package/dist/args.js +134 -0
  5. package/dist/args.js.map +1 -0
  6. package/dist/assets/expected-objects.json +9432 -0
  7. package/dist/assets/migrations/20260911120000_core_companies.sql +260 -0
  8. package/dist/assets/migrations/20260911120100_accounts_journals.sql +211 -0
  9. package/dist/assets/migrations/20260911120200_contacts.sql +90 -0
  10. package/dist/assets/migrations/20260911120300_taxes.sql +146 -0
  11. package/dist/assets/migrations/20260911120400_entries.sql +338 -0
  12. package/dist/assets/migrations/20260911120500_documents.sql +279 -0
  13. package/dist/assets/migrations/20260911120600_payments_reconciliation.sql +251 -0
  14. package/dist/assets/migrations/20260911120700_bank.sql +178 -0
  15. package/dist/assets/migrations/20260911120800_analytics_attachments.sql +128 -0
  16. package/dist/assets/migrations/20260911120900_post_document.sql +279 -0
  17. package/dist/assets/migrations/20260911121000_reporting.sql +235 -0
  18. package/dist/assets/migrations/20260911121100_country_templates.sql +226 -0
  19. package/dist/assets/migrations/20260911121200_fec.sql +71 -0
  20. package/dist/assets/migrations/20260911130000_instance.sql +77 -0
  21. package/dist/assets/migrations/20260911130100_instance_members.sql +224 -0
  22. package/dist/assets/migrations/20260911140000_instance_admins.sql +112 -0
  23. package/dist/assets/migrations/20260911160000_tax_posting_templates_unique.sql +17 -0
  24. package/dist/assets/migrations/20260911170000_document_amount_paid.sql +82 -0
  25. package/dist/assets/migrations/20260911173000_post_payment.sql +122 -0
  26. package/dist/assets/migrations/20260911173100_sequence_counters_under_rls.sql +84 -0
  27. package/dist/assets/migrations/20260911183000_country_journal_defaults.sql +143 -0
  28. package/dist/assets/migrations/20260911193853_line_account_defaults.sql +274 -0
  29. package/dist/assets/migrations/20260911195054_products.sql +221 -0
  30. package/dist/assets/migrations/20260911210131_anon_surface.sql +85 -0
  31. package/dist/assets/migrations/20260912074712_country_packs.sql +318 -0
  32. package/dist/assets/migrations/20260912080311_report_code_and_region.sql +198 -0
  33. package/dist/assets/migrations/20260912081014_pack_certification_maintained.sql +31 -0
  34. package/dist/assets/migrations/20260912081015_pack_certification_backfill.sql +16 -0
  35. package/dist/assets/migrations/20260912090407_tax_report_boxes.sql +329 -0
  36. package/dist/assets/migrations/20260912091917_tax_on_base_value.sql +25 -0
  37. package/dist/assets/migrations/20260912091918_tax_engine_columns.sql +776 -0
  38. package/dist/assets/migrations/20260912094412_opening_and_closing.sql +759 -0
  39. package/dist/assets/migrations/20260912095825_charts_of_accounts.sql +326 -0
  40. package/dist/assets/migrations/20260912100412_financial_statements.sql +632 -0
  41. package/dist/assets/migrations/20260912104719_one_formula_evaluator.sql +209 -0
  42. package/dist/assets/migrations/20260912105720_entry_kind_appropriation.sql +26 -0
  43. package/dist/assets/migrations/20260912105721_appropriation_entry_kind.sql +444 -0
  44. package/dist/assets/migrations/20260912111751_document_rules.sql +274 -0
  45. package/dist/assets/migrations/20260912112132_cash_basis_vat_and_fx.sql +1189 -0
  46. package/dist/assets/migrations/20260913074512_modules.sql +493 -0
  47. package/dist/assets/migrations/20260913075903_asset_disposal_roles.sql +47 -0
  48. package/dist/assets/migrations/20260913083216_capabilities.sql +637 -0
  49. package/dist/assets/migrations/20260913083901_company_invitations.sql +262 -0
  50. package/dist/assets/migrations/20260913084402_user_preferences.sql +376 -0
  51. package/dist/assets/migrations/20260913084847_company_profile.sql +261 -0
  52. package/dist/assets/migrations/20260913085436_numbering_reads_the_pack.sql +235 -0
  53. package/dist/assets/migrations/20260913085932_api_keys.sql +303 -0
  54. package/dist/assets/migrations/20260913090216_fiscal_year_bounds.sql +159 -0
  55. package/dist/assets/migrations/20260913092527_entries_import.sql +219 -0
  56. package/dist/assets/migrations/20260913101536_null_safe_guards.sql +188 -0
  57. package/dist/assets/migrations/20260913102115_installer_is_named.sql +462 -0
  58. package/dist/assets/migrations/20260913102758_no_currency_default.sql +138 -0
  59. package/dist/assets/migrations/20260913103355_cash_basis_needs_a_box.sql +453 -0
  60. package/dist/assets/migrations/20260913104014_aged_balance_named_group.sql +79 -0
  61. package/dist/assets/migrations/20260913104232_foreign_key_indexes.sql +144 -0
  62. package/dist/assets/migrations/20260913105120_declared_no_reader.sql +41 -0
  63. package/dist/assets/migrations/20260913111407_pack_languages.sql +223 -0
  64. package/dist/assets/migrations/20260913112233_schema_comments_for_a_reader.sql +27 -0
  65. package/dist/assets/migrations/20260913114535_fec_opening_balances.sql +307 -0
  66. package/dist/assets/migrations/20260914103412_audit_log.sql +509 -0
  67. package/dist/assets/migrations/20260914111907_pack_upgrade.sql +428 -0
  68. package/dist/assets/migrations/20260914120500_rounding_reads_the_currency.sql +206 -0
  69. package/dist/assets/migrations/20260914121200_amounts_round_at_the_currency.sql +2403 -0
  70. package/dist/assets/migrations/20260914134325_schema_version_0_2_0.sql +36 -0
  71. package/dist/assets/migrations/20260914143915_accounts_in_use.sql +428 -0
  72. package/dist/assets/migrations/20260914144731_account_code_frozen.sql +99 -0
  73. package/dist/assets/migrations/20260914151207_schema_grants_its_own_rights.sql +284 -0
  74. package/dist/assets/migrations/20260914152840_pack_upgrade_records_its_own_line.sql +237 -0
  75. package/dist/assets/migrations/20260914163943_declaration_periodicity.sql +349 -0
  76. package/dist/assets/migrations/20260915094000_foreign_services_received.sql +37 -0
  77. package/dist/assets/migrations/20260915094500_mentions_read_a_foreign_service.sql +72 -0
  78. package/dist/assets/migrations/20260915153000_document_shares.sql +568 -0
  79. package/dist/assets/migrations/20260915160000_ec_sales_list.sql +181 -0
  80. package/dist/assets/migrations/20260915161842_pack_sources.sql +56 -0
  81. package/dist/assets/migrations/20260915170500_schema_version_0_3_0.sql +45 -0
  82. package/dist/assets/migrations/20260915174500_a_vat_category_is_a_code.sql +177 -0
  83. package/dist/assets/migrations/20260915180000_document_rule_references.sql +85 -0
  84. package/dist/assets/migrations/20260915181000_territories.sql +342 -0
  85. package/dist/assets/migrations/20260915181500_ec_sales_list_reads_the_territories.sql +222 -0
  86. package/dist/assets/migrations/20260915182000_intracom_triangular.sql +53 -0
  87. package/dist/assets/migrations/20260915182500_a_triangular_supply_says_reverse_charge.sql +99 -0
  88. package/dist/assets/migrations/20260915191200_a_document_knows_its_language.sql +633 -0
  89. package/dist/assets/migrations/20260915195000_a_price_that_holds_its_tax.sql +649 -0
  90. package/dist/assets/migrations/20260915200000_a_posting_names_its_boxes.sql +1004 -0
  91. package/dist/assets/migrations/20260916094500_a_company_files_more_than_one_declaration.sql +795 -0
  92. package/dist/assets/migrations/20260916103000_a_ledger_line_names_its_posting.sql +902 -0
  93. package/dist/assets/migrations/20260916123000_a_vocabulary_for_a_tax_that_is_not_a_vat.sql +144 -0
  94. package/dist/assets/migrations/20260916124000_a_box_can_be_a_rate_of_a_box.sql +525 -0
  95. package/dist/assets/migrations/20260916125000_a_tax_follows_the_territory.sql +1318 -0
  96. package/dist/assets/migrations/20260916126000_a_tax_point_and_its_exception.sql +1211 -0
  97. package/dist/assets/migrations/20260917090000_a_counterparty_that_learns.sql +561 -0
  98. package/dist/assets/migrations/20260917120000_what_the_money_pays.sql +545 -0
  99. package/dist/assets/migrations/20260917150000_a_filing_is_a_row.sql +488 -0
  100. package/dist/assets/migrations/20260917170000_when_it_is_due.sql +194 -0
  101. package/dist/assets/migrations/20260917180000_a_box_is_a_number_and_a_kind.sql +213 -0
  102. package/dist/assets/migrations/20260917190000_what_a_declaration_owes.sql +486 -0
  103. package/dist/assets/migrations/20260917200000_what_moved_after_it_went.sql +227 -0
  104. package/dist/assets/migrations/20260918090000_a_form_names_its_file.sql +27 -0
  105. package/dist/assets/migrations/20260918110000_a_deposit_and_what_came_back.sql +286 -0
  106. package/dist/assets/migrations/20260918113741_a_fourth_preset_is_named.sql +9 -0
  107. package/dist/assets/migrations/20260918113807_a_client_reads_and_hands_over.sql +96 -0
  108. package/dist/assets/migrations/20260918114322_two_definer_functions_check_their_caller.sql +75 -0
  109. package/dist/assets/migrations/20260918130000_every_company_somebody_keeps.sql +177 -0
  110. package/dist/assets/migrations/20260918140000_a_session_nobody_prepared.sql +40 -0
  111. package/dist/assets/migrations/20260918141107_a_line_keeps_the_tax_it_was_posted_with.sql +256 -0
  112. package/dist/assets/migrations/20260918141342_a_company_has_an_electronic_address.sql +66 -0
  113. package/dist/assets/migrations/20260918141605_an_invoice_reads_whole_from_the_views.sql +313 -0
  114. package/dist/assets/migrations/20260918141627_a_policy_asks_once.sql +132 -0
  115. package/dist/assets/migrations/20260918143352_a_suggestion_reads_the_words_once.sql +204 -0
  116. package/dist/assets/migrations/20260918143417_a_reference_of_the_caller_and_a_rehearsal.sql +115 -0
  117. package/dist/assets/migrations/20260918150712_a_company_leaves_with_its_books.sql +1119 -0
  118. package/dist/assets/migrations/20260918150931_a_statement_is_imported_once.sql +654 -0
  119. package/dist/assets/migrations/20260918161204_a_posted_document_does_not_move.sql +437 -0
  120. package/dist/assets/migrations/20260918161538_a_posted_entry_does_not_move.sql +252 -0
  121. package/dist/assets/migrations/20260918171946_an_entry_is_posted_by_post_entry.sql +289 -0
  122. package/dist/assets/migrations/20260918174312_schema_version_0_4_0.sql +53 -0
  123. package/dist/assets/modules/assets/module.json +13 -0
  124. package/dist/assets/modules/assets/supabase/migrations/20260913081447_assets.sql +1190 -0
  125. package/dist/assets/modules/assets/supabase/migrations/20260913104234_foreign_key_indexes.sql +47 -0
  126. package/dist/assets/modules/assets/supabase/migrations/20260913104500_assets_capabilities.sql +145 -0
  127. package/dist/assets/modules/assets/supabase/migrations/20260914122100_assets_round_at_the_currency.sql +564 -0
  128. package/dist/assets/modules/assets/supabase/migrations/20260914145012_assets_accounts_in_use.sql +37 -0
  129. package/dist/assets/modules/assets/supabase/migrations/20260914151530_assets_grants_its_own_rights.sql +63 -0
  130. package/dist/assets/modules/assets/supabase/migrations/20260918113912_assets_client_reads.sql +20 -0
  131. package/dist/assets/modules/assets/supabase/migrations/20260918150801_assets_leave_with_the_company.sql +41 -0
  132. package/dist/assets/modules/budgets/module.json +12 -0
  133. package/dist/assets/modules/budgets/supabase/migrations/20260913083012_budgets.sql +210 -0
  134. package/dist/assets/modules/budgets/supabase/migrations/20260913104233_foreign_key_indexes.sql +37 -0
  135. package/dist/assets/modules/budgets/supabase/migrations/20260913104501_budgets_capabilities.sql +72 -0
  136. package/dist/assets/modules/budgets/supabase/migrations/20260914122400_budgets_round_at_the_currency.sql +68 -0
  137. package/dist/assets/modules/budgets/supabase/migrations/20260914145048_budgets_accounts_in_use.sql +30 -0
  138. package/dist/assets/modules/budgets/supabase/migrations/20260914151812_budgets_grants_its_own_rights.sql +31 -0
  139. package/dist/assets/modules/budgets/supabase/migrations/20260918113954_budgets_client_reads.sql +20 -0
  140. package/dist/assets/modules/budgets/supabase/migrations/20260918150823_budgets_leave_with_the_company.sql +37 -0
  141. package/dist/assets/modules/schema/module.1.json +69 -0
  142. package/dist/assets/seed/00_currencies.sql +15 -0
  143. package/dist/assets/seed/00_territories.sql +280 -0
  144. package/dist/assets/seed/05_framework_generic.sql +110 -0
  145. package/dist/assets/seed/10_pack_be.sql +1284 -0
  146. package/dist/assets/seed/11_pack_fr.sql +1112 -0
  147. package/dist/assets/seed/12_pack_lu.sql +2484 -0
  148. package/dist/assets/seed/13_pack_ee.sql +733 -0
  149. package/dist/assets/seed/14_pack_gb.sql +796 -0
  150. package/dist/assets/seed/15_pack_us.sql +751 -0
  151. package/dist/assets/seed/90_demo_company.sql +368 -0
  152. package/dist/assets/seed/modules/assets/10_pack_be.sql +58 -0
  153. package/dist/assets/seed/modules/assets/11_pack_fr.sql +57 -0
  154. package/dist/assets/seed/modules/assets/14_pack_gb.sql +58 -0
  155. package/dist/assets/seed/modules/assets/15_pack_us.sql +60 -0
  156. package/dist/auth.d.ts +59 -0
  157. package/dist/auth.d.ts.map +1 -0
  158. package/dist/auth.js +134 -0
  159. package/dist/auth.js.map +1 -0
  160. package/dist/bin.d.ts +6 -0
  161. package/dist/bin.d.ts.map +1 -0
  162. package/dist/bin.js +8 -0
  163. package/dist/bin.js.map +1 -0
  164. package/dist/books.d.ts +60 -0
  165. package/dist/books.d.ts.map +1 -0
  166. package/dist/books.js +168 -0
  167. package/dist/books.js.map +1 -0
  168. package/dist/bootstrap.d.ts +236 -0
  169. package/dist/bootstrap.d.ts.map +1 -0
  170. package/dist/bootstrap.js +459 -0
  171. package/dist/bootstrap.js.map +1 -0
  172. package/dist/bundle.d.ts +27 -0
  173. package/dist/bundle.d.ts.map +1 -0
  174. package/dist/bundle.js +47 -0
  175. package/dist/bundle.js.map +1 -0
  176. package/dist/checklist.d.ts +35 -0
  177. package/dist/checklist.d.ts.map +1 -0
  178. package/dist/checklist.js +64 -0
  179. package/dist/checklist.js.map +1 -0
  180. package/dist/cli.d.ts +14 -0
  181. package/dist/cli.d.ts.map +1 -0
  182. package/dist/cli.js +318 -0
  183. package/dist/cli.js.map +1 -0
  184. package/dist/commands/company.d.ts +59 -0
  185. package/dist/commands/company.d.ts.map +1 -0
  186. package/dist/commands/company.js +287 -0
  187. package/dist/commands/company.js.map +1 -0
  188. package/dist/commands/contact.d.ts +9 -0
  189. package/dist/commands/contact.d.ts.map +1 -0
  190. package/dist/commands/contact.js +82 -0
  191. package/dist/commands/contact.js.map +1 -0
  192. package/dist/commands/demo.d.ts +19 -0
  193. package/dist/commands/demo.d.ts.map +1 -0
  194. package/dist/commands/demo.js +72 -0
  195. package/dist/commands/demo.js.map +1 -0
  196. package/dist/commands/doctor.d.ts +12 -0
  197. package/dist/commands/doctor.d.ts.map +1 -0
  198. package/dist/commands/doctor.js +68 -0
  199. package/dist/commands/doctor.js.map +1 -0
  200. package/dist/commands/document.d.ts +17 -0
  201. package/dist/commands/document.d.ts.map +1 -0
  202. package/dist/commands/document.js +217 -0
  203. package/dist/commands/document.js.map +1 -0
  204. package/dist/commands/init.d.ts +31 -0
  205. package/dist/commands/init.d.ts.map +1 -0
  206. package/dist/commands/init.js +535 -0
  207. package/dist/commands/init.js.map +1 -0
  208. package/dist/commands/login.d.ts +24 -0
  209. package/dist/commands/login.d.ts.map +1 -0
  210. package/dist/commands/login.js +128 -0
  211. package/dist/commands/login.js.map +1 -0
  212. package/dist/commands/migrate.d.ts +30 -0
  213. package/dist/commands/migrate.d.ts.map +1 -0
  214. package/dist/commands/migrate.js +115 -0
  215. package/dist/commands/migrate.js.map +1 -0
  216. package/dist/commands/module.d.ts +32 -0
  217. package/dist/commands/module.d.ts.map +1 -0
  218. package/dist/commands/module.js +285 -0
  219. package/dist/commands/module.js.map +1 -0
  220. package/dist/commands/pack.d.ts +22 -0
  221. package/dist/commands/pack.d.ts.map +1 -0
  222. package/dist/commands/pack.js +481 -0
  223. package/dist/commands/pack.js.map +1 -0
  224. package/dist/commands/payment.d.ts +15 -0
  225. package/dist/commands/payment.d.ts.map +1 -0
  226. package/dist/commands/payment.js +97 -0
  227. package/dist/commands/payment.js.map +1 -0
  228. package/dist/commands/register.d.ts +20 -0
  229. package/dist/commands/register.d.ts.map +1 -0
  230. package/dist/commands/register.js +134 -0
  231. package/dist/commands/register.js.map +1 -0
  232. package/dist/commands/status.d.ts +8 -0
  233. package/dist/commands/status.d.ts.map +1 -0
  234. package/dist/commands/status.js +134 -0
  235. package/dist/commands/status.js.map +1 -0
  236. package/dist/commands/whoami.d.ts +21 -0
  237. package/dist/commands/whoami.d.ts.map +1 -0
  238. package/dist/commands/whoami.js +133 -0
  239. package/dist/commands/whoami.js.map +1 -0
  240. package/dist/company.d.ts +16 -0
  241. package/dist/company.d.ts.map +1 -0
  242. package/dist/company.js +26 -0
  243. package/dist/company.js.map +1 -0
  244. package/dist/config.d.ts +24 -0
  245. package/dist/config.d.ts.map +1 -0
  246. package/dist/config.js +50 -0
  247. package/dist/config.js.map +1 -0
  248. package/dist/connection.d.ts +79 -0
  249. package/dist/connection.d.ts.map +1 -0
  250. package/dist/connection.js +116 -0
  251. package/dist/connection.js.map +1 -0
  252. package/dist/context.d.ts +47 -0
  253. package/dist/context.d.ts.map +1 -0
  254. package/dist/context.js +93 -0
  255. package/dist/context.js.map +1 -0
  256. package/dist/doctor.d.ts +45 -0
  257. package/dist/doctor.d.ts.map +1 -0
  258. package/dist/doctor.js +448 -0
  259. package/dist/doctor.js.map +1 -0
  260. package/dist/grants.d.ts +88 -0
  261. package/dist/grants.d.ts.map +1 -0
  262. package/dist/grants.js +174 -0
  263. package/dist/grants.js.map +1 -0
  264. package/dist/identity.d.ts +54 -0
  265. package/dist/identity.d.ts.map +1 -0
  266. package/dist/identity.js +132 -0
  267. package/dist/identity.js.map +1 -0
  268. package/dist/index.d.ts +44 -0
  269. package/dist/index.d.ts.map +1 -0
  270. package/dist/index.js +43 -0
  271. package/dist/index.js.map +1 -0
  272. package/dist/inventory.d.ts +155 -0
  273. package/dist/inventory.d.ts.map +1 -0
  274. package/dist/inventory.js +303 -0
  275. package/dist/inventory.js.map +1 -0
  276. package/dist/migrations.d.ts +76 -0
  277. package/dist/migrations.d.ts.map +1 -0
  278. package/dist/migrations.js +226 -0
  279. package/dist/migrations.js.map +1 -0
  280. package/dist/module/read.d.ts +91 -0
  281. package/dist/module/read.d.ts.map +1 -0
  282. package/dist/module/read.js +172 -0
  283. package/dist/module/read.js.map +1 -0
  284. package/dist/output.d.ts +110 -0
  285. package/dist/output.d.ts.map +1 -0
  286. package/dist/output.js +173 -0
  287. package/dist/output.js.map +1 -0
  288. package/dist/pack/certification.d.ts +27 -0
  289. package/dist/pack/certification.d.ts.map +1 -0
  290. package/dist/pack/certification.js +38 -0
  291. package/dist/pack/certification.js.map +1 -0
  292. package/dist/pack/compile.d.ts +75 -0
  293. package/dist/pack/compile.d.ts.map +1 -0
  294. package/dist/pack/compile.js +801 -0
  295. package/dist/pack/compile.js.map +1 -0
  296. package/dist/pack/filing.d.ts +48 -0
  297. package/dist/pack/filing.d.ts.map +1 -0
  298. package/dist/pack/filing.js +56 -0
  299. package/dist/pack/filing.js.map +1 -0
  300. package/dist/pack/read.d.ts +766 -0
  301. package/dist/pack/read.d.ts.map +1 -0
  302. package/dist/pack/read.js +2336 -0
  303. package/dist/pack/read.js.map +1 -0
  304. package/dist/pack/schema.d.ts +26 -0
  305. package/dist/pack/schema.d.ts.map +1 -0
  306. package/dist/pack/schema.js +181 -0
  307. package/dist/pack/schema.js.map +1 -0
  308. package/dist/pack/territories.d.ts +98 -0
  309. package/dist/pack/territories.d.ts.map +1 -0
  310. package/dist/pack/territories.js +0 -0
  311. package/dist/pack/territories.js.map +1 -0
  312. package/dist/pack/upgrade.d.ts +86 -0
  313. package/dist/pack/upgrade.d.ts.map +1 -0
  314. package/dist/pack/upgrade.js +76 -0
  315. package/dist/pack/upgrade.js.map +1 -0
  316. package/dist/pack/vat-codes.d.ts +273 -0
  317. package/dist/pack/vat-codes.d.ts.map +1 -0
  318. package/dist/pack/vat-codes.js +513 -0
  319. package/dist/pack/vat-codes.js.map +1 -0
  320. package/dist/profiles.d.ts +67 -0
  321. package/dist/profiles.d.ts.map +1 -0
  322. package/dist/profiles.js +187 -0
  323. package/dist/profiles.js.map +1 -0
  324. package/dist/prompt.d.ts +40 -0
  325. package/dist/prompt.d.ts.map +1 -0
  326. package/dist/prompt.js +120 -0
  327. package/dist/prompt.js.map +1 -0
  328. package/dist/registry.d.ts +84 -0
  329. package/dist/registry.d.ts.map +1 -0
  330. package/dist/registry.js +104 -0
  331. package/dist/registry.js.map +1 -0
  332. package/dist/rest.d.ts +81 -0
  333. package/dist/rest.d.ts.map +1 -0
  334. package/dist/rest.js +245 -0
  335. package/dist/rest.js.map +1 -0
  336. package/dist/schema.d.ts +15 -0
  337. package/dist/schema.d.ts.map +1 -0
  338. package/dist/schema.js +15 -0
  339. package/dist/schema.js.map +1 -0
  340. package/dist/seeds.d.ts +27 -0
  341. package/dist/seeds.d.ts.map +1 -0
  342. package/dist/seeds.js +47 -0
  343. package/dist/seeds.js.map +1 -0
  344. package/dist/session.d.ts +39 -0
  345. package/dist/session.d.ts.map +1 -0
  346. package/dist/session.js +92 -0
  347. package/dist/session.js.map +1 -0
  348. package/dist/sql.d.ts +48 -0
  349. package/dist/sql.d.ts.map +1 -0
  350. package/dist/sql.js +112 -0
  351. package/dist/sql.js.map +1 -0
  352. package/dist/status.d.ts +94 -0
  353. package/dist/status.d.ts.map +1 -0
  354. package/dist/status.js +120 -0
  355. package/dist/status.js.map +1 -0
  356. package/dist/ui.d.ts +55 -0
  357. package/dist/ui.d.ts.map +1 -0
  358. package/dist/ui.js +114 -0
  359. package/dist/ui.js.map +1 -0
  360. package/package.json +29 -0
  361. 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;