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,637 @@
1
+ -- Ekwo OS — a role is a preset, a capability is what a policy tests.
2
+ --
3
+ -- `company_members.role` carried three values and every policy in the schema
4
+ -- read one of them, through `is_company_member`, `can_write_company` and
5
+ -- `is_company_owner`. That is a permission model with three positions: a
6
+ -- bookkeeper who may post invoices and must never move a period lock has no
7
+ -- row to sit on, and the answer in every product that meets the case is a
8
+ -- fourth role, then a fifth.
9
+ --
10
+ -- So the role stays, as a **preset**, and what a policy tests becomes a
11
+ -- **capability**: a code from a fixed vocabulary, seeded by this migration,
12
+ -- that names one thing a person may do. `role_capabilities` says what each
13
+ -- preset holds. `company_members.capabilities_granted` and
14
+ -- `capabilities_revoked` adjust one member on top of their preset, in both
15
+ -- directions, and a revoke wins over a grant — including on an owner, because
16
+ -- a company that wants its owner unable to close a year is describing its own
17
+ -- separation of duties and not a mistake.
18
+ --
19
+ -- **One source of truth.** Every policy that read a role now calls
20
+ -- `has_capability()`, and the three published helpers are rewritten on top of
21
+ -- it rather than left beside it: `can_write_company()` is the answer to
22
+ -- `entries.write` and nothing else. A role is never tested by a policy again.
23
+ --
24
+ -- **What this migration does not do.** It does not consider an instance
25
+ -- administrator. Administering an installation is not being on the books of a
26
+ -- company — `docs/decisions.md` says so and a test asserts it — so the
27
+ -- policies that already admit an administrator keep saying it themselves,
28
+ -- beside the capability, and `has_capability()` answers about membership only.
29
+ --
30
+ -- **The door left open.** A module adds its own codes to `capabilities` with
31
+ -- `area` set to the module's code, and its own rows to `role_capabilities`.
32
+ -- Nothing here enumerates the areas, and nothing refuses a code it has not
33
+ -- seen.
34
+
35
+ -- ---------------------------------------------------------------------------
36
+ -- The vocabulary
37
+ -- ---------------------------------------------------------------------------
38
+
39
+ create table capabilities (
40
+ code text primary key,
41
+ area text not null,
42
+ description text not null,
43
+ constraint capabilities_code_shape check (code ~ '^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$')
44
+ );
45
+
46
+ comment on table capabilities is
47
+ 'Everything a member may be allowed to do, one row per code. Seeded by this migration for the core; a module adds its own with `area` set to the module code.';
48
+ comment on column capabilities.area is
49
+ 'What the code belongs to — a table family of the core, or the code of a module.';
50
+
51
+ -- Twenty codes, and every one of them is read by a policy or by a guard in
52
+ -- this file. Two that an earlier sketch carried are deliberately absent.
53
+ -- `reports.read` would have guarded `trial_balance()` and the statements,
54
+ -- which sum ledger lines row level security has already filtered: a member
55
+ -- without `entries.read` gets an empty report today, and a second lock on the
56
+ -- same door is a lock nobody turns. `exports.run` is the same argument about
57
+ -- the FEC. Neither is a capability until something can genuinely refuse on
58
+ -- it.
59
+ insert into capabilities (code, area, description) values
60
+ ('documents.read', 'documents', 'Read invoices, credit notes, quotes and their lines.'),
61
+ ('documents.write', 'documents', 'Create and change draft documents, their lines and their attachments.'),
62
+ ('documents.post', 'documents', 'Book a document to the ledger. Cannot be undone.'),
63
+ ('entries.read', 'entries', 'Read the ledger: entries, lines, matchings and the counters behind the numbering.'),
64
+ ('entries.write', 'entries', 'Create and change draft entries and their lines.'),
65
+ ('entries.post', 'entries', 'Post an entry, which draws its number. Cannot be undone.'),
66
+ ('payments.read', 'payments', 'Read payments in and out.'),
67
+ ('payments.write', 'payments', 'Record a payment and book it.'),
68
+ ('reconcile.write', 'reconcile', 'Match a debit against a credit, and undo a matching.'),
69
+ ('bank.read', 'bank', 'Read bank accounts, statements and statement lines.'),
70
+ ('bank.write', 'bank', 'Add bank accounts and statement lines.'),
71
+ ('contacts.read', 'contacts', 'Read customers, suppliers and other third parties.'),
72
+ ('contacts.write', 'contacts', 'Create and change contacts.'),
73
+ ('products.read', 'products', 'Read the catalogue.'),
74
+ ('products.write', 'products', 'Create, change and retire catalogue items.'),
75
+ ('settings.read', 'settings', 'Read the chart of accounts, the journals, the taxes, the analytic plan and the financial years.'),
76
+ ('settings.write', 'settings', 'Change the chart of accounts, the journals, the taxes, the analytic plan and the financial years.'),
77
+ ('company.write', 'company', 'Change the company itself: its profile, its default accounts and its period locks.'),
78
+ ('members.manage', 'members', 'Invite, change and remove members, and issue machine access keys.'),
79
+ ('year_end.close', 'year_end', 'Close and re-open a financial year.');
80
+
81
+ -- ---------------------------------------------------------------------------
82
+ -- The presets
83
+ --
84
+ -- The three roles keep exactly what they could do the day before this
85
+ -- migration, expressed in the new vocabulary: a viewer reads, an accountant
86
+ -- keeps the books, an owner also administers the company and its members.
87
+ --
88
+ -- There is no fourth role. `admin` was considered and left out: the existing
89
+ -- helpers drew one line — write the books, or administer the company — and
90
+ -- `owner` is already the second half of it, so a role between them would have
91
+ -- been a name with no work to do. A member who needs exactly that is an
92
+ -- accountant with `members.manage` granted, which is what the grant column is
93
+ -- for.
94
+ -- ---------------------------------------------------------------------------
95
+
96
+ create table role_capabilities (
97
+ role member_role not null,
98
+ capability text not null references capabilities(code) on delete cascade,
99
+ primary key (role, capability)
100
+ );
101
+
102
+ comment on table role_capabilities is
103
+ 'What each preset holds. A role is never tested by a policy; it is resolved here into capabilities.';
104
+
105
+ insert into role_capabilities (role, capability)
106
+ select 'viewer'::member_role, code from capabilities where code like '%.read';
107
+
108
+ insert into role_capabilities (role, capability)
109
+ select 'accountant'::member_role, code
110
+ from capabilities
111
+ where code like '%.read'
112
+ or code in ('documents.write', 'documents.post', 'entries.write', 'entries.post',
113
+ 'payments.write', 'reconcile.write', 'bank.write', 'contacts.write',
114
+ 'products.write', 'settings.write', 'year_end.close');
115
+
116
+ insert into role_capabilities (role, capability)
117
+ select 'owner'::member_role, code from capabilities;
118
+
119
+ -- ---------------------------------------------------------------------------
120
+ -- The adjustments, per member
121
+ -- ---------------------------------------------------------------------------
122
+
123
+ alter table company_members
124
+ add column if not exists capabilities_granted text[] not null default '{}',
125
+ add column if not exists capabilities_revoked text[] not null default '{}';
126
+
127
+ comment on column company_members.capabilities_granted is
128
+ 'Capabilities this member holds beyond their preset.';
129
+ comment on column company_members.capabilities_revoked is
130
+ 'Capabilities this member does not hold whatever their preset says. A revoke wins over a grant and over a role.';
131
+
132
+ -- A code that is not in `capabilities` is a typo, and a typo in a permission
133
+ -- column is a permission nobody notices is missing. A foreign key cannot
134
+ -- reach inside an array, so this is a trigger — and it reads the table, so a
135
+ -- module's codes are accepted the moment the module's migration adds them.
136
+ create or replace function company_members_capabilities_known()
137
+ returns trigger
138
+ language plpgsql
139
+ as $$
140
+ declare
141
+ v_unknown text;
142
+ begin
143
+ select c into v_unknown
144
+ from unnest(new.capabilities_granted || new.capabilities_revoked) as c
145
+ where c not in (select code from capabilities)
146
+ limit 1;
147
+
148
+ if v_unknown is not null then
149
+ raise exception 'unknown_capability: % is not a capability of this installation', v_unknown
150
+ using errcode = '23514';
151
+ end if;
152
+
153
+ return new;
154
+ end;
155
+ $$;
156
+
157
+ create trigger company_members_capabilities_known
158
+ before insert or update of capabilities_granted, capabilities_revoked on company_members
159
+ for each row execute function company_members_capabilities_known();
160
+
161
+ -- ---------------------------------------------------------------------------
162
+ -- The one question every policy asks
163
+ --
164
+ -- SECURITY DEFINER for the reason `company_role()` is: the policy on
165
+ -- `company_members` must not recurse into the table it protects. It answers
166
+ -- about `auth.uid()` and about membership only — an instance administrator is
167
+ -- not a member, and the policies that admit one say so themselves.
168
+ -- ---------------------------------------------------------------------------
169
+
170
+ create or replace function has_capability(p_company_id uuid, p_capability text)
171
+ returns boolean
172
+ language sql
173
+ stable
174
+ security definer
175
+ set search_path = public, pg_temp
176
+ as $$
177
+ select coalesce(
178
+ (select case
179
+ when p_capability = any (m.capabilities_revoked) then false
180
+ when p_capability = any (m.capabilities_granted) then true
181
+ else exists (
182
+ select 1 from role_capabilities rc
183
+ where rc.role = m.role and rc.capability = p_capability
184
+ )
185
+ end
186
+ from company_members m
187
+ where m.company_id = p_company_id
188
+ and m.user_id = auth.uid()),
189
+ false);
190
+ $$;
191
+
192
+ comment on function has_capability(uuid, text) is
193
+ 'Whether the current user may do one named thing in one company. Revoked beats granted, granted beats the preset, and a non-member holds nothing.';
194
+
195
+ -- What a member may do, for an interface that has to draw it — and, when the
196
+ -- caller may manage members, what somebody else may do.
197
+ create or replace function member_capabilities(p_company_id uuid, p_user_id uuid default null)
198
+ returns setof text
199
+ language plpgsql
200
+ stable
201
+ security definer
202
+ set search_path = public, pg_temp
203
+ as $$
204
+ declare
205
+ v_user uuid := coalesce(p_user_id, auth.uid());
206
+ begin
207
+ if v_user is distinct from auth.uid() and not has_capability(p_company_id, 'members.manage') then
208
+ raise exception 'not_allowed: reading what another member may do needs members.manage'
209
+ using errcode = '42501';
210
+ end if;
211
+
212
+ return query
213
+ select c.code
214
+ from capabilities c
215
+ join company_members m
216
+ on m.company_id = p_company_id and m.user_id = v_user
217
+ where not (c.code = any (m.capabilities_revoked))
218
+ and (c.code = any (m.capabilities_granted)
219
+ or exists (select 1 from role_capabilities rc
220
+ where rc.role = m.role and rc.capability = c.code))
221
+ order by c.code;
222
+ end;
223
+ $$;
224
+
225
+ comment on function member_capabilities(uuid, uuid) is
226
+ 'The capabilities one member effectively holds on one company, preset and adjustments resolved. Reading another member''s needs members.manage.';
227
+
228
+ -- ---------------------------------------------------------------------------
229
+ -- The published helpers, rewritten on top of the one question
230
+ --
231
+ -- They keep their names and their meaning, and they stop being a second place
232
+ -- where a permission is decided. `is_company_member` stays a membership test:
233
+ -- belonging to a company is not a capability, it is what makes capabilities
234
+ -- possible. `is_company_owner` stays the literal role, for a caller that wants
235
+ -- to know which preset somebody is on — no policy tests it any more.
236
+ -- ---------------------------------------------------------------------------
237
+
238
+ create or replace function can_write_company(p_company_id uuid)
239
+ returns boolean
240
+ language sql
241
+ stable
242
+ as $$
243
+ select has_capability(p_company_id, 'entries.write');
244
+ $$;
245
+
246
+ comment on function can_write_company(uuid) is
247
+ 'Whether the current user may write the books of a company. Kept for callers that have it; it is now one capability and not a role.';
248
+
249
+ -- ---------------------------------------------------------------------------
250
+ -- Every policy that tested a role now tests a capability
251
+ -- ---------------------------------------------------------------------------
252
+
253
+ -- The company itself, its members and its financial years.
254
+ drop policy companies_update on companies;
255
+ create policy companies_update on companies
256
+ for update using (has_capability(id, 'company.write') or is_instance_admin())
257
+ with check (has_capability(id, 'company.write') or is_instance_admin());
258
+
259
+ drop policy companies_delete on companies;
260
+ create policy companies_delete on companies
261
+ for delete using (has_capability(id, 'company.write'));
262
+
263
+ drop policy company_members_insert on company_members;
264
+ create policy company_members_insert on company_members
265
+ for insert with check (
266
+ has_capability(company_id, 'members.manage')
267
+ or is_instance_admin()
268
+ or company_has_no_member(company_id)
269
+ );
270
+
271
+ drop policy company_members_update on company_members;
272
+ create policy company_members_update on company_members
273
+ for update using (has_capability(company_id, 'members.manage'))
274
+ with check (has_capability(company_id, 'members.manage'));
275
+
276
+ drop policy company_members_delete on company_members;
277
+ create policy company_members_delete on company_members
278
+ for delete using (has_capability(company_id, 'members.manage'));
279
+
280
+ drop policy fiscal_years_select on fiscal_years;
281
+ create policy fiscal_years_select on fiscal_years
282
+ for select using (has_capability(company_id, 'settings.read'));
283
+ drop policy fiscal_years_write on fiscal_years;
284
+ create policy fiscal_years_write on fiscal_years
285
+ for all using (has_capability(company_id, 'settings.write'))
286
+ with check (has_capability(company_id, 'settings.write'));
287
+
288
+ -- The chart, the journals, the taxes, the analytic plan and what the company
289
+ -- copied from a pack: one family, `settings`.
290
+ drop policy accounts_select on accounts;
291
+ create policy accounts_select on accounts
292
+ for select using (has_capability(company_id, 'settings.read'));
293
+ drop policy accounts_write on accounts;
294
+ create policy accounts_write on accounts
295
+ for all using (has_capability(company_id, 'settings.write'))
296
+ with check (has_capability(company_id, 'settings.write'));
297
+
298
+ drop policy journals_select on journals;
299
+ create policy journals_select on journals
300
+ for select using (has_capability(company_id, 'settings.read'));
301
+ drop policy journals_write on journals;
302
+ create policy journals_write on journals
303
+ for all using (has_capability(company_id, 'settings.write'))
304
+ with check (has_capability(company_id, 'settings.write'));
305
+
306
+ drop policy taxes_select on taxes;
307
+ create policy taxes_select on taxes
308
+ for select using (has_capability(company_id, 'settings.read'));
309
+ drop policy taxes_write on taxes;
310
+ create policy taxes_write on taxes
311
+ for all using (has_capability(company_id, 'settings.write'))
312
+ with check (has_capability(company_id, 'settings.write'));
313
+
314
+ drop policy tax_postings_select on tax_postings;
315
+ create policy tax_postings_select on tax_postings
316
+ for select using (has_capability(company_id, 'settings.read'));
317
+ drop policy tax_postings_write on tax_postings;
318
+ create policy tax_postings_write on tax_postings
319
+ for all using (has_capability(company_id, 'settings.write'))
320
+ with check (has_capability(company_id, 'settings.write'));
321
+
322
+ drop policy analytic_axes_select on analytic_axes;
323
+ create policy analytic_axes_select on analytic_axes
324
+ for select using (has_capability(company_id, 'settings.read'));
325
+ drop policy analytic_axes_write on analytic_axes;
326
+ create policy analytic_axes_write on analytic_axes
327
+ for all using (has_capability(company_id, 'settings.write'))
328
+ with check (has_capability(company_id, 'settings.write'));
329
+
330
+ drop policy analytic_values_select on analytic_values;
331
+ create policy analytic_values_select on analytic_values
332
+ for select using (has_capability(company_id, 'settings.read'));
333
+ drop policy analytic_values_write on analytic_values;
334
+ create policy analytic_values_write on analytic_values
335
+ for all using (has_capability(company_id, 'settings.write'))
336
+ with check (has_capability(company_id, 'settings.write'));
337
+
338
+ drop policy company_packs_select on company_packs;
339
+ create policy company_packs_select on company_packs
340
+ for select using (has_capability(company_id, 'settings.read'));
341
+ drop policy company_packs_write on company_packs;
342
+ create policy company_packs_write on company_packs
343
+ for all using (has_capability(company_id, 'settings.write'))
344
+ with check (has_capability(company_id, 'settings.write'));
345
+
346
+ -- Contacts and the catalogue.
347
+ drop policy contacts_select on contacts;
348
+ create policy contacts_select on contacts
349
+ for select using (has_capability(company_id, 'contacts.read'));
350
+ drop policy contacts_write on contacts;
351
+ create policy contacts_write on contacts
352
+ for all using (has_capability(company_id, 'contacts.write'))
353
+ with check (has_capability(company_id, 'contacts.write'));
354
+
355
+ drop policy products_select on products;
356
+ create policy products_select on products
357
+ for select using (has_capability(company_id, 'products.read'));
358
+ drop policy products_write on products;
359
+ create policy products_write on products
360
+ for all using (has_capability(company_id, 'products.write'))
361
+ with check (has_capability(company_id, 'products.write'));
362
+
363
+ -- Documents, their lines and what is filed with them.
364
+ drop policy documents_select on documents;
365
+ create policy documents_select on documents
366
+ for select using (has_capability(company_id, 'documents.read'));
367
+ drop policy documents_write on documents;
368
+ create policy documents_write on documents
369
+ for all using (has_capability(company_id, 'documents.write'))
370
+ with check (has_capability(company_id, 'documents.write'));
371
+
372
+ drop policy document_lines_select on document_lines;
373
+ create policy document_lines_select on document_lines
374
+ for select using (has_capability(company_id, 'documents.read'));
375
+ drop policy document_lines_write on document_lines;
376
+ create policy document_lines_write on document_lines
377
+ for all using (has_capability(company_id, 'documents.write'))
378
+ with check (has_capability(company_id, 'documents.write'));
379
+
380
+ drop policy attachments_select on attachments;
381
+ create policy attachments_select on attachments
382
+ for select using (has_capability(company_id, 'documents.read'));
383
+ drop policy attachments_write on attachments;
384
+ create policy attachments_write on attachments
385
+ for all using (has_capability(company_id, 'documents.write'))
386
+ with check (has_capability(company_id, 'documents.write'));
387
+
388
+ -- The ledger, its analytics and the counters behind its numbering.
389
+ drop policy entries_select on entries;
390
+ create policy entries_select on entries
391
+ for select using (has_capability(company_id, 'entries.read'));
392
+ drop policy entries_write on entries;
393
+ create policy entries_write on entries
394
+ for all using (has_capability(company_id, 'entries.write'))
395
+ with check (has_capability(company_id, 'entries.write'));
396
+
397
+ drop policy entry_lines_select on entry_lines;
398
+ create policy entry_lines_select on entry_lines
399
+ for select using (has_capability(company_id, 'entries.read'));
400
+ drop policy entry_lines_write on entry_lines;
401
+ create policy entry_lines_write on entry_lines
402
+ for all using (has_capability(company_id, 'entries.write'))
403
+ with check (has_capability(company_id, 'entries.write'));
404
+
405
+ drop policy entry_line_analytics_select on entry_line_analytics;
406
+ create policy entry_line_analytics_select on entry_line_analytics
407
+ for select using (has_capability(company_id, 'entries.read'));
408
+ drop policy entry_line_analytics_write on entry_line_analytics;
409
+ create policy entry_line_analytics_write on entry_line_analytics
410
+ for all using (has_capability(company_id, 'entries.write'))
411
+ with check (has_capability(company_id, 'entries.write'));
412
+
413
+ drop policy journal_sequences_select on journal_sequences;
414
+ create policy journal_sequences_select on journal_sequences
415
+ for select using (
416
+ exists (select 1 from journals j
417
+ where j.id = journal_id and has_capability(j.company_id, 'entries.read'))
418
+ );
419
+
420
+ drop policy matching_sequences_select on matching_sequences;
421
+ create policy matching_sequences_select on matching_sequences
422
+ for select using (has_capability(company_id, 'entries.read'));
423
+
424
+ -- Money, and what it settles.
425
+ drop policy payments_select on payments;
426
+ create policy payments_select on payments
427
+ for select using (has_capability(company_id, 'payments.read'));
428
+ drop policy payments_write on payments;
429
+ create policy payments_write on payments
430
+ for all using (has_capability(company_id, 'payments.write'))
431
+ with check (has_capability(company_id, 'payments.write'));
432
+
433
+ drop policy reconciliations_select on reconciliations;
434
+ create policy reconciliations_select on reconciliations
435
+ for select using (has_capability(company_id, 'entries.read'));
436
+ drop policy reconciliations_write on reconciliations;
437
+ create policy reconciliations_write on reconciliations
438
+ for all using (has_capability(company_id, 'reconcile.write'))
439
+ with check (has_capability(company_id, 'reconcile.write'));
440
+
441
+ drop policy bank_accounts_select on bank_accounts;
442
+ create policy bank_accounts_select on bank_accounts
443
+ for select using (has_capability(company_id, 'bank.read'));
444
+ drop policy bank_accounts_write on bank_accounts;
445
+ create policy bank_accounts_write on bank_accounts
446
+ for all using (has_capability(company_id, 'bank.write'))
447
+ with check (has_capability(company_id, 'bank.write'));
448
+
449
+ drop policy bank_statements_select on bank_statements;
450
+ create policy bank_statements_select on bank_statements
451
+ for select using (has_capability(company_id, 'bank.read'));
452
+ drop policy bank_statements_write on bank_statements;
453
+ create policy bank_statements_write on bank_statements
454
+ for all using (has_capability(company_id, 'bank.write'))
455
+ with check (has_capability(company_id, 'bank.write'));
456
+
457
+ drop policy bank_transactions_select on bank_transactions;
458
+ create policy bank_transactions_select on bank_transactions
459
+ for select using (has_capability(company_id, 'bank.read'));
460
+ drop policy bank_transactions_write on bank_transactions;
461
+ create policy bank_transactions_write on bank_transactions
462
+ for all using (has_capability(company_id, 'bank.write'))
463
+ with check (has_capability(company_id, 'bank.write'));
464
+
465
+ -- ---------------------------------------------------------------------------
466
+ -- Posting and closing are acts, not rows
467
+ --
468
+ -- `documents.post`, `entries.post` and `year_end.close` name three things a
469
+ -- policy on a table cannot express: what changes is a state, and the row is
470
+ -- one the member may already write. They are guarded by triggers on the
471
+ -- transition rather than inside `post_document()`, `post_entry()` and
472
+ -- `close_fiscal_year()` — a guard on the transition holds for every path into
473
+ -- it, including a client that updates the column itself, and it does not
474
+ -- require republishing a function that carries a thousand lines of accounting
475
+ -- for four lines of permission.
476
+ --
477
+ -- `auth.uid() is null` is the installer and the migrations, where row level
478
+ -- security is bypassed rather than satisfied; the same exemption
479
+ -- `next_entry_number()` makes.
480
+ -- ---------------------------------------------------------------------------
481
+
482
+ create or replace function assert_may_post_entry()
483
+ returns trigger
484
+ language plpgsql
485
+ as $$
486
+ begin
487
+ if auth.uid() is not null and not has_capability(new.company_id, 'entries.post') then
488
+ raise exception 'not_allowed: posting an entry in this company needs entries.post'
489
+ using errcode = '42501';
490
+ end if;
491
+ return new;
492
+ end;
493
+ $$;
494
+
495
+ -- Two triggers rather than one: the WHEN clause of an INSERT trigger cannot
496
+ -- read OLD, so the transition and the arrival are written separately.
497
+ create trigger entries_assert_may_post_on_insert
498
+ before insert on entries
499
+ for each row when (new.state = 'posted')
500
+ execute function assert_may_post_entry();
501
+
502
+ create trigger entries_assert_may_post
503
+ before update of state on entries
504
+ for each row when (new.state = 'posted' and old.state is distinct from 'posted')
505
+ execute function assert_may_post_entry();
506
+
507
+ create or replace function assert_may_post_document()
508
+ returns trigger
509
+ language plpgsql
510
+ as $$
511
+ begin
512
+ if auth.uid() is not null and not has_capability(new.company_id, 'documents.post') then
513
+ raise exception 'not_allowed: booking a document in this company needs documents.post'
514
+ using errcode = '42501';
515
+ end if;
516
+ return new;
517
+ end;
518
+ $$;
519
+
520
+ create trigger documents_assert_may_post_on_insert
521
+ before insert on documents
522
+ for each row when (new.state = 'posted')
523
+ execute function assert_may_post_document();
524
+
525
+ create trigger documents_assert_may_post
526
+ before update of state on documents
527
+ for each row when (new.state = 'posted' and old.state is distinct from 'posted')
528
+ execute function assert_may_post_document();
529
+
530
+ create or replace function assert_may_close_year()
531
+ returns trigger
532
+ language plpgsql
533
+ as $$
534
+ begin
535
+ if auth.uid() is not null and not has_capability(new.company_id, 'year_end.close') then
536
+ raise exception 'not_allowed: closing or re-opening a financial year needs year_end.close'
537
+ using errcode = '42501';
538
+ end if;
539
+ return new;
540
+ end;
541
+ $$;
542
+
543
+ create trigger fiscal_years_assert_may_close
544
+ before update of is_closed on fiscal_years
545
+ for each row
546
+ when (new.is_closed is distinct from old.is_closed)
547
+ execute function assert_may_close_year();
548
+
549
+ -- ---------------------------------------------------------------------------
550
+ -- What the counters ask before they hand out a number
551
+ -- ---------------------------------------------------------------------------
552
+
553
+ create or replace function next_entry_number(p_journal_id uuid, p_date date)
554
+ returns text
555
+ language plpgsql
556
+ security definer
557
+ set search_path = public, pg_temp
558
+ as $$
559
+ declare
560
+ v_code text;
561
+ v_company uuid;
562
+ v_year smallint := extract(year from p_date)::smallint;
563
+ v_number integer;
564
+ begin
565
+ select j.code, j.company_id into v_code, v_company from journals j where j.id = p_journal_id;
566
+ if v_code is null then
567
+ raise exception 'unknown_journal: journal % does not exist', p_journal_id;
568
+ end if;
569
+
570
+ if auth.uid() is not null and not has_capability(v_company, 'entries.post') then
571
+ raise exception 'not_allowed: drawing a number in this company needs entries.post'
572
+ using errcode = '42501';
573
+ end if;
574
+
575
+ insert into journal_sequences (journal_id, year, last_number)
576
+ values (p_journal_id, v_year, 1)
577
+ on conflict (journal_id, year)
578
+ do update set last_number = journal_sequences.last_number + 1
579
+ returning last_number into v_number;
580
+
581
+ return v_code || '/' || v_year::text || '/' || lpad(v_number::text, 4, '0');
582
+ end;
583
+ $$;
584
+
585
+ create or replace function next_matching_number(p_company_id uuid)
586
+ returns text
587
+ language plpgsql
588
+ security definer
589
+ set search_path = public, pg_temp
590
+ as $$
591
+ declare
592
+ v_number integer;
593
+ begin
594
+ if auth.uid() is not null and not has_capability(p_company_id, 'reconcile.write') then
595
+ raise exception 'not_allowed: matching in this company needs reconcile.write'
596
+ using errcode = '42501';
597
+ end if;
598
+
599
+ insert into matching_sequences (company_id, last_number)
600
+ values (p_company_id, 1)
601
+ on conflict (company_id)
602
+ do update set last_number = matching_sequences.last_number + 1
603
+ returning last_number into v_number;
604
+
605
+ return 'A' || lpad(v_number::text, 4, '0');
606
+ end;
607
+ $$;
608
+
609
+ -- ---------------------------------------------------------------------------
610
+ -- Row level security on the two new tables
611
+ --
612
+ -- The vocabulary and the presets are reference data: anyone signed in reads
613
+ -- them — an interface has to draw the list — and nobody writes them through
614
+ -- the API. A migration is the only thing that fills them.
615
+ -- ---------------------------------------------------------------------------
616
+
617
+ alter table capabilities enable row level security;
618
+ alter table role_capabilities enable row level security;
619
+
620
+ create policy capabilities_select on capabilities
621
+ for select using (auth.uid() is not null);
622
+ create policy role_capabilities_select on role_capabilities
623
+ for select using (auth.uid() is not null);
624
+
625
+ -- ---------------------------------------------------------------------------
626
+ -- Grants
627
+ --
628
+ -- `has_capability` is called by policies, so `anon` needs EXECUTE on it for
629
+ -- the same reason as the eight helpers before it: without it an anonymous
630
+ -- SELECT raises "permission denied for function" instead of returning
631
+ -- nothing. It answers about `auth.uid()`, which is null for `anon`, so what
632
+ -- it gives away is the word no. `member_capabilities` is not a policy helper
633
+ -- and stays closed.
634
+ -- ---------------------------------------------------------------------------
635
+
636
+ revoke execute on all functions in schema public from public;
637
+ grant execute on function has_capability(uuid, text) to anon;