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,224 @@
1
+ -- Ekwo OS — the instance-level role.
2
+ --
3
+ -- `member_role` is now one vocabulary for the whole installation:
4
+ -- `instance_admin` at instance level, `owner` / `accountant` / `viewer` per
5
+ -- company. The storage is split because the keys differ — a company role is
6
+ -- keyed by (company, user), an instance role by user alone — and a nullable
7
+ -- company in `company_members` would break its primary key and its meaning.
8
+ -- Two check constraints keep each table to the roles it may carry.
9
+ --
10
+ -- Users live in the customer's own Supabase Auth. `user_id` holds an
11
+ -- `auth.users.id` from their project; Ekwo never holds an account.
12
+
13
+ create table instance_members (
14
+ user_id uuid primary key,
15
+ role member_role not null default 'instance_admin',
16
+ created_at timestamptz not null default now(),
17
+ updated_at timestamptz not null default now(),
18
+ -- The only instance-level role today. Widening this is a deliberate act.
19
+ constraint instance_members_role_is_instance_level check (role = 'instance_admin')
20
+ );
21
+
22
+ comment on table instance_members is
23
+ 'Instance administrators: they create companies and invite members. user_id is an auth.users id from the customer''s own Supabase project.';
24
+ comment on column instance_members.user_id is
25
+ 'An auth.users.id in the customer''s Supabase Auth. No foreign key, so the schema installs on a plain Postgres and seeds never write into auth.';
26
+
27
+ create trigger instance_members_set_updated_at
28
+ before update on instance_members
29
+ for each row execute function set_updated_at();
30
+
31
+ -- A company role is a company role.
32
+ alter table company_members
33
+ add constraint company_members_role_is_company_level check (role <> 'instance_admin');
34
+
35
+ -- ---------------------------------------------------------------------------
36
+ -- Helpers
37
+ -- ---------------------------------------------------------------------------
38
+
39
+ -- SECURITY DEFINER so the policy on instance_members does not recurse into
40
+ -- the table it protects.
41
+ create or replace function is_instance_admin()
42
+ returns boolean
43
+ language sql
44
+ stable
45
+ security definer
46
+ set search_path = public, pg_temp
47
+ as $$
48
+ select exists (
49
+ select 1 from instance_members m
50
+ where m.user_id = auth.uid() and m.role = 'instance_admin'
51
+ );
52
+ $$;
53
+
54
+ comment on function is_instance_admin() is
55
+ 'Whether the current user administers this installation.';
56
+
57
+ create or replace function instance_has_no_admin()
58
+ returns boolean
59
+ language sql
60
+ stable
61
+ security definer
62
+ set search_path = public, pg_temp
63
+ as $$
64
+ select not exists (select 1 from instance_members);
65
+ $$;
66
+
67
+ -- ---------------------------------------------------------------------------
68
+ -- Installation
69
+ -- ---------------------------------------------------------------------------
70
+
71
+ create or replace function init_instance(
72
+ p_organization_name text,
73
+ p_country char(2),
74
+ p_edition instance_edition default 'community'
75
+ )
76
+ returns instance
77
+ language plpgsql
78
+ as $$
79
+ declare
80
+ v_row instance%rowtype;
81
+ begin
82
+ if exists (select 1 from instance) then
83
+ raise exception 'instance_already_initialised: this installation is already set up';
84
+ end if;
85
+
86
+ insert into instance (id, organization_name, country, edition, schema_version)
87
+ values (1, p_organization_name, upper(p_country), p_edition, ekwo_schema_version())
88
+ returning * into v_row;
89
+
90
+ return v_row;
91
+ end;
92
+ $$;
93
+
94
+ comment on function init_instance(text, char, instance_edition) is
95
+ 'Records the installation. Called once, by the installer. Leaves the registration fields empty.';
96
+
97
+ -- The first user to ask takes the instance. After that, only an existing
98
+ -- administrator can appoint another.
99
+ create or replace function claim_instance_admin(p_user_id uuid default auth.uid())
100
+ returns instance_members
101
+ language plpgsql
102
+ as $$
103
+ declare
104
+ v_row instance_members%rowtype;
105
+ begin
106
+ if p_user_id is null then
107
+ raise exception 'no_user: claim_instance_admin needs a signed-in user or an explicit id';
108
+ end if;
109
+
110
+ if exists (select 1 from instance_members) and not is_instance_admin() then
111
+ raise exception 'instance_already_claimed: only an instance administrator can appoint another';
112
+ end if;
113
+
114
+ insert into instance_members (user_id, role)
115
+ values (p_user_id, 'instance_admin')
116
+ on conflict (user_id) do update set role = 'instance_admin'
117
+ returning * into v_row;
118
+
119
+ return v_row;
120
+ end;
121
+ $$;
122
+
123
+ comment on function claim_instance_admin(uuid) is
124
+ 'Makes a user an instance administrator. The first claim is open; afterwards only an administrator may appoint one.';
125
+
126
+ -- Opt-in registration with Ekwo. Nothing calls this on its own.
127
+ create or replace function register_instance(p_contact_email text)
128
+ returns instance
129
+ language plpgsql
130
+ as $$
131
+ declare
132
+ v_row instance%rowtype;
133
+ begin
134
+ if not is_instance_admin() then
135
+ raise exception 'not_instance_admin: only an instance administrator can register this installation';
136
+ end if;
137
+
138
+ update instance
139
+ set contact_email = p_contact_email,
140
+ registered_at = now()
141
+ where id = 1
142
+ returning * into v_row;
143
+
144
+ if not found then
145
+ raise exception 'instance_not_initialised: call init_instance() first';
146
+ end if;
147
+
148
+ return v_row;
149
+ end;
150
+ $$;
151
+
152
+ comment on function register_instance(text) is
153
+ 'Opt-in: records an address and a date so Ekwo can reach the operator. Never required, and reversible with unregister_instance().';
154
+
155
+ create or replace function unregister_instance()
156
+ returns instance
157
+ language plpgsql
158
+ as $$
159
+ declare
160
+ v_row instance%rowtype;
161
+ begin
162
+ if not is_instance_admin() then
163
+ raise exception 'not_instance_admin: only an instance administrator can unregister this installation';
164
+ end if;
165
+
166
+ update instance set contact_email = null, registered_at = null where id = 1
167
+ returning * into v_row;
168
+
169
+ return v_row;
170
+ end;
171
+ $$;
172
+
173
+ comment on function unregister_instance() is
174
+ 'Undoes register_instance(). Opting in is reversible, or it is not a choice.';
175
+
176
+ -- ---------------------------------------------------------------------------
177
+ -- Row level security
178
+ -- ---------------------------------------------------------------------------
179
+
180
+ alter table instance_members enable row level security;
181
+
182
+ create policy instance_members_select on instance_members
183
+ for select using (auth.uid() is not null);
184
+ create policy instance_members_insert on instance_members
185
+ for insert with check (is_instance_admin() or instance_has_no_admin());
186
+ create policy instance_members_update on instance_members
187
+ for update using (is_instance_admin()) with check (is_instance_admin());
188
+ create policy instance_members_delete on instance_members
189
+ for delete using (is_instance_admin());
190
+
191
+ create policy instance_write on instance
192
+ for all using (is_instance_admin()) with check (is_instance_admin());
193
+
194
+ -- Creating a company is an instance-level act. The select policy has to
195
+ -- follow: `insert ... returning` is checked against it too, so without this
196
+ -- an administrator would create a company and still get an error back.
197
+ drop policy companies_insert on companies;
198
+ create policy companies_insert on companies
199
+ for insert with check (is_instance_admin());
200
+
201
+ drop policy companies_select on companies;
202
+ create policy companies_select on companies
203
+ for select using (is_company_member(id) or is_instance_admin());
204
+
205
+ -- An instance administrator may invite members into any company.
206
+ drop policy company_members_insert on company_members;
207
+ create policy company_members_insert on company_members
208
+ for insert with check (
209
+ is_company_owner(company_id)
210
+ or is_instance_admin()
211
+ or company_has_no_member(company_id)
212
+ );
213
+
214
+ drop policy company_members_select on company_members;
215
+ create policy company_members_select on company_members
216
+ for select using (
217
+ user_id = auth.uid() or is_company_member(company_id) or is_instance_admin()
218
+ );
219
+
220
+ -- ... and an instance administrator may administer a company they are not in.
221
+ drop policy companies_update on companies;
222
+ create policy companies_update on companies
223
+ for update using (is_company_owner(id) or is_instance_admin())
224
+ with check (is_company_owner(id) or is_instance_admin());
@@ -0,0 +1,112 @@
1
+ -- Ekwo OS — instance administrators, final shape.
2
+ --
3
+ -- Three changes on top of the previous migration, additive as always:
4
+ --
5
+ -- 1. `instance_members` becomes `instance_admins`. The table name carries
6
+ -- the role, so the `role` column carried nothing.
7
+ -- 2. `user_id` gets a real foreign key to `auth.users`. An administrator is
8
+ -- necessarily a signed-in user, so there is no invite-before-signup case
9
+ -- to accommodate here. `company_members` deliberately keeps no such key:
10
+ -- inviting someone into a company before they have an account is a
11
+ -- normal thing to want.
12
+ -- 3. Reading the instance row is for people who are actually on this
13
+ -- installation — a member of at least one company, or an administrator —
14
+ -- rather than anyone holding a token.
15
+
16
+ alter table instance_members rename to instance_admins;
17
+
18
+ alter policy instance_members_select on instance_admins rename to instance_admins_select;
19
+ alter policy instance_members_insert on instance_admins rename to instance_admins_insert;
20
+ alter policy instance_members_update on instance_admins rename to instance_admins_update;
21
+ alter policy instance_members_delete on instance_admins rename to instance_admins_delete;
22
+
23
+ alter table instance_admins drop constraint instance_members_role_is_instance_level;
24
+ alter table instance_admins drop column role;
25
+
26
+ alter table instance_admins
27
+ add constraint instance_admins_user_fkey
28
+ foreign key (user_id) references auth.users(id) on delete cascade;
29
+
30
+ comment on table instance_admins is
31
+ 'Instance administrators: they create companies and invite members. One row per user, keyed on auth.users of the customer''s own Supabase project.';
32
+ comment on column instance_admins.user_id is
33
+ 'An auth.users.id in the customer''s Supabase Auth. Ekwo holds no account and no directory.';
34
+
35
+ -- ---------------------------------------------------------------------------
36
+ -- Helpers, against the renamed table
37
+ -- ---------------------------------------------------------------------------
38
+
39
+ create or replace function is_instance_admin()
40
+ returns boolean
41
+ language sql
42
+ stable
43
+ security definer
44
+ set search_path = public, pg_temp
45
+ as $$
46
+ select exists (select 1 from instance_admins a where a.user_id = auth.uid());
47
+ $$;
48
+
49
+ create or replace function instance_has_no_admin()
50
+ returns boolean
51
+ language sql
52
+ stable
53
+ security definer
54
+ set search_path = public, pg_temp
55
+ as $$
56
+ select not exists (select 1 from instance_admins);
57
+ $$;
58
+
59
+ -- Whether the current user is on the books of at least one company here.
60
+ create or replace function is_any_company_member()
61
+ returns boolean
62
+ language sql
63
+ stable
64
+ security definer
65
+ set search_path = public, pg_temp
66
+ as $$
67
+ select exists (select 1 from company_members m where m.user_id = auth.uid());
68
+ $$;
69
+
70
+ comment on function is_any_company_member() is
71
+ 'Whether the current user belongs to at least one company of this installation.';
72
+
73
+ drop function if exists claim_instance_admin(uuid);
74
+
75
+ create or replace function claim_instance_admin(p_user_id uuid default auth.uid())
76
+ returns instance_admins
77
+ language plpgsql
78
+ as $$
79
+ declare
80
+ v_row instance_admins%rowtype;
81
+ begin
82
+ if p_user_id is null then
83
+ raise exception 'no_user: claim_instance_admin needs a signed-in user or an explicit id';
84
+ end if;
85
+
86
+ if exists (select 1 from instance_admins) and not is_instance_admin() then
87
+ raise exception 'instance_already_claimed: only an instance administrator can appoint another';
88
+ end if;
89
+
90
+ insert into instance_admins (user_id)
91
+ values (p_user_id)
92
+ on conflict (user_id) do nothing
93
+ returning * into v_row;
94
+
95
+ if v_row.user_id is null then
96
+ select * into v_row from instance_admins where user_id = p_user_id;
97
+ end if;
98
+
99
+ return v_row;
100
+ end;
101
+ $$;
102
+
103
+ comment on function claim_instance_admin(uuid) is
104
+ 'Makes a user an instance administrator. The first claim is open; afterwards only an administrator may appoint one.';
105
+
106
+ -- ---------------------------------------------------------------------------
107
+ -- Reading the instance row
108
+ -- ---------------------------------------------------------------------------
109
+
110
+ drop policy instance_select on instance;
111
+ create policy instance_select on instance
112
+ for select using (is_any_company_member() or is_instance_admin());
@@ -0,0 +1,17 @@
1
+ -- Ekwo OS — one natural key for tax posting templates.
2
+ --
3
+ -- `tax_posting_templates` had a surrogate primary key and a partial unique
4
+ -- index on the base line only, so re-applying a seed inserted every tax line
5
+ -- a second time and then failed on the base. Found on the first real
6
+ -- installation, when `ekwo init` was run twice on the same project.
7
+ --
8
+ -- A posting template is identified by the tax, the kind of document, the
9
+ -- posting type and its sequence. Making that a unique index gives the seeds a
10
+ -- conflict target and makes them idempotent, which `supabase/seed/README.md`
11
+ -- had promised all along.
12
+
13
+ create unique index tax_posting_templates_natural_key_idx
14
+ on tax_posting_templates (tax_template_id, document_kind, posting_type, sequence);
15
+
16
+ comment on index tax_posting_templates_natural_key_idx is
17
+ 'The natural key of a posting template; the conflict target of the seeds.';
@@ -0,0 +1,82 @@
1
+ -- Ekwo OS — a document's settlement is derived from its matching.
2
+ --
3
+ -- `documents.amount_paid` was a column somebody had to remember to write, while
4
+ -- the truth of what has been settled lived in `reconciliations`. That is the
5
+ -- rule of this schema broken on its own terms: totals are derived, never keyed
6
+ -- in. Matching a third-party line now moves the document, and `payment_state`
7
+ -- follows from there as it already did.
8
+ --
9
+ -- The recomputation is folded into the existing reconciliation trigger rather
10
+ -- than added as a second one, because two AFTER ROW triggers on the same table
11
+ -- fire in name order, and this one has to run after the line residuals have
12
+ -- been updated, not before.
13
+
14
+ create or replace function documents_refresh_amount_paid(p_document_id uuid)
15
+ returns void
16
+ language plpgsql
17
+ as $$
18
+ begin
19
+ -- The statement names amount_paid so that documents_refresh_payment_state,
20
+ -- which watches that column, fires in turn.
21
+ update documents d
22
+ set amount_paid = coalesce((
23
+ select sum(l.matched_amount)
24
+ from entry_lines l
25
+ join accounts a on a.id = l.account_id
26
+ where l.entry_id = d.entry_id
27
+ and a.reconcilable
28
+ and a.account_type in ('asset_receivable', 'liability_payable')
29
+ ), 0)
30
+ where d.id = p_document_id
31
+ and d.entry_id is not null;
32
+ end;
33
+ $$;
34
+
35
+ comment on function documents_refresh_amount_paid(uuid) is
36
+ 'Recomputes what a document has been settled by, from the matched amounts on its third-party lines.';
37
+
38
+ comment on column documents.amount_paid is
39
+ 'Derived from reconciliations on the third-party lines of the document''s entry. A value written by hand is replaced at the next matching.';
40
+
41
+ create or replace function reconciliations_refresh_lines()
42
+ returns trigger
43
+ language plpgsql
44
+ as $$
45
+ declare
46
+ v_ids uuid[];
47
+ v_id uuid;
48
+ v_doc uuid;
49
+ begin
50
+ v_ids := array_remove(array[
51
+ coalesce(new.debit_line_id, old.debit_line_id),
52
+ coalesce(new.credit_line_id, old.credit_line_id)
53
+ ], null);
54
+
55
+ foreach v_id in array v_ids loop
56
+ update entry_lines l
57
+ set matched_amount = coalesce((
58
+ select sum(r.amount) from reconciliations r
59
+ where r.debit_line_id = l.id or r.credit_line_id = l.id
60
+ ), 0),
61
+ matching_number = (
62
+ select r.matching_number from reconciliations r
63
+ where r.debit_line_id = l.id or r.credit_line_id = l.id
64
+ order by r.created_at
65
+ limit 1
66
+ )
67
+ where l.id = v_id;
68
+ end loop;
69
+
70
+ -- Then the documents those lines belong to, now that the residuals are right.
71
+ for v_doc in
72
+ select distinct d.id
73
+ from entry_lines l
74
+ join documents d on d.entry_id = l.entry_id
75
+ where l.id = any (v_ids)
76
+ loop
77
+ perform documents_refresh_amount_paid(v_doc);
78
+ end loop;
79
+
80
+ return null;
81
+ end;
82
+ $$;
@@ -0,0 +1,122 @@
1
+ -- Ekwo OS — booking a payment.
2
+ --
3
+ -- `post_document` turned an invoice into an entry from the first release;
4
+ -- money moving had no equivalent, so every client had to assemble the two
5
+ -- ledger lines of a payment itself. That is the one thing a client must never
6
+ -- do: the moment a caller writes `entry_lines`, the rules about sides,
7
+ -- accounts and locks live in whichever client happens to be writing them, and
8
+ -- there are as many answers as there are clients. So the rule moves here,
9
+ -- next to the other one.
10
+ --
11
+ -- What it books, and nothing else:
12
+ --
13
+ -- inbound — money in. debit the bank, credit the receivable.
14
+ -- outbound — money out. credit the bank, debit the payable.
15
+ --
16
+ -- The third-party account is resolved by role, the same way `post_document`
17
+ -- resolves it: the contact's override first, the company default second,
18
+ -- never by matching a code prefix. The bank side comes from the payment's
19
+ -- bank account, or from the default account of its journal.
20
+ --
21
+ -- What it deliberately does not do: match anything. A payment is money
22
+ -- arriving; which invoices it settles is a separate decision, taken by
23
+ -- `reconcile`, and conflating the two is how a payment ends up silently
24
+ -- allocated to the wrong invoice.
25
+
26
+ create or replace function post_payment(p_payment_id uuid)
27
+ returns entries
28
+ language plpgsql
29
+ as $$
30
+ declare
31
+ v_pay payments%rowtype;
32
+ v_entry entries%rowtype;
33
+ v_money uuid;
34
+ v_third uuid;
35
+ v_contact uuid;
36
+ v_inbound boolean;
37
+ v_label text;
38
+ begin
39
+ select * into v_pay from payments where id = p_payment_id for update;
40
+ if not found then
41
+ raise exception 'unknown_payment: payment % does not exist', p_payment_id;
42
+ end if;
43
+ if v_pay.entry_id is not null then
44
+ raise exception 'payment_already_booked: payment % already points at entry %',
45
+ p_payment_id, v_pay.entry_id;
46
+ end if;
47
+ if v_pay.state = 'cancelled' then
48
+ raise exception 'payment_cancelled: payment % cannot be booked', p_payment_id;
49
+ end if;
50
+
51
+ v_inbound := v_pay.direction = 'inbound';
52
+
53
+ -- The bank side: the account behind the payment's bank account, or the
54
+ -- default account of the journal it goes through.
55
+ select b.account_id into v_money
56
+ from bank_accounts b
57
+ where b.id = v_pay.bank_account_id;
58
+
59
+ if v_money is null then
60
+ select coalesce(j.default_account_id, b.account_id) into v_money
61
+ from journals j
62
+ left join bank_accounts b on b.id = j.bank_account_id
63
+ where j.id = v_pay.journal_id;
64
+ end if;
65
+
66
+ -- A bank account wired to the journal from its own side counts too: that is
67
+ -- the direction an operator fills in first.
68
+ if v_money is null then
69
+ select b.account_id into v_money
70
+ from bank_accounts b
71
+ where b.journal_id = v_pay.journal_id
72
+ and b.active
73
+ and b.account_id is not null
74
+ order by b.created_at
75
+ limit 1;
76
+ end if;
77
+
78
+ if v_money is null then
79
+ raise exception 'no_bank_account: set bank_account_id on the payment, or default_account_id on journal %',
80
+ v_pay.journal_id;
81
+ end if;
82
+
83
+ -- The third-party side, by role.
84
+ v_contact := commercial_entity(v_pay.contact_id);
85
+ v_third := resolve_counterpart_account(v_pay.company_id, v_contact, v_inbound);
86
+
87
+ v_label := coalesce(v_pay.reference, v_pay.memo, 'payment');
88
+
89
+ insert into entries (company_id, journal_id, fiscal_year_id, entry_date, reference,
90
+ description, state, currency_code)
91
+ values (v_pay.company_id, v_pay.journal_id,
92
+ fiscal_year_at(v_pay.company_id, v_pay.payment_date), v_pay.payment_date,
93
+ v_pay.reference, v_label, 'draft', v_pay.currency_code)
94
+ returning * into v_entry;
95
+
96
+ insert into entry_lines (entry_id, company_id, account_id, sequence, name,
97
+ debit, credit, currency_code)
98
+ values (v_entry.id, v_pay.company_id, v_money, 10, v_label,
99
+ case when v_inbound then v_pay.amount else 0 end,
100
+ case when v_inbound then 0 else v_pay.amount end,
101
+ v_pay.currency_code);
102
+
103
+ insert into entry_lines (entry_id, company_id, account_id, sequence, name,
104
+ debit, credit, contact_id, currency_code)
105
+ values (v_entry.id, v_pay.company_id, v_third, 20, v_label,
106
+ case when v_inbound then 0 else v_pay.amount end,
107
+ case when v_inbound then v_pay.amount else 0 end,
108
+ v_contact, v_pay.currency_code);
109
+
110
+ v_entry := post_entry(v_entry.id);
111
+
112
+ update payments
113
+ set entry_id = v_entry.id,
114
+ state = 'posted'
115
+ where id = p_payment_id;
116
+
117
+ return v_entry;
118
+ end;
119
+ $$;
120
+
121
+ comment on function post_payment(uuid) is
122
+ 'Books a payment: the bank side from the payment''s bank account or its journal, the third-party side by role. Matches nothing.';
@@ -0,0 +1,84 @@
1
+ -- Ekwo OS — the counters have to work for the person doing the booking.
2
+ --
3
+ -- `journal_sequences` and `matching_sequences` carry a select policy and no
4
+ -- other, which is right: nobody should be able to write a counter by hand.
5
+ -- But `next_entry_number()` and `next_matching_number()` write them, and they
6
+ -- ran as the caller — so posting an entry or drawing a matching letter
7
+ -- succeeded for the table owner and failed for every signed-in user with
8
+ -- "new row violates row-level security policy for table journal_sequences".
9
+ --
10
+ -- It went unnoticed because the tests exercised the schema as the owner, the
11
+ -- installer holds an owner connection, and until now nothing else wrote to
12
+ -- this database. The first client that signs in as a user — the MCP server —
13
+ -- hits it on the first invoice.
14
+ --
15
+ -- The counters are infrastructure, not user data: they exist so two
16
+ -- concurrent bookings do not draw the same number. So both functions become
17
+ -- SECURITY DEFINER, and each one checks first that the caller may write the
18
+ -- company it is counting for. A definer function that skipped that check
19
+ -- would be a way to burn numbers in somebody else's journal.
20
+
21
+ create or replace function next_entry_number(p_journal_id uuid, p_date date)
22
+ returns text
23
+ language plpgsql
24
+ security definer
25
+ set search_path = public, pg_temp
26
+ as $$
27
+ declare
28
+ v_code text;
29
+ v_company uuid;
30
+ v_year smallint := extract(year from p_date)::smallint;
31
+ v_number integer;
32
+ begin
33
+ select j.code, j.company_id into v_code, v_company from journals j where j.id = p_journal_id;
34
+ if v_code is null then
35
+ raise exception 'unknown_journal: journal % does not exist', p_journal_id;
36
+ end if;
37
+
38
+ -- The caller is about to write an entry in this company; they have to be
39
+ -- allowed to. auth.uid() is null on an owner connection, where row level
40
+ -- security is bypassed anyway and the installer is the caller.
41
+ if auth.uid() is not null and not can_write_company(v_company) then
42
+ raise exception 'not_allowed: you may not book in this company'
43
+ using errcode = '42501';
44
+ end if;
45
+
46
+ insert into journal_sequences (journal_id, year, last_number)
47
+ values (p_journal_id, v_year, 1)
48
+ on conflict (journal_id, year)
49
+ do update set last_number = journal_sequences.last_number + 1
50
+ returning last_number into v_number;
51
+
52
+ return v_code || '/' || v_year::text || '/' || lpad(v_number::text, 4, '0');
53
+ end;
54
+ $$;
55
+
56
+ comment on function next_entry_number(uuid, date) is
57
+ 'Next number for a journal and year, as CODE/YYYY/NNNN. Atomic: the counter row is locked, not the journal. Definer, because the counter is infrastructure and nobody writes it by hand.';
58
+
59
+ create or replace function next_matching_number(p_company_id uuid)
60
+ returns text
61
+ language plpgsql
62
+ security definer
63
+ set search_path = public, pg_temp
64
+ as $$
65
+ declare
66
+ v_number integer;
67
+ begin
68
+ if auth.uid() is not null and not can_write_company(p_company_id) then
69
+ raise exception 'not_allowed: you may not match in this company'
70
+ using errcode = '42501';
71
+ end if;
72
+
73
+ insert into matching_sequences (company_id, last_number)
74
+ values (p_company_id, 1)
75
+ on conflict (company_id)
76
+ do update set last_number = matching_sequences.last_number + 1
77
+ returning last_number into v_number;
78
+
79
+ return 'A' || lpad(v_number::text, 4, '0');
80
+ end;
81
+ $$;
82
+
83
+ comment on function next_matching_number(uuid) is
84
+ 'Next reconciliation letter for a company, as A0001. Definer, for the same reason as next_entry_number.';