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,85 @@
1
+ -- Ekwo OS — a smaller surface for the anonymous role and for strangers.
2
+ --
3
+ -- Two findings of the security audit of 11 September 2026, both about what
4
+ -- someone who is *not* a member of any company can reach.
5
+ --
6
+ -- 1. Every function in `public` was executable by `anon`, because Postgres
7
+ -- grants EXECUTE to PUBLIC on creation and Supabase exposes `public`
8
+ -- functions as RPC. Row level security made every call return nothing,
9
+ -- but nothing is not the same as no. `anon` keeps EXECUTE on the eight
10
+ -- helpers that row level security policies call — they answer about
11
+ -- `auth.uid()` only, and without them an anonymous SELECT would error
12
+ -- instead of returning an empty set — and loses it on everything else.
13
+ -- The default privileges are changed too, so a function added tomorrow
14
+ -- starts closed.
15
+ --
16
+ -- 2. `instance_admins` was readable by any signed-in user, member or not.
17
+ -- Supabase projects accept self sign-up by default, so "signed in" is a
18
+ -- weaker claim than it sounds. Administrators are now visible to members
19
+ -- of at least one company, to administrators, and to oneself.
20
+ --
21
+ -- Nothing already published is edited: revokes, grants and one policy.
22
+
23
+ -- 1. Functions ------------------------------------------------------------
24
+
25
+ revoke execute on all functions in schema public from public, anon;
26
+ grant execute on all functions in schema public to authenticated, service_role;
27
+
28
+ -- The helpers a policy may evaluate on behalf of an anonymous request.
29
+ grant execute on function company_role(uuid) to anon;
30
+ grant execute on function is_company_member(uuid) to anon;
31
+ grant execute on function can_write_company(uuid) to anon;
32
+ grant execute on function is_company_owner(uuid) to anon;
33
+ grant execute on function is_instance_admin() to anon;
34
+ grant execute on function is_any_company_member() to anon;
35
+ grant execute on function company_has_no_member(uuid) to anon;
36
+ grant execute on function instance_has_no_admin() to anon;
37
+
38
+ alter default privileges in schema public revoke execute on functions from public, anon;
39
+ alter default privileges in schema public grant execute on functions to authenticated, service_role;
40
+
41
+ -- 2. Who may see the administrators ---------------------------------------
42
+
43
+ drop policy if exists instance_admins_select on instance_admins;
44
+ create policy instance_admins_select on instance_admins
45
+ for select
46
+ using (user_id = auth.uid() or is_any_company_member() or is_instance_admin());
47
+
48
+ comment on policy instance_admins_select on instance_admins is
49
+ 'Members, administrators, and oneself. A signed-in stranger sees no one.';
50
+
51
+ -- 3. The claim keeps its clear refusal ------------------------------------
52
+ --
53
+ -- `claim_instance_admin` used to learn whether the seat was taken by reading
54
+ -- `instance_admins` as the caller. A stranger can no longer read it, so the
55
+ -- same call would have fallen through to the policy and answered with a bare
56
+ -- row-level-security error. `instance_has_no_admin()` is the definer helper
57
+ -- made for exactly this question, and it answers it for everyone.
58
+
59
+ create or replace function claim_instance_admin(p_user_id uuid default auth.uid())
60
+ returns instance_admins
61
+ language plpgsql
62
+ as $$
63
+ declare
64
+ v_row instance_admins%rowtype;
65
+ begin
66
+ if p_user_id is null then
67
+ raise exception 'no_user: claim_instance_admin needs a signed-in user or an explicit id';
68
+ end if;
69
+
70
+ if not instance_has_no_admin() and not is_instance_admin() then
71
+ raise exception 'instance_already_claimed: only an instance administrator can appoint another';
72
+ end if;
73
+
74
+ insert into instance_admins (user_id)
75
+ values (p_user_id)
76
+ on conflict (user_id) do nothing
77
+ returning * into v_row;
78
+
79
+ if v_row.user_id is null then
80
+ select * into v_row from instance_admins where user_id = p_user_id;
81
+ end if;
82
+
83
+ return v_row;
84
+ end;
85
+ $$;
@@ -0,0 +1,318 @@
1
+ -- Ekwo OS — a country is a pack, and an installation knows which one it has.
2
+ --
3
+ -- `packs/<cc>/` became the source of a country in the previous change, and
4
+ -- the seeds are now compiled from it. What the schema still could not say is
5
+ -- *which version* of a pack an instance holds and which version a company
6
+ -- copied — so nothing could ever tell an operator that their chart of
7
+ -- accounts is a year behind the pack, and `ekwo pack upgrade` would have had
8
+ -- nothing to compare.
9
+ --
10
+ -- Two tables answer that, and nothing else changes hands:
11
+ --
12
+ -- `country_packs` one row per pack loaded here, written by the generated
13
+ -- seed: version, checksum, and the certification status
14
+ -- that `ekwo init` prints, so an operator knows whether a
15
+ -- professional has ever read the pack they installed.
16
+ -- `company_packs` what each company copied, and when. A company can hold
17
+ -- two packs — a foreign VAT registration is exactly that —
18
+ -- which is why the key is (company, country).
19
+ --
20
+ -- The rest is the translated labels the pack format already carries.
21
+ -- `account_templates.name_i18n` holds every language of the pack;
22
+ -- `install_country_template` copies the one the company asked for into
23
+ -- `name` and keeps the whole object beside it, so renaming a chart into
24
+ -- another language is a data change and never an import. Translations live in
25
+ -- `jsonb` rather than in a table, which is where Odoo landed in v16 for the
26
+ -- same reason: a table would cost a join per label on six tables.
27
+ --
28
+ -- Additive throughout: new tables, `add column if not exists`, and one
29
+ -- function replaced. `install_country_template` gains a third argument, so
30
+ -- the two-argument version is dropped first — an overload with a default
31
+ -- would make a two-argument `install_country_template` call ambiguous, and
32
+ -- Postgres would refuse the call that works today.
33
+
34
+ -- ---------------------------------------------------------------------------
35
+ -- country_packs — what this instance holds
36
+ -- ---------------------------------------------------------------------------
37
+
38
+ do $$
39
+ begin
40
+ if not exists (select 1 from pg_type where typname = 'pack_certification') then
41
+ create type pack_certification as enum ('community', 'reviewed', 'ekwo');
42
+ end if;
43
+ end;
44
+ $$;
45
+
46
+ comment on type pack_certification is
47
+ 'How much a pack has been read: contributed, read by a named professional, or maintained by Ekwo.';
48
+
49
+ create table if not exists country_packs (
50
+ country char(2) primary key,
51
+ name text not null,
52
+ version text not null,
53
+ released_at date,
54
+ schema_min text,
55
+ certification_status pack_certification not null default 'community',
56
+ certified_by text,
57
+ certified_at date,
58
+ checksum text,
59
+ installed_at timestamptz not null default now(),
60
+ constraint country_packs_country_format check (country ~ '^[A-Z]{2}$'),
61
+ constraint country_packs_version_semver check (version ~ '^[0-9]+\.[0-9]+\.[0-9]+$')
62
+ );
63
+
64
+ comment on table country_packs is 'Country packs loaded in this installation, with their version and certification.';
65
+ comment on column country_packs.checksum is 'sha256 of the pack files, so a changed pack is visible without a diff.';
66
+ comment on column country_packs.certification_status is 'Printed by `ekwo init`: a community pack has not been read by an accountant.';
67
+
68
+ -- ---------------------------------------------------------------------------
69
+ -- company_packs — what each company copied
70
+ -- ---------------------------------------------------------------------------
71
+
72
+ create table if not exists company_packs (
73
+ company_id uuid not null references companies(id) on delete cascade,
74
+ country char(2) not null,
75
+ version text not null,
76
+ installed_at timestamptz not null default now(),
77
+ upgraded_at timestamptz,
78
+ primary key (company_id, country),
79
+ constraint company_packs_country_format check (country ~ '^[A-Z]{2}$'),
80
+ constraint company_packs_version_semver check (version ~ '^[0-9]+\.[0-9]+\.[0-9]+$')
81
+ );
82
+
83
+ comment on table company_packs is 'Which version of which country pack a company copied. A company may hold two: a foreign VAT registration is one.';
84
+ comment on column company_packs.upgraded_at is 'Last time `install_country_template` or `ekwo pack upgrade` moved this company to another version.';
85
+
86
+ create index if not exists company_packs_country_idx on company_packs (country);
87
+
88
+ -- ---------------------------------------------------------------------------
89
+ -- Translated labels, and the line an account reports on
90
+ -- ---------------------------------------------------------------------------
91
+
92
+ alter table account_templates
93
+ add column if not exists name_i18n jsonb not null default '{}'::jsonb,
94
+ add column if not exists statement_hint text;
95
+
96
+ alter table accounts
97
+ add column if not exists name_i18n jsonb not null default '{}'::jsonb,
98
+ add column if not exists statement_hint text;
99
+
100
+ comment on column account_templates.name_i18n is 'Label by language, from packs/<cc>/i18n/. The pack''s own language stays in `name`.';
101
+ comment on column accounts.name_i18n is 'Label by language, copied from the template at install. `name` holds the language the company chose.';
102
+ comment on column account_templates.statement_hint is 'Statement line this account falls under when no rule catches it. Read by financial_statement() (P0-4).';
103
+ comment on column accounts.statement_hint is 'Statement line this account falls under when no rule catches it. Read by financial_statement() (P0-4).';
104
+
105
+ alter table companies
106
+ add column if not exists language char(2) not null default 'fr';
107
+
108
+ comment on column companies.language is
109
+ 'Language this company keeps its books in. Chosen at install; decides which label of name_i18n lands in accounts.name.';
110
+
111
+ alter table country_defaults
112
+ add column if not exists language_default char(2);
113
+
114
+ comment on column country_defaults.language_default is
115
+ 'Language `ekwo init` offers for a company of this country, before the company row exists — like currency_code, and for the same reason.';
116
+
117
+ -- No backfill here. A pack fills this column, and the compiled seed of every
118
+ -- country upserts `country_defaults`, so re-applying the seeds is what gives
119
+ -- an existing installation the value. Naming a language for a country in a
120
+ -- migration would put a country back into the core.
121
+
122
+ -- ---------------------------------------------------------------------------
123
+ -- install_country_template, in a language, recording what it copied
124
+ -- ---------------------------------------------------------------------------
125
+
126
+ drop function if exists install_country_template(uuid, char);
127
+
128
+ create or replace function install_country_template(
129
+ p_company_id uuid,
130
+ p_country char(2),
131
+ p_language char(2) default null
132
+ )
133
+ returns void
134
+ language plpgsql
135
+ as $$
136
+ declare
137
+ v_defaults country_defaults%rowtype;
138
+ r record;
139
+ v_tax_id uuid;
140
+ v_language char(2);
141
+ v_version text;
142
+ begin
143
+ if not exists (select 1 from companies where id = p_company_id) then
144
+ raise exception 'unknown_company: %', p_company_id;
145
+ end if;
146
+ if not exists (select 1 from account_templates where country = p_country) then
147
+ raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
148
+ end if;
149
+
150
+ -- The language of the copy: what the caller asked for, else what the
151
+ -- company keeps its books in. `companies.language` is never null, so there
152
+ -- is always an answer, and a label missing from a pack falls back to the
153
+ -- pack's own language rather than to nothing.
154
+ select coalesce(p_language, c.language) into v_language
155
+ from companies c where c.id = p_company_id;
156
+
157
+ -- 1. Accounts, without the hierarchy.
158
+ insert into accounts (company_id, code, name, name_i18n, statement_hint,
159
+ account_type, reconcilable)
160
+ select p_company_id, t.code,
161
+ coalesce(nullif(t.name_i18n ->> v_language, ''), t.name),
162
+ t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
163
+ from account_templates t
164
+ where t.country = p_country
165
+ on conflict (company_id, code) do nothing;
166
+
167
+ -- 2. Hierarchy, now that every code exists.
168
+ update accounts a
169
+ set parent_id = p.id
170
+ from account_templates t
171
+ join accounts p on p.company_id = p_company_id and p.code = t.parent_code
172
+ where a.company_id = p_company_id
173
+ and a.code = t.code
174
+ and t.country = p_country
175
+ and t.parent_code is not null
176
+ and a.parent_id is null;
177
+
178
+ -- 3. Journals.
179
+ insert into journals (company_id, code, name, journal_type)
180
+ select p_company_id, t.code, t.name, t.journal_type
181
+ from journal_templates t
182
+ where t.country = p_country
183
+ on conflict (company_id, code) do nothing;
184
+
185
+ -- 4. Taxes and their postings.
186
+ for r in
187
+ select * from tax_templates where country = p_country order by sequence, code
188
+ loop
189
+ v_tax_id := null;
190
+ insert into taxes (company_id, code, name, description, amount_type, amount,
191
+ applies_to, treatment, country, valid_from, valid_to,
192
+ legal_reference, vat_category, exemption_code, sequence)
193
+ values (p_company_id, r.code, r.name, r.description, r.amount_type, r.amount,
194
+ r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
195
+ r.legal_reference, r.vat_category, r.exemption_code, r.sequence)
196
+ on conflict (company_id, code) do nothing
197
+ returning id into v_tax_id;
198
+
199
+ if v_tax_id is null then
200
+ continue;
201
+ end if;
202
+
203
+ insert into tax_postings (tax_id, company_id, document_kind, posting_type,
204
+ factor_percent, account_id, declaration_box,
205
+ box_factor_percent, sequence)
206
+ select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
207
+ tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
208
+ tp.declaration_box, tp.box_factor_percent, tp.sequence
209
+ from tax_posting_templates tp
210
+ where tp.tax_template_id = r.id
211
+ order by tp.sequence;
212
+ end loop;
213
+
214
+ -- 5. Roles.
215
+ select * into v_defaults from country_defaults where country = p_country;
216
+ if found then
217
+ update companies c
218
+ set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
219
+ payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
220
+ suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
221
+ rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
222
+ retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
223
+ default_sales_account_id = coalesce(c.default_sales_account_id, account_id_by_code(p_company_id, v_defaults.sales_account_code)),
224
+ default_purchase_account_id = coalesce(c.default_purchase_account_id, account_id_by_code(p_company_id, v_defaults.purchase_account_code)),
225
+ 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)),
226
+ 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)),
227
+ 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))
228
+ where c.id = p_company_id;
229
+
230
+ -- 6. The financial journals point at their account.
231
+ update journals j
232
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
233
+ where j.company_id = p_company_id
234
+ and j.journal_type = 'bank'
235
+ and j.default_account_id is null
236
+ and v_defaults.bank_account_code is not null;
237
+
238
+ update journals j
239
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
240
+ where j.company_id = p_company_id
241
+ and j.journal_type = 'cash'
242
+ and j.default_account_id is null
243
+ and v_defaults.cash_account_code is not null;
244
+ end if;
245
+
246
+ -- 7. What was copied, and from which version. A pack that has not declared
247
+ -- itself — an instance seeded before this migration — counts as 1.0.0,
248
+ -- which is what every pack of this release is.
249
+ v_version := coalesce((select version from country_packs where country = p_country), '1.0.0');
250
+
251
+ insert into company_packs (company_id, country, version)
252
+ values (p_company_id, p_country, v_version)
253
+ on conflict (company_id, country) do update
254
+ set version = excluded.version,
255
+ upgraded_at = now()
256
+ where company_packs.version is distinct from excluded.version;
257
+ end;
258
+ $$;
259
+
260
+ comment on function install_country_template(uuid, char, char) is
261
+ 'Copies a country pack into a company in one language, wires the default roles and the financial journals, and records the pack version in company_packs.';
262
+
263
+ -- A function created today is *not* closed, and migration `20260911210131`
264
+ -- promised it would be. `alter default privileges ... revoke execute on
265
+ -- functions from public` does not delete the built-in world default on
266
+ -- PostgreSQL 15: `get_user_default_acl` merges the stored default with it, so
267
+ -- a function created afterwards still comes out with `=X` — EXECUTE for
268
+ -- PUBLIC, which on Supabase means an anonymous RPC endpoint. This migration
269
+ -- is the first to create a function since that one, and
270
+ -- `install_country_template` came out open; row level security would have
271
+ -- refused every write it attempts, but the surface should be closed and not
272
+ -- merely empty.
273
+ --
274
+ -- So the revoke is repeated here, and from PUBLIC only: `anon` holds explicit
275
+ -- grants on the eight policy helpers, and revoking from `anon` would take
276
+ -- those away and break every anonymous SELECT the policies evaluate. Any
277
+ -- migration that adds a function has to end with this line — the rule is in
278
+ -- `supabase/migrations/README.md`.
279
+
280
+ revoke execute on all functions in schema public from public;
281
+
282
+ -- ---------------------------------------------------------------------------
283
+ -- Companies installed before this migration
284
+ --
285
+ -- They hold a chart, so they hold a pack; only its version was never written
286
+ -- down. Every pack of this release is 1.0.0, so that is what they copied.
287
+ -- ---------------------------------------------------------------------------
288
+
289
+ insert into company_packs (company_id, country, version, installed_at)
290
+ select c.id, c.country, '1.0.0', c.created_at
291
+ from companies c
292
+ where exists (select 1 from accounts a where a.company_id = c.id)
293
+ on conflict (company_id, country) do nothing;
294
+
295
+ -- ---------------------------------------------------------------------------
296
+ -- Row level security
297
+ --
298
+ -- `country_packs` is reference data about the installation, not about a
299
+ -- company: a member of any company may read it, an administrator may read it
300
+ -- before any company exists. `company_packs` follows its company, and the
301
+ -- right to write it is the right that installs a chart of accounts in the
302
+ -- first place. Neither table has a policy that lets a signed-in stranger see
303
+ -- anything.
304
+ -- ---------------------------------------------------------------------------
305
+
306
+ alter table country_packs enable row level security;
307
+ alter table company_packs enable row level security;
308
+
309
+ create policy country_packs_select on country_packs
310
+ for select using (is_any_company_member() or is_instance_admin());
311
+
312
+ create policy company_packs_select on company_packs
313
+ for select using (is_company_member(company_id));
314
+ create policy company_packs_write on company_packs
315
+ for all using (can_write_company(company_id)) with check (can_write_company(company_id));
316
+
317
+ comment on policy country_packs_select on country_packs is
318
+ 'Members of any company and instance administrators. Written by the generated seed, which runs as the owner.';
@@ -0,0 +1,198 @@
1
+ -- Ekwo OS — which declaration a box belongs to, and which province a party is in.
2
+ --
3
+ -- Two columns the Canadian pack will need and that cost nothing to add now.
4
+ -- Both are phase-0 additions on purpose: adding them with the pack would mean
5
+ -- migrating `tax_postings` and `entry_lines` twice, and the second time they
6
+ -- would already hold years of postings.
7
+ --
8
+ -- **`report_code`.** `declaration_box` is a box number, and a box number is
9
+ -- only unique inside one form. Belgium and France each file one periodic
10
+ -- return, so '59' has never been ambiguous. A Canadian company files two at
11
+ -- once — the GST/HST return to the CRA and, in Québec, the combined return to
12
+ -- Revenu Québec — and line '101' of one is not line '101' of the other.
13
+ -- `report_code` says which form. Null keeps today's meaning: the periodic
14
+ -- return of the company's `fiscal_country`.
15
+ --
16
+ -- **`region`.** Canadian tax follows the *buyer's* province, not the seller's
17
+ -- (place of supply). The core deliberately chooses no tax for anyone; it can
18
+ -- only ever suggest one, and to suggest it has to know where the parties are.
19
+ -- ISO 3166-2 without the country prefix — `QC`, `BC`, `ON` — because the
20
+ -- country is already on the row next to it. The declarative rules that turn a
21
+ -- region into a suggested tax (`tax_rule_templates`, `suggest_tax()`) are
22
+ -- phase 1, with the pack that needs them; nothing reads `region` yet, and
23
+ -- that is the one deviation from "no column without a reader" this schema
24
+ -- makes, taken knowingly and written down here.
25
+ --
26
+ -- Additive: four `add column if not exists`, a backfill of the rows that
27
+ -- exist, and `install_country_template` replaced so it carries the new column
28
+ -- into a company.
29
+
30
+ -- ---------------------------------------------------------------------------
31
+ -- report_code
32
+ -- ---------------------------------------------------------------------------
33
+
34
+ alter table tax_posting_templates add column if not exists report_code text;
35
+ alter table tax_postings add column if not exists report_code text;
36
+
37
+ comment on column tax_posting_templates.report_code is
38
+ 'Declaration form the box belongs to (BE-VAT-PERIODIC, FR-CA3, CA-GST34…). Null means the periodic return of the country.';
39
+ comment on column tax_postings.report_code is
40
+ 'Declaration form the box belongs to. Copied from the template; read by vat_return(company, from, to, report_code) from P0-3.';
41
+
42
+ -- Nothing is backfilled here, and that is the whole point of the column being
43
+ -- nullable. On the template side the packs carry the code and the compiled
44
+ -- seeds upsert it, so re-applying the seeds names every template posting. On
45
+ -- the company side a null keeps its documented meaning — the periodic return
46
+ -- of the company's country — and `vat_return()` reads it exactly that way, so
47
+ -- a posting an older installation holds lands on the right form without
48
+ -- anything being rewritten. A backfill would have had to name a country and
49
+ -- the code of its form, which is the thing this schema refuses to do.
50
+
51
+ -- ---------------------------------------------------------------------------
52
+ -- region
53
+ -- ---------------------------------------------------------------------------
54
+
55
+ alter table companies add column if not exists region text;
56
+ alter table contacts add column if not exists region text;
57
+
58
+ comment on column companies.region is
59
+ 'Province or state, ISO 3166-2 without the country prefix: QC, BC, CA. Null in a country that taxes uniformly.';
60
+ comment on column contacts.region is
61
+ 'Province or state of the party, ISO 3166-2 without the country prefix. Canadian tax follows the buyer''s province, not the seller''s.';
62
+
63
+ -- ---------------------------------------------------------------------------
64
+ -- install_country_template carries report_code into the company
65
+ -- ---------------------------------------------------------------------------
66
+
67
+ create or replace function install_country_template(
68
+ p_company_id uuid,
69
+ p_country char(2),
70
+ p_language char(2) default null
71
+ )
72
+ returns void
73
+ language plpgsql
74
+ as $$
75
+ declare
76
+ v_defaults country_defaults%rowtype;
77
+ r record;
78
+ v_tax_id uuid;
79
+ v_language char(2);
80
+ v_version text;
81
+ begin
82
+ if not exists (select 1 from companies where id = p_company_id) then
83
+ raise exception 'unknown_company: %', p_company_id;
84
+ end if;
85
+ if not exists (select 1 from account_templates where country = p_country) then
86
+ raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
87
+ end if;
88
+
89
+ select coalesce(p_language, c.language) into v_language
90
+ from companies c where c.id = p_company_id;
91
+
92
+ -- 1. Accounts, without the hierarchy.
93
+ insert into accounts (company_id, code, name, name_i18n, statement_hint,
94
+ account_type, reconcilable)
95
+ select p_company_id, t.code,
96
+ coalesce(nullif(t.name_i18n ->> v_language, ''), t.name),
97
+ t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
98
+ from account_templates t
99
+ where t.country = p_country
100
+ on conflict (company_id, code) do nothing;
101
+
102
+ -- 2. Hierarchy, now that every code exists.
103
+ update accounts a
104
+ set parent_id = p.id
105
+ from account_templates t
106
+ join accounts p on p.company_id = p_company_id and p.code = t.parent_code
107
+ where a.company_id = p_company_id
108
+ and a.code = t.code
109
+ and t.country = p_country
110
+ and t.parent_code is not null
111
+ and a.parent_id is null;
112
+
113
+ -- 3. Journals.
114
+ insert into journals (company_id, code, name, journal_type)
115
+ select p_company_id, t.code, t.name, t.journal_type
116
+ from journal_templates t
117
+ where t.country = p_country
118
+ on conflict (company_id, code) do nothing;
119
+
120
+ -- 4. Taxes and their postings, each posting keeping the form its box is on.
121
+ for r in
122
+ select * from tax_templates where country = p_country order by sequence, code
123
+ loop
124
+ v_tax_id := null;
125
+ insert into taxes (company_id, code, name, description, amount_type, amount,
126
+ applies_to, treatment, country, valid_from, valid_to,
127
+ legal_reference, vat_category, exemption_code, sequence)
128
+ values (p_company_id, r.code, r.name, r.description, r.amount_type, r.amount,
129
+ r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
130
+ r.legal_reference, r.vat_category, r.exemption_code, r.sequence)
131
+ on conflict (company_id, code) do nothing
132
+ returning id into v_tax_id;
133
+
134
+ if v_tax_id is null then
135
+ continue;
136
+ end if;
137
+
138
+ insert into tax_postings (tax_id, company_id, document_kind, posting_type,
139
+ factor_percent, account_id, declaration_box,
140
+ box_factor_percent, report_code, sequence)
141
+ select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
142
+ tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
143
+ tp.declaration_box, tp.box_factor_percent, tp.report_code, tp.sequence
144
+ from tax_posting_templates tp
145
+ where tp.tax_template_id = r.id
146
+ order by tp.sequence;
147
+ end loop;
148
+
149
+ -- 5. Roles.
150
+ select * into v_defaults from country_defaults where country = p_country;
151
+ if found then
152
+ update companies c
153
+ set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
154
+ payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
155
+ suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
156
+ rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
157
+ retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
158
+ default_sales_account_id = coalesce(c.default_sales_account_id, account_id_by_code(p_company_id, v_defaults.sales_account_code)),
159
+ default_purchase_account_id = coalesce(c.default_purchase_account_id, account_id_by_code(p_company_id, v_defaults.purchase_account_code)),
160
+ 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)),
161
+ 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)),
162
+ 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))
163
+ where c.id = p_company_id;
164
+
165
+ update journals j
166
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
167
+ where j.company_id = p_company_id
168
+ and j.journal_type = 'bank'
169
+ and j.default_account_id is null
170
+ and v_defaults.bank_account_code is not null;
171
+
172
+ update journals j
173
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
174
+ where j.company_id = p_company_id
175
+ and j.journal_type = 'cash'
176
+ and j.default_account_id is null
177
+ and v_defaults.cash_account_code is not null;
178
+ end if;
179
+
180
+ -- 6. What was copied, and from which version.
181
+ v_version := coalesce((select version from country_packs where country = p_country), '1.0.0');
182
+
183
+ insert into company_packs (company_id, country, version)
184
+ values (p_company_id, p_country, v_version)
185
+ on conflict (company_id, country) do update
186
+ set version = excluded.version,
187
+ upgraded_at = now()
188
+ where company_packs.version is distinct from excluded.version;
189
+ end;
190
+ $$;
191
+
192
+ comment on function install_country_template(uuid, char, char) is
193
+ 'Copies a country pack into a company in one language, wires the default roles and the financial journals, and records the pack version in company_packs.';
194
+
195
+ -- The rule of `20260911210131`, which its own default privileges cannot keep:
196
+ -- a function created now comes out executable by PUBLIC unless this runs.
197
+ -- From PUBLIC only — `anon` holds explicit grants on the policy helpers.
198
+ revoke execute on all functions in schema public from public;
@@ -0,0 +1,31 @@
1
+ -- Ekwo OS — Ekwo maintains a pack; only a professional reviews one.
2
+ --
3
+ -- `pack_certification` shipped with three values, and one of them said the
4
+ -- wrong thing. `ekwo` read as "certified by Ekwo", which is a claim nobody
5
+ -- here can make: we write the pack, we test that it is internally coherent,
6
+ -- and none of that is an accountant reading it against the law. The word
7
+ -- *certified* should only ever describe a review by a named professional.
8
+ --
9
+ -- So the scale becomes:
10
+ --
11
+ -- community contributed, not read by an accountant
12
+ -- maintained maintained by Ekwo, not yet reviewed by an accountant
13
+ -- reviewed read by a named professional, with `certified_by`,
14
+ -- `certified_at` and the sources they worked from
15
+ --
16
+ -- `ekwo` stays in the type because a value is never removed from an enum a
17
+ -- published column uses, and it is deprecated: nothing writes it any more,
18
+ -- the JSON schema of a pack refuses it, and the next migration moves the rows
19
+ -- that hold it. A value cannot be added and used in the same transaction,
20
+ -- which is why the update is its own file rather than ten lines below.
21
+
22
+ alter type pack_certification add value if not exists 'maintained' after 'community';
23
+
24
+ comment on type pack_certification is
25
+ 'How much a pack has been read: community (contributed, unread), maintained (by Ekwo, not yet reviewed), reviewed (by a named professional). The value `ekwo` is deprecated and nothing writes it.';
26
+
27
+ comment on column country_packs.certification_status is
28
+ 'How much a pack has been read, printed by `ekwo init`: community (contributed, unread), maintained (by Ekwo, not yet reviewed), reviewed (by the professional named in certified_by).';
29
+
30
+ comment on column country_packs.certified_by is
31
+ 'The professional who reviewed the pack. Only on a reviewed pack: maintaining is not reviewing.';
@@ -0,0 +1,16 @@
1
+ -- Ekwo OS — the packs that called themselves certified are maintained.
2
+ --
3
+ -- Its own file because PostgreSQL refuses a new enum value in the transaction
4
+ -- that added it, and the migration runner gives each file one transaction.
5
+ --
6
+ -- `certified_by` is emptied along with it: on a maintained pack it held
7
+ -- "Ekwo AI", which is the claim this pair of migrations exists to stop
8
+ -- making. On a reviewed pack it holds the professional who read it, and this
9
+ -- touches nothing of the sort. The generated seeds carry the same values, so
10
+ -- an installation that re-applies them lands in the same place.
11
+
12
+ update country_packs
13
+ set certification_status = 'maintained',
14
+ certified_by = null,
15
+ certified_at = null
16
+ where certification_status = 'ekwo';