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,1004 @@
1
+ -- Ekwo OS — one posting, printed in as many boxes as the form asks for.
2
+ --
3
+ -- A tax carries one `base` posting per kind of document. That rule is right
4
+ -- and it stays: a taxable amount has one definition, and every further place
5
+ -- the form shows it is a `total` adding the boxes the postings already wrote.
6
+ -- What the rule could not express is a form that prints one amount in boxes
7
+ -- **no total can derive from one another**, and two of the packs in this
8
+ -- repository meet it:
9
+ --
10
+ -- Form KMD (Estonia) reports an intra-Community acquisition of goods in box
11
+ -- 6.1, in box 6 around it and in box 1 around that. Box 6 is not the sum
12
+ -- of the boxes printed under it — the form prints only 6.1, and the rest
13
+ -- of 6 is services received, which the form never prints on its own — so
14
+ -- the pack had to invent a leaf box for the part nobody prints and total
15
+ -- the two. Six such boxes on a form of thirty.
16
+ -- The VAT Return (United Kingdom) asks for the value of a service received
17
+ -- from a supplier established abroad in box 6, which is outputs, and in
18
+ -- box 7, which is inputs. Neither box contains the other, and the pack
19
+ -- had to invent three leaf boxes on a form of nine.
20
+ --
21
+ -- Both are the same gap, recorded in `docs/international.md` under two names,
22
+ -- and both are closed here: a posting names **a list** of boxes, and
23
+ -- `vat_return()` sums it into each one.
24
+ --
25
+ -- **The shape is an array, not a table of its own.** `declaration_boxes
26
+ -- text[]` beside `declaration_box`, on the template and on the company's copy,
27
+ -- with the first of the list equal to the column. Three reasons, and
28
+ -- `docs/decisions.md` carries them at length: the return reads the list with
29
+ -- one `unnest` where a junction is a join and a second order to keep; a
30
+ -- posting has no identity of its own — `pack upgrade` already replaces every
31
+ -- posting of a tax as one fact — so the rows of a junction would carry a key
32
+ -- nothing names; and a junction is a table, which is a policy, a grant and an
33
+ -- inventory entry for a list that is almost always one element long.
34
+ --
35
+ -- **`declaration_box` stays and stays first.** It is what a posting is known
36
+ -- by, what `vat_return()` reads a line back on, and what every reader that
37
+ -- knows nothing of this change still sees. A check constraint holds the two
38
+ -- in step, and a trigger fills the list from the column for a writer that
39
+ -- knows only the column — so nothing that writes a posting today has to
40
+ -- change, and a pack that names one box compiles to exactly what it did.
41
+ --
42
+ -- **What does not change**: `evaluate_totals()`, which never knew a box from
43
+ -- a statement line; `post_document()`, because a posting still writes one
44
+ -- ledger line and that line still carries one box — the expansion belongs to
45
+ -- the return, not to the ledger; and `ec_sales_list()`, which reads treatments
46
+ -- and never a declaration box.
47
+
48
+ -- ---------------------------------------------------------------------------
49
+ -- The column, on the template and on the company's copy
50
+ -- ---------------------------------------------------------------------------
51
+
52
+ alter table tax_posting_templates
53
+ add column if not exists declaration_boxes text[];
54
+
55
+ alter table tax_postings
56
+ add column if not exists declaration_boxes text[];
57
+
58
+ update tax_posting_templates
59
+ set declaration_boxes = array[declaration_box]
60
+ where declaration_box is not null
61
+ and declaration_boxes is null;
62
+
63
+ update tax_postings
64
+ set declaration_boxes = array[declaration_box]
65
+ where declaration_box is not null
66
+ and declaration_boxes is null;
67
+
68
+ comment on column tax_posting_templates.declaration_boxes is
69
+ 'Every box this one amount is printed in, from packs/<cc>/taxes.json. Almost always the single box declaration_box names, which is the first of the list; several where the form prints one figure in boxes that are not sums of one another. A box that is a sum stays a total and is never named here.';
70
+
71
+ comment on column tax_postings.declaration_boxes is
72
+ 'Every box this one amount is printed in, copied from the template. The first is declaration_box, which is what the posting is known by; vat_return() sums the line into each box of the list.';
73
+
74
+ -- The two are one fact written twice, so they are held in step. The list may
75
+ -- not be empty, it starts at the box the posting is known by, and it is null
76
+ -- exactly where that box is — a posting that reports nowhere reports nowhere
77
+ -- in every column.
78
+ --
79
+ -- Duplicates inside the list are not refused here. `array[..] = array[..]`
80
+ -- cannot say it without a subquery, which a check constraint may not hold,
81
+ -- and a duplicate changes no figure — `unnest` would sum the same line into
82
+ -- the same box twice, which is a defect in the pack rather than in a row.
83
+ -- `ekwo pack check` refuses it at the one moment it is worth saying, which is
84
+ -- when somebody is writing the pack.
85
+
86
+ alter table tax_posting_templates
87
+ add constraint tax_posting_templates_boxes_start_at_the_box check (
88
+ case when declaration_box is null
89
+ then declaration_boxes is null
90
+ else declaration_boxes is not null
91
+ and cardinality(declaration_boxes) >= 1
92
+ and declaration_boxes[1] = declaration_box
93
+ end
94
+ );
95
+
96
+ alter table tax_postings
97
+ add constraint tax_postings_boxes_start_at_the_box check (
98
+ case when declaration_box is null
99
+ then declaration_boxes is null
100
+ else declaration_boxes is not null
101
+ and cardinality(declaration_boxes) >= 1
102
+ and declaration_boxes[1] = declaration_box
103
+ end
104
+ );
105
+
106
+ -- ---------------------------------------------------------------------------
107
+ -- A writer that knows one column is handed the other
108
+ -- ---------------------------------------------------------------------------
109
+ --
110
+ -- Everything that wrote a posting before this migration names `declaration_box`
111
+ -- and nothing else: the seeds of an older release, a company's own tax typed
112
+ -- into the application, a test fixture. Rather than make every one of them
113
+ -- learn a second column, the row fills the half it was not given.
114
+ --
115
+ -- "Not given" is read from what moved, never from what is null, and that is
116
+ -- the whole subtlety here. A writer that clears the box means the posting
117
+ -- reports nowhere, and a trigger that read `declaration_box is null` as "say
118
+ -- nothing" would put the box straight back from the list — which is a write
119
+ -- that does not take, the worst kind. So: the box moved and the list did not,
120
+ -- the list follows; the list moved and the box did not, the box follows; both
121
+ -- moved, the writer knows what it is doing and the check constraint judges it.
122
+
123
+ create or replace function tax_posting_boxes_agree()
124
+ returns trigger
125
+ language plpgsql
126
+ as $$
127
+ declare
128
+ -- An insert is an update from a row where both were null, which is the same
129
+ -- rule written once instead of twice.
130
+ v_was_box text := case when tg_op = 'UPDATE' then old.declaration_box else null end;
131
+ v_was_boxes text[] := case when tg_op = 'UPDATE' then old.declaration_boxes else null end;
132
+ v_box_moved boolean := new.declaration_box is distinct from v_was_box;
133
+ v_list_moved boolean := new.declaration_boxes is distinct from v_was_boxes;
134
+ begin
135
+ if v_box_moved and not v_list_moved then
136
+ new.declaration_boxes := case when new.declaration_box is null
137
+ then null
138
+ else array[new.declaration_box] end;
139
+ elsif v_list_moved and not v_box_moved then
140
+ new.declaration_box := case when new.declaration_boxes is null
141
+ or cardinality(new.declaration_boxes) = 0
142
+ then null
143
+ else new.declaration_boxes[1] end;
144
+ end if;
145
+ return new;
146
+ end;
147
+ $$;
148
+
149
+ comment on function tax_posting_boxes_agree() is
150
+ 'Keeps declaration_box and declaration_boxes in step on a tax posting: a writer that moves one is handed the other. A writer that moves both is left alone and judged by the check constraint, and a writer that clears the box clears the list with it.';
151
+
152
+ create trigger tax_posting_templates_boxes_agree
153
+ before insert or update on tax_posting_templates
154
+ for each row execute function tax_posting_boxes_agree();
155
+
156
+ create trigger tax_postings_boxes_agree
157
+ before insert or update on tax_postings
158
+ for each row execute function tax_posting_boxes_agree();
159
+
160
+ -- A trigger function runs as the statement that fired it and is called by
161
+ -- nobody directly, so it is open to nobody.
162
+ revoke execute on function tax_posting_boxes_agree() from public, anon;
163
+
164
+ -- ---------------------------------------------------------------------------
165
+ -- Installing a pack copies the list
166
+ -- ---------------------------------------------------------------------------
167
+ --
168
+ -- Replaced whole rather than patched, because a function is replaced whole in
169
+ -- PostgreSQL. Everything but the insert into `tax_postings` is `20260914143915`
170
+ -- unchanged.
171
+
172
+ create or replace function install_country_template(
173
+ p_company_id uuid,
174
+ p_country char(2),
175
+ p_language char(2) default null,
176
+ p_chart_code text default null
177
+ )
178
+ returns void
179
+ language plpgsql
180
+ as $$
181
+ declare
182
+ v_defaults country_defaults%rowtype;
183
+ r record;
184
+ v_tax_id uuid;
185
+ v_language char(2);
186
+ v_version text;
187
+ v_chart text;
188
+ v_charts text;
189
+ begin
190
+ if not exists (select 1 from companies where id = p_company_id) then
191
+ raise exception 'unknown_company: %', p_company_id;
192
+ end if;
193
+
194
+ -- Which chart. The caller names one, or the pack's default stands. A named
195
+ -- chart that does not exist is an error that lists what does: guessing here
196
+ -- would install the wrong plan of accounts and nobody would notice for a
197
+ -- year.
198
+ if p_chart_code is null then
199
+ select c.code into v_chart
200
+ from chart_templates c
201
+ where c.country = p_country and c.is_default;
202
+ if v_chart is null then
203
+ -- No pack for this country, or a pack that names no default chart.
204
+ raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
205
+ end if;
206
+ else
207
+ select c.code into v_chart
208
+ from chart_templates c
209
+ where c.country = p_country and c.code = p_chart_code;
210
+ if v_chart is null then
211
+ select string_agg(c.code, ', ' order by c.code) into v_charts
212
+ from chart_templates c where c.country = p_country;
213
+ raise exception 'unknown_chart: % has no chart %. It has: %',
214
+ p_country, p_chart_code, coalesce(v_charts, 'none');
215
+ end if;
216
+ end if;
217
+
218
+ if not exists (
219
+ select 1 from account_templates
220
+ where country = p_country and chart_code = v_chart
221
+ ) then
222
+ raise exception 'unknown_country_template: chart % of % holds no account', v_chart, p_country;
223
+ end if;
224
+
225
+ -- The language of the copy: what the caller asked for, else what the
226
+ -- company keeps its books in.
227
+ select coalesce(p_language, c.language) into v_language
228
+ from companies c where c.id = p_company_id;
229
+
230
+ -- 1. Accounts, without the hierarchy.
231
+ insert into accounts (company_id, code, name, name_i18n, statement_hint,
232
+ account_type, reconcilable)
233
+ select p_company_id, t.code,
234
+ label_for(t.name, t.name_i18n, array[v_language]),
235
+ t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
236
+ from account_templates t
237
+ where t.country = p_country
238
+ and t.chart_code = v_chart
239
+ on conflict (company_id, code) do nothing;
240
+
241
+ -- 2. Hierarchy, now that every code exists.
242
+ update accounts a
243
+ set parent_id = p.id
244
+ from account_templates t
245
+ join accounts p on p.company_id = p_company_id and p.code = t.parent_code
246
+ where a.company_id = p_company_id
247
+ and a.code = t.code
248
+ and t.country = p_country
249
+ and t.chart_code = v_chart
250
+ and t.parent_code is not null
251
+ and a.parent_id is null;
252
+
253
+ -- 3. Journals. Common to every chart of the country.
254
+ insert into journals (company_id, code, name, name_i18n, journal_type)
255
+ select p_company_id, t.code,
256
+ label_for(t.name, t.name_i18n, array[v_language]),
257
+ t.name_i18n, t.journal_type
258
+ from journal_templates t
259
+ where t.country = p_country
260
+ on conflict (company_id, code) do nothing;
261
+
262
+ -- 4. Taxes and their postings. Also common: a chart changes how a company
263
+ -- presents itself, not what it owes.
264
+ for r in
265
+ select * from tax_templates where country = p_country order by sequence, code
266
+ loop
267
+ v_tax_id := null;
268
+ insert into taxes (company_id, code, name, name_i18n, description, amount_type, amount,
269
+ applies_to, treatment, country, valid_from, valid_to,
270
+ legal_reference, vat_category, exemption_code, sequence,
271
+ tax_kind, recoverable, jurisdiction, price_include,
272
+ cash_basis, cash_basis_transition_account_id)
273
+ values (p_company_id, r.code,
274
+ label_for(r.name, r.name_i18n, array[v_language]),
275
+ r.name_i18n, r.description, r.amount_type, r.amount,
276
+ r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
277
+ r.legal_reference, r.vat_category, r.exemption_code, r.sequence,
278
+ r.tax_kind, r.recoverable, r.jurisdiction, r.price_include,
279
+ r.cash_basis, account_id_by_code(p_company_id, r.cash_basis_transition_account_code))
280
+ on conflict (company_id, code) do nothing
281
+ returning id into v_tax_id;
282
+
283
+ if v_tax_id is null then
284
+ continue;
285
+ end if;
286
+
287
+ insert into tax_postings (tax_id, company_id, document_kind, posting_type,
288
+ factor_percent, account_id, declaration_box,
289
+ declaration_boxes, box_factor_percent, report_code,
290
+ sequence)
291
+ select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
292
+ tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
293
+ tp.declaration_box, tp.declaration_boxes, tp.box_factor_percent,
294
+ tp.report_code, tp.sequence
295
+ from tax_posting_templates tp
296
+ where tp.tax_template_id = r.id
297
+ order by tp.sequence;
298
+ end loop;
299
+
300
+ -- 5. Roles.
301
+ select * into v_defaults from country_defaults where country = p_country;
302
+ if found then
303
+ update companies c
304
+ set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
305
+ payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
306
+ suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
307
+ rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
308
+ retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
309
+ default_sales_account_id = coalesce(c.default_sales_account_id, account_id_by_code(p_company_id, v_defaults.sales_account_code)),
310
+ default_purchase_account_id = coalesce(c.default_purchase_account_id, account_id_by_code(p_company_id, v_defaults.purchase_account_code)),
311
+ sales_journal_id = coalesce(c.sales_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.sales_journal_code)),
312
+ purchase_journal_id = coalesce(c.purchase_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.purchase_journal_code)),
313
+ miscellaneous_journal_id = coalesce(c.miscellaneous_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.misc_journal_code))
314
+ where c.id = p_company_id;
315
+
316
+ -- 6. The financial journals point at their account.
317
+ update journals j
318
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
319
+ where j.company_id = p_company_id
320
+ and j.journal_type = 'bank'
321
+ and j.default_account_id is null
322
+ and v_defaults.bank_account_code is not null;
323
+
324
+ update journals j
325
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
326
+ where j.company_id = p_company_id
327
+ and j.journal_type = 'cash'
328
+ and j.default_account_id is null
329
+ and v_defaults.cash_account_code is not null;
330
+ end if;
331
+
332
+ -- 7. What was copied, from which version, and on which chart.
333
+ v_version := coalesce((select version from country_packs where country = p_country), '1.0.0');
334
+
335
+ insert into company_packs (company_id, country, version, chart_code)
336
+ values (p_company_id, p_country, v_version, v_chart)
337
+ on conflict (company_id, country) do update
338
+ set version = excluded.version,
339
+ chart_code = excluded.chart_code,
340
+ upgraded_at = now()
341
+ where company_packs.version is distinct from excluded.version
342
+ or company_packs.chart_code is distinct from excluded.chart_code;
343
+
344
+ -- 8. The working chart. Everything wired above is pinned, so a company opens
345
+ -- on the accounts its country model actually points at rather than on the
346
+ -- whole transcription.
347
+ perform pin_referenced_accounts(p_company_id);
348
+ end;
349
+ $$;
350
+
351
+
352
+ comment on function install_country_template(uuid, char, char, text) is
353
+ 'Copies one chart of a country pack into a company in one language, with the country''s journals and taxes, wires the default roles, pins the accounts it wired, and records the pack version and the chart in company_packs. A tax posting is copied with every declaration box it prints in.';
354
+
355
+ revoke execute on function install_country_template(uuid, char, char, text) from public, anon;
356
+ grant execute on function install_country_template(uuid, char, char, text) to authenticated, service_role;
357
+
358
+ -- ---------------------------------------------------------------------------
359
+ -- The upgrade compares the list and carries it
360
+ -- ---------------------------------------------------------------------------
361
+ --
362
+ -- A tax that starts printing its base in a second box is a difference like any
363
+ -- other, and one a company has to be told about: without the list in the diff,
364
+ -- an upgrade would report "nothing to do" and the return would stay short by a
365
+ -- box. The postings of a tax are compared as one fact, so the list joins the
366
+ -- object the diff already builds. Everything else in both functions is
367
+ -- `20260914111907` and `20260914152840` unchanged.
368
+
369
+
370
+ create or replace function pack_upgrade_diff(
371
+ p_company_id uuid,
372
+ p_country char(2) default null
373
+ )
374
+ returns table (
375
+ object text,
376
+ key text,
377
+ change text,
378
+ rule pack_change_rule,
379
+ detail jsonb
380
+ )
381
+ language plpgsql
382
+ stable
383
+ as $$
384
+ declare
385
+ v_country char(2);
386
+ v_chart text;
387
+ begin
388
+ select coalesce(p_country, c.country) into v_country
389
+ from companies c where c.id = p_company_id;
390
+ if v_country is null then
391
+ raise exception 'unknown_company: %', p_company_id;
392
+ end if;
393
+
394
+ select cp.chart_code into v_chart
395
+ from company_packs cp
396
+ where cp.company_id = p_company_id and cp.country = v_country;
397
+ if v_chart is null then
398
+ raise exception 'no_pack_installed: company % holds no % pack, so there is nothing to upgrade from', p_company_id, v_country;
399
+ end if;
400
+
401
+ -- Accounts -----------------------------------------------------------------
402
+ return query
403
+ select 'account', t.code, 'missing', 'addition'::pack_change_rule,
404
+ jsonb_build_object('pack', jsonb_build_object('name', t.name, 'account_type', t.account_type))
405
+ from account_templates t
406
+ where t.country = v_country and t.chart_code = v_chart
407
+ and not exists (select 1 from accounts a where a.company_id = p_company_id and a.code = t.code);
408
+
409
+ return query
410
+ select 'account', a.code, 'differs', 'review'::pack_change_rule,
411
+ jsonb_build_object(
412
+ 'pack', jsonb_build_object('name', t.name, 'account_type', t.account_type),
413
+ 'company', jsonb_build_object('name', a.name, 'account_type', a.account_type))
414
+ from accounts a
415
+ join account_templates t
416
+ on t.country = v_country and t.chart_code = v_chart and t.code = a.code
417
+ where a.company_id = p_company_id
418
+ and (label_for(t.name, t.name_i18n, preferred_languages(p_company_id)) is distinct from a.name
419
+ or t.account_type is distinct from a.account_type);
420
+
421
+ return query
422
+ select 'account', a.code, 'company_only', 'review'::pack_change_rule,
423
+ jsonb_build_object('company', jsonb_build_object('name', a.name, 'account_type', a.account_type))
424
+ from accounts a
425
+ where a.company_id = p_company_id
426
+ and not exists (select 1 from account_templates t
427
+ where t.country = v_country and t.chart_code = v_chart and t.code = a.code);
428
+
429
+ -- Journals -----------------------------------------------------------------
430
+ return query
431
+ select 'journal', t.code, 'missing', 'addition'::pack_change_rule,
432
+ jsonb_build_object('pack', jsonb_build_object('name', t.name, 'journal_type', t.journal_type))
433
+ from journal_templates t
434
+ where t.country = v_country
435
+ and not exists (select 1 from journals j where j.company_id = p_company_id and j.code = t.code);
436
+
437
+ return query
438
+ select 'journal', j.code, 'differs', 'review'::pack_change_rule,
439
+ jsonb_build_object(
440
+ 'pack', jsonb_build_object('name', t.name, 'journal_type', t.journal_type),
441
+ 'company', jsonb_build_object('name', j.name, 'journal_type', j.journal_type))
442
+ from journals j
443
+ join journal_templates t on t.country = v_country and t.code = j.code
444
+ where j.company_id = p_company_id
445
+ and (t.name is distinct from j.name or t.journal_type is distinct from j.journal_type);
446
+
447
+ -- Taxes --------------------------------------------------------------------
448
+ return query
449
+ select 'tax', t.code, 'missing', 'addition'::pack_change_rule,
450
+ jsonb_build_object('pack', jsonb_build_object(
451
+ 'name', t.name, 'amount', t.amount, 'amount_type', t.amount_type,
452
+ 'valid_from', t.valid_from, 'valid_to', t.valid_to))
453
+ from tax_templates t
454
+ where t.country = v_country
455
+ and not exists (select 1 from taxes x where x.company_id = p_company_id and x.code = t.code);
456
+
457
+ -- A validity the pack has closed and the company still holds open, or holds
458
+ -- open longer. This is a rate change arriving: the old tax stops on the day
459
+ -- the pack says, and the new rate is an addition above.
460
+ return query
461
+ select 'tax', x.code, 'valid_to', 'closure'::pack_change_rule,
462
+ jsonb_build_object('pack', jsonb_build_object('valid_to', t.valid_to),
463
+ 'company', jsonb_build_object('valid_to', x.valid_to))
464
+ from taxes x
465
+ join tax_templates t on t.country = v_country and t.code = x.code
466
+ where x.company_id = p_company_id
467
+ and t.valid_to is not null
468
+ and (x.valid_to is null or x.valid_to > t.valid_to);
469
+
470
+ return query
471
+ select 'tax', x.code, 'differs', 'review'::pack_change_rule,
472
+ jsonb_build_object(
473
+ 'pack', jsonb_build_object('name', t.name, 'amount', t.amount,
474
+ 'amount_type', t.amount_type, 'treatment', t.treatment,
475
+ 'applies_to', t.applies_to, 'tax_kind', t.tax_kind,
476
+ 'recoverable', t.recoverable, 'valid_from', t.valid_from),
477
+ 'company', jsonb_build_object('name', x.name, 'amount', x.amount,
478
+ 'amount_type', x.amount_type, 'treatment', x.treatment,
479
+ 'applies_to', x.applies_to, 'tax_kind', x.tax_kind,
480
+ 'recoverable', x.recoverable, 'valid_from', x.valid_from))
481
+ from taxes x
482
+ join tax_templates t on t.country = v_country and t.code = x.code
483
+ where x.company_id = p_company_id
484
+ and (label_for(t.name, null, preferred_languages(p_company_id)) is distinct from x.name
485
+ or t.amount is distinct from x.amount
486
+ or t.amount_type is distinct from x.amount_type
487
+ or t.treatment is distinct from x.treatment
488
+ or t.applies_to is distinct from x.applies_to
489
+ or t.tax_kind is distinct from x.tax_kind
490
+ or t.recoverable is distinct from x.recoverable
491
+ or t.valid_from is distinct from x.valid_from);
492
+
493
+ return query
494
+ select 'tax', x.code, 'company_only', 'review'::pack_change_rule,
495
+ jsonb_build_object('company', jsonb_build_object('name', x.name, 'amount', x.amount))
496
+ from taxes x
497
+ where x.company_id = p_company_id
498
+ and x.country = v_country
499
+ and not exists (select 1 from tax_templates t where t.country = v_country and t.code = x.code);
500
+
501
+ -- What a tax books. Compared as a whole per tax rather than line by line: a
502
+ -- posting has no identity of its own, and "this tax now books its
503
+ -- non-deductible share somewhere else" is one fact, not four.
504
+ return query
505
+ with pack as (
506
+ select t.code,
507
+ jsonb_agg(jsonb_build_object('document_kind', tp.document_kind,
508
+ 'posting_type', tp.posting_type,
509
+ 'factor_percent', tp.factor_percent,
510
+ 'account_code', tp.account_code,
511
+ 'declaration_box', tp.declaration_box,
512
+ 'declaration_boxes', to_jsonb(tp.declaration_boxes),
513
+ 'box_factor_percent', tp.box_factor_percent,
514
+ 'report_code', tp.report_code)
515
+ order by tp.document_kind, tp.posting_type, tp.sequence) as postings
516
+ from tax_templates t
517
+ join tax_posting_templates tp on tp.tax_template_id = t.id
518
+ where t.country = v_country
519
+ group by t.code
520
+ ), held as (
521
+ select x.code,
522
+ jsonb_agg(jsonb_build_object('document_kind', p.document_kind,
523
+ 'posting_type', p.posting_type,
524
+ 'factor_percent', p.factor_percent,
525
+ 'account_code', a.code,
526
+ 'declaration_box', p.declaration_box,
527
+ 'declaration_boxes', to_jsonb(p.declaration_boxes),
528
+ 'box_factor_percent', p.box_factor_percent,
529
+ 'report_code', p.report_code)
530
+ order by p.document_kind, p.posting_type, p.sequence) as postings
531
+ from taxes x
532
+ join tax_postings p on p.tax_id = x.id
533
+ left join accounts a on a.id = p.account_id
534
+ where x.company_id = p_company_id
535
+ group by x.code
536
+ )
537
+ select 'tax_posting', pack.code, 'differs', 'review'::pack_change_rule,
538
+ jsonb_build_object('pack', pack.postings, 'company', held.postings)
539
+ from pack
540
+ join held on held.code = pack.code
541
+ where pack.postings is distinct from held.postings;
542
+ end;
543
+ $$;
544
+
545
+
546
+ comment on function pack_upgrade_diff(uuid, char) is
547
+ 'What separates a company from the country pack this installation now holds, by natural key, each difference carrying the rule that decides what an upgrade does with it. What a tax books is compared as one object, every declaration box of every posting included.';
548
+
549
+ revoke execute on function pack_upgrade_diff(uuid, char) from public, anon;
550
+ grant execute on function pack_upgrade_diff(uuid, char) to authenticated, service_role;
551
+
552
+
553
+ create or replace function pack_upgrade(
554
+ p_company_id uuid,
555
+ p_country char(2) default null,
556
+ p_apply boolean default false
557
+ )
558
+ returns jsonb
559
+ language plpgsql
560
+ security definer
561
+ set search_path = public, pg_temp
562
+ as $$
563
+ declare
564
+ v_country char(2);
565
+ v_chart text;
566
+ v_from text;
567
+ v_to text;
568
+ v_applied jsonb := '[]'::jsonb;
569
+ v_listed jsonb := '[]'::jsonb;
570
+ v_refused jsonb := '[]'::jsonb;
571
+ d record;
572
+ v_tax_id uuid;
573
+ v_template tax_templates%rowtype;
574
+ begin
575
+ select coalesce(p_country, c.country) into v_country
576
+ from companies c where c.id = p_company_id;
577
+ if v_country is null then
578
+ raise exception 'unknown_company: %', p_company_id;
579
+ end if;
580
+
581
+ if auth.uid() is not null and not has_capability(p_company_id, 'company.write') then
582
+ raise exception 'not_allowed: upgrading a country pack needs company.write';
583
+ end if;
584
+
585
+ select cp.version, cp.chart_code into v_from, v_chart
586
+ from company_packs cp
587
+ where cp.company_id = p_company_id and cp.country = v_country;
588
+ if v_chart is null then
589
+ raise exception 'no_pack_installed: company % holds no % pack, so there is nothing to upgrade from', p_company_id, v_country;
590
+ end if;
591
+
592
+ select version into v_to from country_packs where country = v_country;
593
+ if v_to is null then
594
+ raise exception 'no_pack_loaded: this installation holds no % pack. Apply the seeds of this release first.', v_country;
595
+ end if;
596
+
597
+ for d in select * from pack_upgrade_diff(p_company_id, v_country) loop
598
+ -- Never, whatever the caller asked. An upgrade adds and closes; it does
599
+ -- not take a row out of a company's books.
600
+ if d.change = 'company_only' then
601
+ v_refused := v_refused || jsonb_build_object('object', d.object, 'key', d.key,
602
+ 'change', d.change, 'rule', d.rule,
603
+ 'detail', d.detail);
604
+ continue;
605
+ end if;
606
+
607
+ if d.rule = 'review' and not p_apply then
608
+ v_listed := v_listed || jsonb_build_object('object', d.object, 'key', d.key,
609
+ 'change', d.change, 'rule', d.rule,
610
+ 'detail', d.detail);
611
+ continue;
612
+ end if;
613
+
614
+ if d.object = 'account' and d.change = 'missing' then
615
+ insert into accounts (company_id, code, name, name_i18n, statement_hint, account_type, reconcilable)
616
+ select p_company_id, t.code, label_for(t.name, t.name_i18n, preferred_languages(p_company_id)),
617
+ t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
618
+ from account_templates t
619
+ where t.country = v_country and t.chart_code = v_chart and t.code = d.key
620
+ on conflict (company_id, code) do nothing;
621
+
622
+ elsif d.object = 'account' and d.change = 'differs' then
623
+ update accounts a
624
+ set name = label_for(t.name, t.name_i18n, preferred_languages(p_company_id)),
625
+ name_i18n = t.name_i18n,
626
+ account_type = t.account_type
627
+ from account_templates t
628
+ where t.country = v_country and t.chart_code = v_chart and t.code = a.code
629
+ and a.company_id = p_company_id and a.code = d.key;
630
+
631
+ elsif d.object = 'journal' and d.change = 'missing' then
632
+ insert into journals (company_id, code, name, journal_type)
633
+ select p_company_id, t.code, t.name, t.journal_type
634
+ from journal_templates t
635
+ where t.country = v_country and t.code = d.key
636
+ on conflict (company_id, code) do nothing;
637
+
638
+ elsif d.object = 'journal' and d.change = 'differs' then
639
+ update journals j
640
+ set name = t.name, journal_type = t.journal_type
641
+ from journal_templates t
642
+ where t.country = v_country and t.code = j.code
643
+ and j.company_id = p_company_id and j.code = d.key;
644
+
645
+ elsif d.object = 'tax' and d.change = 'missing' then
646
+ select * into v_template from tax_templates where country = v_country and code = d.key;
647
+ insert into taxes (company_id, code, name, description, amount_type, amount,
648
+ applies_to, treatment, country, valid_from, valid_to,
649
+ legal_reference, vat_category, exemption_code, sequence,
650
+ tax_kind, recoverable, jurisdiction, price_include,
651
+ cash_basis, cash_basis_transition_account_id)
652
+ values (p_company_id, v_template.code, v_template.name, v_template.description,
653
+ v_template.amount_type, v_template.amount, v_template.applies_to,
654
+ v_template.treatment, v_country, v_template.valid_from, v_template.valid_to,
655
+ v_template.legal_reference, v_template.vat_category, v_template.exemption_code,
656
+ v_template.sequence, v_template.tax_kind, v_template.recoverable,
657
+ v_template.jurisdiction, v_template.price_include, v_template.cash_basis,
658
+ account_id_by_code(p_company_id, v_template.cash_basis_transition_account_code))
659
+ on conflict (company_id, code) do nothing
660
+ returning id into v_tax_id;
661
+
662
+ if v_tax_id is not null then
663
+ insert into tax_postings (tax_id, company_id, document_kind, posting_type,
664
+ factor_percent, account_id, declaration_box,
665
+ declaration_boxes, box_factor_percent, report_code,
666
+ sequence)
667
+ select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
668
+ tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
669
+ tp.declaration_box, tp.declaration_boxes, tp.box_factor_percent,
670
+ tp.report_code, tp.sequence
671
+ from tax_posting_templates tp
672
+ where tp.tax_template_id = v_template.id
673
+ order by tp.sequence;
674
+ end if;
675
+
676
+ elsif d.object = 'tax' and d.change = 'valid_to' then
677
+ update taxes x
678
+ set valid_to = t.valid_to
679
+ from tax_templates t
680
+ where t.country = v_country and t.code = x.code
681
+ and x.company_id = p_company_id and x.code = d.key;
682
+
683
+ elsif d.object = 'tax' and d.change = 'differs' then
684
+ update taxes x
685
+ set name = t.name, description = t.description, amount = t.amount,
686
+ amount_type = t.amount_type, applies_to = t.applies_to,
687
+ treatment = t.treatment, valid_from = t.valid_from,
688
+ legal_reference = t.legal_reference, vat_category = t.vat_category,
689
+ exemption_code = t.exemption_code, tax_kind = t.tax_kind,
690
+ recoverable = t.recoverable, jurisdiction = t.jurisdiction,
691
+ price_include = t.price_include, cash_basis = t.cash_basis
692
+ from tax_templates t
693
+ where t.country = v_country and t.code = x.code
694
+ and x.company_id = p_company_id and x.code = d.key;
695
+
696
+ elsif d.object = 'tax_posting' and d.change = 'differs' then
697
+ select id into v_tax_id from taxes where company_id = p_company_id and code = d.key;
698
+ delete from tax_postings where tax_id = v_tax_id;
699
+ insert into tax_postings (tax_id, company_id, document_kind, posting_type,
700
+ factor_percent, account_id, declaration_box,
701
+ declaration_boxes, box_factor_percent, report_code,
702
+ sequence)
703
+ select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
704
+ tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
705
+ tp.declaration_box, tp.declaration_boxes, tp.box_factor_percent,
706
+ tp.report_code, tp.sequence
707
+ from tax_posting_templates tp
708
+ join tax_templates t on t.id = tp.tax_template_id
709
+ where t.country = v_country and t.code = d.key
710
+ order by tp.sequence;
711
+
712
+ else
713
+ -- A difference this function does not know how to apply is listed
714
+ -- rather than guessed at. Raising here would make a new kind of
715
+ -- difference break every upgrade.
716
+ v_listed := v_listed || jsonb_build_object('object', d.object, 'key', d.key,
717
+ 'change', d.change, 'rule', d.rule,
718
+ 'detail', d.detail);
719
+ continue;
720
+ end if;
721
+
722
+ v_applied := v_applied || jsonb_build_object('object', d.object, 'key', d.key,
723
+ 'change', d.change, 'rule', d.rule,
724
+ 'detail', d.detail);
725
+ end loop;
726
+
727
+ -- The version moves only when nothing is left waiting. A company that still
728
+ -- holds a difference it has not decided on has not finished upgrading, and
729
+ -- saying otherwise would hide the difference at the next run.
730
+ if jsonb_array_length(v_listed) = 0 then
731
+ update company_packs
732
+ set version = v_to, upgraded_at = now()
733
+ where company_id = p_company_id and country = v_country;
734
+ end if;
735
+
736
+ perform audit_record(
737
+ p_company_id, 'company_packs', null, v_country, 'update', 'pack_upgraded',
738
+ jsonb_build_object('version', v_from),
739
+ jsonb_build_object('version', case when jsonb_array_length(v_listed) = 0 then v_to else v_from end,
740
+ 'pack_version', v_to,
741
+ 'chart_code', v_chart,
742
+ 'applied', v_applied,
743
+ 'listed', v_listed,
744
+ 'never_applied', v_refused));
745
+
746
+ return jsonb_build_object(
747
+ 'company_id', p_company_id,
748
+ 'country', v_country,
749
+ 'chart_code', v_chart,
750
+ 'from_version', v_from,
751
+ 'to_version', v_to,
752
+ 'version_moved', jsonb_array_length(v_listed) = 0,
753
+ 'applied', v_applied,
754
+ 'listed', v_listed,
755
+ 'never_applied', v_refused);
756
+ end;
757
+ $$;
758
+
759
+
760
+ revoke execute on function pack_upgrade(uuid, char, boolean) from public, anon;
761
+ grant execute on function pack_upgrade(uuid, char, boolean) to authenticated, service_role;
762
+
763
+ -- ---------------------------------------------------------------------------
764
+ -- The return sums a line into every box its posting names
765
+ -- ---------------------------------------------------------------------------
766
+ --
767
+ -- One `unnest`, and the rest of `20260914163943` unchanged. The amount is the
768
+ -- same in each box: `box_factor_percent` is the share of the amount the form
769
+ -- expects, and a form that prints one figure in three places prints the same
770
+ -- figure in all three. A form that wants a fraction of it in one of them is
771
+ -- printing a different figure, which is a second posting or a total.
772
+ --
773
+ -- A parent that *is* a sum stays a total and is untouched: a posting that
774
+ -- named both a box and the total above it would be counted twice, which is
775
+ -- why `ekwo pack check` refuses a posting that names a total at all.
776
+
777
+
778
+ create or replace function vat_return(
779
+ p_company_id uuid,
780
+ p_from date,
781
+ p_to date,
782
+ p_report_code text default null
783
+ )
784
+ returns table (
785
+ box text,
786
+ kind text,
787
+ amount numeric,
788
+ computed boolean,
789
+ name text,
790
+ sequence integer,
791
+ hidden boolean,
792
+ report_code text
793
+ )
794
+ language plpgsql
795
+ stable
796
+ as $$
797
+ declare
798
+ -- 'box|kind' -> amount, for every box summed from the ledger, then the
799
+ -- totals `evaluate_totals()` derives from them. The key carries the kind
800
+ -- because the French CA3 puts a base and a tax on line 08 and a formula has
801
+ -- to be able to name one of them.
802
+ v_values jsonb := '{}'::jsonb;
803
+ v_formulas jsonb := '[]'::jsonb;
804
+ v_totals jsonb := '{}'::jsonb;
805
+ v_rows jsonb := '[]'::jsonb;
806
+ v_country char(2);
807
+ v_report text;
808
+ v_in char(2);
809
+ v_count integer;
810
+ v_codes text;
811
+ -- How often this company files, what the form accepts, and what the two
812
+ -- dates asked for actually are.
813
+ v_files declaration_period;
814
+ v_accepts declaration_period[];
815
+ v_asked declaration_period;
816
+ -- A declaration figure is not a ledger figure, but it is written in the
817
+ -- same currency and with the same decimals.
818
+ v_round money_rounding;
819
+ r record;
820
+ begin
821
+ select c.fiscal_country, c.vat_period into v_country, v_files
822
+ from companies c where c.id = p_company_id;
823
+ if not found then
824
+ raise exception 'unknown_company: %', p_company_id;
825
+ end if;
826
+ v_round := rounding_of(p_company_id);
827
+
828
+ -- Which form. The caller names one, or the country files exactly one on
829
+ -- that date. Two and no name is a question only the caller can answer — a
830
+ -- Canadian company files the federal return and the Québec one at once — so
831
+ -- this asks instead of guessing.
832
+ if p_report_code is not null then
833
+ select t.country, t.code into v_in, v_report
834
+ from tax_report_templates t
835
+ where t.code = p_report_code
836
+ and t.valid_from <= p_to
837
+ and (t.valid_to is null or t.valid_to >= p_to)
838
+ order by t.country
839
+ limit 1;
840
+ if v_report is null then
841
+ raise exception 'unknown_tax_report: % is not a declaration form in force on %',
842
+ p_report_code, p_to;
843
+ end if;
844
+ else
845
+ select count(*), min(t.code), string_agg(t.code, ', ' order by t.code)
846
+ into v_count, v_report, v_codes
847
+ from tax_report_templates t
848
+ where t.country = v_country
849
+ and t.is_periodic_return
850
+ and t.valid_from <= p_to
851
+ and (t.valid_to is null or t.valid_to >= p_to);
852
+ if v_count > 1 then
853
+ raise exception 'ambiguous_tax_report: % files several declarations on % (%); name one',
854
+ v_country, p_to, v_codes;
855
+ end if;
856
+ v_in := v_country;
857
+ if v_count = 0 then
858
+ v_report := null; -- no pack for this country: the ledger boxes, and no total.
859
+ end if;
860
+ end if;
861
+
862
+ -- The period asked for against the one this company files on. Four things
863
+ -- have to hold before this refuses, and the fourth is what keeps it out of
864
+ -- everybody's way: the company has recorded a cadence, the form is one it
865
+ -- files on that cadence, the dates are themselves a whole cadence of that
866
+ -- form, and the two are not the same. A fortnight, a half-year, the annual
867
+ -- recapitulative form a quarterly filer also files — none of those is a
868
+ -- filing on the wrong cadence, and none of them is refused.
869
+ if v_files is not null and v_report is not null then
870
+ select t.periods into v_accepts
871
+ from tax_report_templates t
872
+ where t.country = v_in and t.code = v_report;
873
+ v_asked := declaration_period_of(p_from, p_to);
874
+ if v_asked is not null
875
+ and v_files = any(v_accepts)
876
+ and v_asked = any(v_accepts)
877
+ and v_asked <> v_files then
878
+ raise exception
879
+ 'wrong_declaration_period: this company files % returns on %; % to % is a %',
880
+ v_files, v_report, p_from, p_to, v_asked;
881
+ end if;
882
+ end if;
883
+
884
+ -- 1. What the tax postings wrote on the ledger, in every box each of them
885
+ -- names. A line carries the box it is known by; the posting behind it
886
+ -- carries the whole list, which is one box for all but the handful of
887
+ -- forms that print a figure twice. No country rule here either: the
888
+ -- expansion is `unnest`, and which boxes there are is the pack's answer.
889
+ for r in
890
+ with lines as (
891
+ select l.declaration_box as lbox,
892
+ case when l.tax_line then 'tax' else 'base' end as lkind,
893
+ l.tax_id as ltax,
894
+ l.box_amount as lamount
895
+ from entry_lines l
896
+ join entries e on e.id = l.entry_id
897
+ where l.company_id = p_company_id
898
+ and e.state = 'posted'
899
+ and e.entry_date between p_from and p_to
900
+ and l.declaration_box is not null
901
+ ),
902
+ -- The posting that wrote the line: which form it is on, and every box it
903
+ -- prints in. A line with no posting behind it — an entry keyed by hand,
904
+ -- a tax a company wrote itself — is on this form and in the one box it
905
+ -- names, which is what the left join leaves.
906
+ sourced as (
907
+ select ln.lbox, ln.lkind, ln.lamount,
908
+ coalesce(p.boxes, array[ln.lbox]) as lboxes
909
+ from lines ln
910
+ left join lateral (
911
+ select min(tp.report_code) as report_code,
912
+ array_agg(distinct b.box) as boxes
913
+ from tax_postings tp
914
+ cross join lateral unnest(tp.declaration_boxes) as b(box)
915
+ where tp.tax_id = ln.ltax
916
+ and tp.declaration_box = ln.lbox
917
+ and tp.posting_type = ln.lkind::tax_posting_type
918
+ ) p on true
919
+ -- A box number belongs to one form. A line whose posting names
920
+ -- another form is not on this declaration; one that names none is
921
+ -- the single-return case every European company is in.
922
+ where v_report is null or coalesce(p.report_code, v_report) = v_report
923
+ ),
924
+ ledger as (
925
+ select x.box as lbox, s.lkind,
926
+ round_amount(sum(s.lamount), v_round) as lamount
927
+ from sourced s
928
+ cross join lateral unnest(s.lboxes) as x(box)
929
+ group by 1, 2
930
+ having round_amount(sum(s.lamount), v_round) <> 0
931
+ )
932
+ select g.lbox, g.lkind, g.lamount, b.name as lname,
933
+ b.sequence as lsequence, coalesce(b.hidden, false) as lhidden
934
+ from ledger g
935
+ left join tax_report_box_templates b
936
+ on b.country = v_in and b.report_code = v_report
937
+ and b.box = g.lbox and b.kind = g.lkind
938
+ order by coalesce(b.sequence, 2147483647), g.lbox, g.lkind
939
+ loop
940
+ v_values := v_values || jsonb_build_object(r.lbox || '|' || r.lkind, r.lamount);
941
+ v_rows := v_rows || jsonb_build_array(jsonb_build_object(
942
+ 'box', r.lbox, 'kind', r.lkind, 'amount', r.lamount, 'computed', false,
943
+ 'name', r.lname, 'sequence', r.lsequence, 'hidden', r.lhidden,
944
+ 'report_code', v_report));
945
+ end loop;
946
+
947
+ -- 2. The totals of the form, through the evaluator the statements use. A
948
+ -- return prints what it has, so a nil total is left out of the answer —
949
+ -- and kept in the working set, so a later total that names it reads a
950
+ -- zero rather than a gap.
951
+ if v_report is not null then
952
+ select coalesce(jsonb_agg(jsonb_build_object(
953
+ 'key', b.box || '|total', 'plus', to_jsonb(b.plus_boxes),
954
+ 'minus', to_jsonb(b.minus_boxes), 'floor_zero', b.floor_zero,
955
+ 'sequence', b.sequence
956
+ ) order by b.sequence, b.box), '[]'::jsonb)
957
+ into v_formulas
958
+ from tax_report_box_templates b
959
+ where b.country = v_in
960
+ and b.report_code = v_report
961
+ and b.kind = 'total'
962
+ and (b.valid_from is null or b.valid_from <= p_to)
963
+ and (b.valid_to is null or b.valid_to >= p_to);
964
+
965
+ v_totals := evaluate_totals(v_values, v_formulas, v_round, false);
966
+
967
+ for r in
968
+ select b.box as tbox, b.name as tname, b.sequence as tsequence, b.hidden as thidden
969
+ from tax_report_box_templates b
970
+ where b.country = v_in
971
+ and b.report_code = v_report
972
+ and b.kind = 'total'
973
+ and (b.valid_from is null or b.valid_from <= p_to)
974
+ and (b.valid_to is null or b.valid_to >= p_to)
975
+ and v_totals ? (b.box || '|total')
976
+ order by b.sequence, b.box
977
+ loop
978
+ v_rows := v_rows || jsonb_build_array(jsonb_build_object(
979
+ 'box', r.tbox, 'kind', 'total',
980
+ 'amount', (v_totals ->> (r.tbox || '|total'))::numeric, 'computed', true,
981
+ 'name', r.tname, 'sequence', r.tsequence, 'hidden', r.thidden,
982
+ 'report_code', v_report));
983
+ end loop;
984
+ end if;
985
+
986
+ return query
987
+ select (x ->> 'box')::text,
988
+ (x ->> 'kind')::text,
989
+ (x ->> 'amount')::numeric,
990
+ (x ->> 'computed')::boolean,
991
+ (x ->> 'name')::text,
992
+ (x ->> 'sequence')::integer,
993
+ (x ->> 'hidden')::boolean,
994
+ (x ->> 'report_code')::text
995
+ from jsonb_array_elements(v_rows) as x;
996
+ end;
997
+ $$;
998
+
999
+
1000
+ comment on function vat_return(uuid, date, date, text) is
1001
+ 'Declaration boxes for a period: summed from the ledger into every box the posting behind each line names, then the totals of the country''s form worked out by evaluate_totals(), the same evaluator financial_statement() uses. Refuses a period the company does not file on, when it has recorded one. No country rule lives in this function.';
1002
+
1003
+ revoke execute on function vat_return(uuid, date, date, text) from public, anon;
1004
+ grant execute on function vat_return(uuid, date, date, text) to authenticated, service_role;