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,252 @@
1
+ -- Ekwo OS — a posted entry is immutable, in an open period too.
2
+ --
3
+ -- The schema has said so from the first day, in comments, in the description
4
+ -- of the audit trail — "a posted entry is immutable and is corrected by a
5
+ -- reversal, so what is recorded is the act of posting and never the lines" —
6
+ -- and in every design decision that leans on it. What enforced it was
7
+ -- `entries_guard_period`, and that guards a *locked* period. In an open one,
8
+ -- established under row level security as a member holding `entries.write`:
9
+ --
10
+ -- * the lines of a posted entry could be deleted, all of them;
11
+ -- * the entry itself could be deleted;
12
+ -- * its state could be set back to `draft`, after which it is an ordinary
13
+ -- draft and anything goes;
14
+ -- * an amount on its own was refused — by `entries_posted_is_balanced`, a
15
+ -- check that the entry still balances, which two amounts changed together
16
+ -- satisfy.
17
+ --
18
+ -- The audit trail records the posting and deliberately not the lines, on the
19
+ -- ground that they cannot change. So a change to them was not only possible,
20
+ -- it left no trace.
21
+ --
22
+ -- Same shape as `20260918161204` for the document, for the same reason and
23
+ -- with the same absence of an exemption. Once an entry has left `draft`:
24
+ --
25
+ -- * it is not deleted and its state does not change. A posted entry is
26
+ -- undone by a reversal — a second entry naming it in `reversed_entry_id` —
27
+ -- which is what `reopen_fiscal_year()` already does;
28
+ -- * no column of it moves but `updated_at`. `total_debit` and `total_credit`
29
+ -- are derived from lines that no longer move;
30
+ -- * its lines take no insert and no delete, and keep every column but the
31
+ -- two the matching writes — `matching_number` and `matched_amount` — which
32
+ -- is the exception `entry_lines_guard_period` has made for a locked period
33
+ -- since the first migration: matching is not a change to the accounts.
34
+ --
35
+ -- Closed lists again: a column added later is frozen the day it is added.
36
+ --
37
+ -- **Every path of the schema that writes an entry was read before this was
38
+ -- written**, because a guard nobody is exempt from has to be one nobody needs
39
+ -- an exemption from. They all do the same thing: insert a `draft`, insert its
40
+ -- lines, call `post_entry()`.
41
+ --
42
+ -- * `post_document()`, `post_payment()`, `post_module_entry()` — which is how
43
+ -- every module reaches the ledger —, `opening_balance()`,
44
+ -- `settle_cash_basis_tax()`, `settle_filing()`: a draft, its lines,
45
+ -- `post_entry()`. Nothing is touched afterwards.
46
+ -- * `close_fiscal_year()`: the same, twice; its one `delete from entries` is
47
+ -- of the closing entry it has just created as a draft and found nothing to
48
+ -- put in.
49
+ -- * `reconcile()` books an exchange difference as a new entry, and
50
+ -- `unreconcile()` undoes it with a **mirror** naming it in
51
+ -- `reversed_entry_id` — it never deletes or edits the first.
52
+ -- `reopen_fiscal_year()` does the same for the entries of a close.
53
+ -- * `reconciliations_refresh_lines()` writes `matched_amount` and
54
+ -- `matching_number` on posted lines: the closed list below.
55
+ -- * `entries_refresh_totals()` rewrote `total_debit` and `total_credit` on
56
+ -- every write of a line, whatever the state. The lines of a posted entry
57
+ -- no longer move, so it now acts on drafts.
58
+ --
59
+ -- So the reversal is already how this schema undoes an entry, everywhere it
60
+ -- does; what was missing is that nothing made it the *only* way.
61
+ --
62
+ -- **Nobody is born posted**, as for a document and for the same reason: an
63
+ -- entry inserted with a state that is not `draft`, or a line inserted under
64
+ -- one, is `entry_born_posted` for everybody, the installer included. Books
65
+ -- kept elsewhere arrive through `import_company()`, which switches the
66
+ -- triggers of the tables it fills off by name for the time of the load and
67
+ -- needs no exemption here. What it loads is still judged by what no switch
68
+ -- turns off — `entries_posted_is_balanced`, `entries_posted_has_number` — and
69
+ -- is frozen afterwards like everything else. The period guards are triggers
70
+ -- and are off during a load with the rest: an archive brings closed years with
71
+ -- it, and whether a loaded entry may sit in one is the import's question.
72
+ --
73
+ -- **The guards are `security definer`**, for the reason found on the document:
74
+ -- asked under the caller's row level security, "is the company still there?"
75
+ -- is answered *no row* by whoever cannot read `companies`, and that answer let
76
+ -- a delete through. Unlike the guard of a document line they are not named
77
+ -- to fire first: the older guards of these two tables — who may post, what a
78
+ -- module tag may become, which period is locked — refuse more precisely what
79
+ -- they refuse, and keep answering first where they do.
80
+ --
81
+ -- `draft` to `posted` is `post_entry()`'s one statement on a row that is still
82
+ -- a draft, and passes untouched. The cascade of a company being deleted passes
83
+ -- for the reason it does on a document: the company's row is already gone.
84
+ -- The period guards stay exactly as they are and keep their own job — a draft
85
+ -- dated in a locked period is theirs to refuse, not this file's.
86
+
87
+ create or replace function entries_guard_posted()
88
+ returns trigger
89
+ language plpgsql
90
+ security definer
91
+ set search_path = public, pg_temp
92
+ as $$
93
+ declare
94
+ v_moved text[];
95
+ begin
96
+ if tg_op = 'INSERT' then
97
+ if new.state <> 'draft' then
98
+ raise exception 'entry_born_posted: an entry is written as a draft and posted by post_entry(). One that is already % arrives with a company being loaded, by import_company(), and in no other way.',
99
+ new.state
100
+ using errcode = '55006';
101
+ end if;
102
+ return new;
103
+ end if;
104
+
105
+ if old.state = 'draft' then
106
+ return coalesce(new, old);
107
+ end if;
108
+
109
+ if tg_op = 'DELETE' then
110
+ if not exists (select 1 from companies c where c.id = old.company_id) then
111
+ return old;
112
+ end if;
113
+ raise exception 'entry_posted: entry % is % and cannot be deleted. A posted entry is undone by a reversal that names it.',
114
+ coalesce(old.number, old.id::text), old.state
115
+ using errcode = '55006';
116
+ end if;
117
+
118
+ if new.state is distinct from old.state then
119
+ raise exception 'entry_posted: entry % is % and stays so. A posted entry is undone by a reversal that names it, never by changing its state to %.',
120
+ coalesce(old.number, old.id::text), old.state, new.state
121
+ using errcode = '55006';
122
+ end if;
123
+
124
+ select array_agg(n.key order by n.key)
125
+ into v_moved
126
+ from jsonb_each(to_jsonb(new)) n
127
+ join jsonb_each(to_jsonb(old)) o on o.key = n.key
128
+ where n.value is distinct from o.value
129
+ -- `is_balanced` is generated, and not yet computed in a `before` trigger.
130
+ and n.key not in ('updated_at', 'is_balanced');
131
+
132
+ if v_moved is not null then
133
+ raise exception 'entry_posted: entry % was posted and keeps its % (%). A posted entry is undone by a reversal that names it.',
134
+ coalesce(old.number, old.id::text),
135
+ case when array_length(v_moved, 1) = 1 then 'value of' else 'values of' end,
136
+ array_to_string(v_moved, ', ')
137
+ using errcode = '55006';
138
+ end if;
139
+ return new;
140
+ end;
141
+ $$;
142
+
143
+ comment on function entries_guard_posted() is
144
+ 'Once an entry has left draft: it is not deleted, its state does not change and no column moves. Refuses entry_posted, by name, to everybody, and entry_born_posted to whoever inserts one that is already posted: that arrives only with a company loaded by import_company(). The period guards keep their own job.';
145
+
146
+ create trigger entries_guard_posted
147
+ before insert or update or delete on entries
148
+ for each row execute function entries_guard_posted();
149
+
150
+ revoke execute on function entries_guard_posted() from public, anon, authenticated, service_role;
151
+
152
+ create or replace function entry_lines_guard_posted()
153
+ returns trigger
154
+ language plpgsql
155
+ security definer
156
+ set search_path = public, pg_temp
157
+ as $$
158
+ declare
159
+ -- What the matching writes, and nothing else. `balance` is generated from
160
+ -- two columns that are frozen, and is not yet computed when a `before`
161
+ -- trigger reads the new row.
162
+ c_still_moves constant text[] := array['matching_number', 'matched_amount', 'balance', 'updated_at'];
163
+ v_state entry_state;
164
+ v_number text;
165
+ v_moved text[];
166
+ begin
167
+ -- The entry the line is leaving, where it is leaving one: a line does not
168
+ -- walk out of a posted entry into a draft.
169
+ select e.state, e.number into v_state, v_number
170
+ from entries e where e.id = coalesce(old.entry_id, new.entry_id);
171
+ if found and v_state = 'draft' and tg_op = 'UPDATE' and new.entry_id is distinct from old.entry_id then
172
+ select e.state, e.number into v_state, v_number from entries e where e.id = new.entry_id;
173
+ end if;
174
+
175
+ if not found or v_state = 'draft' then
176
+ return coalesce(new, old);
177
+ end if;
178
+
179
+ if tg_op = 'DELETE' then
180
+ if not exists (select 1 from companies c where c.id = old.company_id) then
181
+ return old;
182
+ end if;
183
+ raise exception 'entry_posted: line % of entry % was posted and cannot be deleted. A posted entry is undone by a reversal that names it.',
184
+ old.sequence, coalesce(v_number, old.entry_id::text)
185
+ using errcode = '55006';
186
+ end if;
187
+
188
+ if tg_op = 'INSERT' then
189
+ raise exception 'entry_posted: entry % was posted with the lines it has, and takes no other. A posted entry is undone by a reversal that names it.',
190
+ coalesce(v_number, new.entry_id::text)
191
+ using errcode = '55006';
192
+ end if;
193
+
194
+ select array_agg(n.key order by n.key)
195
+ into v_moved
196
+ from jsonb_each(to_jsonb(new)) n
197
+ join jsonb_each(to_jsonb(old)) o on o.key = n.key
198
+ where n.value is distinct from o.value
199
+ and n.key <> all (c_still_moves);
200
+
201
+ if v_moved is not null then
202
+ raise exception 'entry_posted: line % of entry % was posted and keeps its % (%). A posted entry is undone by a reversal that names it.',
203
+ old.sequence, coalesce(v_number, old.entry_id::text),
204
+ case when array_length(v_moved, 1) = 1 then 'value of' else 'values of' end,
205
+ array_to_string(v_moved, ', ')
206
+ using errcode = '55006';
207
+ end if;
208
+ return new;
209
+ end;
210
+ $$;
211
+
212
+ comment on function entry_lines_guard_posted() is
213
+ 'Once an entry has left draft, its lines are the lines that were posted: no insert, no delete, and no column changed but the two the matching writes, matching_number and matched_amount. Refuses entry_posted, by name, to everybody.';
214
+
215
+ create trigger entry_lines_guard_posted
216
+ before insert or update or delete on entry_lines
217
+ for each row execute function entry_lines_guard_posted();
218
+
219
+ revoke execute on function entry_lines_guard_posted() from public, anon, authenticated, service_role;
220
+
221
+ -- ---------------------------------------------------------------------------
222
+ -- The totals of a posted entry are not recomputed
223
+ --
224
+ -- `20260911120400` as it was, acting on drafts only. The lines of a posted
225
+ -- entry do not move, so there is nothing to recompute; and a trigger that
226
+ -- restates the totals of a posted entry from its lines is the mechanism by
227
+ -- which a changed line used to become a changed entry.
228
+ -- ---------------------------------------------------------------------------
229
+
230
+ create or replace function entries_refresh_totals()
231
+ returns trigger
232
+ language plpgsql
233
+ as $$
234
+ declare
235
+ v_entry_id uuid := coalesce(new.entry_id, old.entry_id);
236
+ begin
237
+ update entries e
238
+ set total_debit = coalesce(s.debit, 0),
239
+ total_credit = coalesce(s.credit, 0)
240
+ from (
241
+ select sum(l.debit) as debit, sum(l.credit) as credit
242
+ from entry_lines l
243
+ where l.entry_id = v_entry_id
244
+ ) s
245
+ where e.id = v_entry_id
246
+ and e.state = 'draft';
247
+ return null;
248
+ end;
249
+ $$;
250
+
251
+ comment on function entries_refresh_totals() is
252
+ 'Keeps the totals of a draft entry in step with its lines. A posted entry keeps the totals it was posted, or loaded, with.';
@@ -0,0 +1,289 @@
1
+ -- Ekwo OS — an entry is posted by post_entry(), a document by post_document().
2
+ --
3
+ -- The two guards published this morning freeze what is posted. They looked at
4
+ -- the way *out* of `posted` and let the way *in* alone, on the ground that the
5
+ -- transition is one statement of `post_entry()`. It is — and it is also one
6
+ -- statement of anybody's. Established as the `accountant` preset, which holds
7
+ -- `entries.post`, under row level security, on a balanced draft:
8
+ --
9
+ -- update entries set state = 'posted', number = 'HAND/1' where id = …
10
+ --
11
+ -- went through. What stood in the way was two check constraints (a posted
12
+ -- entry has a number and balances), the capability, and the lock of the
13
+ -- period as `entries_guard_period` reads it. What did not: the number was
14
+ -- chosen by hand, in a country whose law forbids a hole in the sequence and
15
+ -- where `post_entry()` refuses exactly that by name (`numbering_gapless`);
16
+ -- `posted_at` and `fiscal_year_id` stayed null; an entry with no line at all
17
+ -- balances at zero and was accepted; the period was not asked the stricter
18
+ -- question `post_entry()` asks. And the guard of this morning then froze the
19
+ -- result for good.
20
+ --
21
+ -- The same on a document: `update documents set state = 'posted', entry_id = …`
22
+ -- pointing at an entry that is legitimately posted and is *not* the one the
23
+ -- document produced — an entry keyed by hand, say — went through, because
24
+ -- `document_posted_without_entry` only asked that the entry be posted.
25
+ --
26
+ -- **Judged on facts, not on a flag.** The obvious fix is a transaction-local
27
+ -- setting that `post_entry()` raises and the guard reads. It does not hold: a
28
+ -- custom setting is writable by any session that can run `set_config`, so the
29
+ -- flag would say "post_entry is running" for whoever said so first — and
30
+ -- `post_entry()` runs as its caller, so there is no privilege behind which to
31
+ -- hide it. What `post_entry()` *produces*, on the other hand, is all on the
32
+ -- row and in the counter, and can be checked there whoever wrote it:
33
+ --
34
+ -- * `posted_at` is set, and `fiscal_year_id`, where there is one, is the year
35
+ -- the date falls in;
36
+ -- * the entry has at least one line (`entry_empty`);
37
+ -- * the period is open, asked the way `post_entry()` asks it;
38
+ -- * **the number was drawn, not chosen**, wherever the rule applies: in a
39
+ -- country that numbers without a hole, for a caller who does not hold
40
+ -- `entries.import`, the number has to be the last one the counter of its
41
+ -- journal delivered. `next_entry_number()` advances that counter in the
42
+ -- same statement and holds its row until the transaction ends, and a number
43
+ -- already used is refused by `entries_company_number_idx` — so "equal to
44
+ -- the last delivered, and unique" *is* "just drawn". Elsewhere a number
45
+ -- chosen by hand is what `post_entry()` allows too, and the guard then
46
+ -- brings the counter up to it, as the function does.
47
+ --
48
+ -- A row that satisfies all of that is, fact for fact, the row `post_entry()`
49
+ -- writes, and is let through whoever wrote it: a rule about facts cannot refuse
50
+ -- the facts, and nothing is lost by it. `post_entry()` is unchanged, and so are
51
+ -- the seven functions that call it; `import_company()` switches the guard off
52
+ -- for a load, as before.
53
+ --
54
+ -- The cost is said plainly: the conditions of `post_entry()` are now read in
55
+ -- two places, the function and the guard. `tests/posted_by_hand.test.ts` walks
56
+ -- every refusal of the function beside the same attempt by hand, so that a
57
+ -- condition added to one and not to the other fails a test.
58
+ --
59
+ -- For a document the facts are on two rows: the entry names this document
60
+ -- (`entries.document_id`, written by `post_document()` when it builds it, and
61
+ -- frozen with the entry), the document is booked on its entry's day, and it
62
+ -- carries the number it had, or its entry's. `document_posted_by_hand`
63
+ -- otherwise.
64
+
65
+ -- ---------------------------------------------------------------------------
66
+ -- 1. The guard of an entry, asking at the transition what post_entry() leaves
67
+ --
68
+ -- `20260918161538` as it was, with the transition judged. It is written in the
69
+ -- trigger and not in a function beside it: a function nobody may call is a
70
+ -- function the grants have no honest line for.
71
+ -- ---------------------------------------------------------------------------
72
+
73
+ create or replace function entries_guard_posted()
74
+ returns trigger
75
+ language plpgsql
76
+ security definer
77
+ set search_path = public, pg_temp
78
+ as $$
79
+ declare
80
+ v_moved text[];
81
+ v_format text;
82
+ v_gapless boolean;
83
+ v_code text;
84
+ v_period smallint;
85
+ v_last integer;
86
+ begin
87
+ if tg_op = 'INSERT' then
88
+ if new.state <> 'draft' then
89
+ raise exception 'entry_born_posted: an entry is written as a draft and posted by post_entry(). One that is already % arrives with a company being loaded, by import_company(), and in no other way.',
90
+ new.state
91
+ using errcode = '55006';
92
+ end if;
93
+ return new;
94
+ end if;
95
+
96
+ if old.state = 'draft' then
97
+ if tg_op = 'UPDATE' and new.state = 'posted' then
98
+ if new.posted_at is null then
99
+ raise exception 'entry_posted_by_hand: entry % would be posted with no instant it was posted at. An entry is posted by post_entry() and by nothing else.',
100
+ coalesce(new.number, new.id::text)
101
+ using errcode = '55006';
102
+ end if;
103
+ -- Null is what post_entry() itself writes where it finds no year — a company
104
+ -- that has not opened one, or a caller who cannot read `fiscal_years`, which
105
+ -- is a machine key today. A year that is *named* has to be the right one.
106
+ if new.fiscal_year_id is not null
107
+ and new.fiscal_year_id is distinct from fiscal_year_at(new.company_id, new.entry_date) then
108
+ raise exception 'entry_posted_by_hand: entry % would be posted outside the financial year its date falls in. An entry is posted by post_entry() and by nothing else.',
109
+ coalesce(new.number, new.id::text)
110
+ using errcode = '55006';
111
+ end if;
112
+
113
+ if not exists (select 1 from entry_lines l where l.entry_id = new.id) then
114
+ raise exception 'entry_empty: entry % has no lines', new.id;
115
+ end if;
116
+
117
+ perform assert_period_open(new.company_id, new.entry_date, true);
118
+
119
+ select r.number_format, r.numbering_gapless into v_format, v_gapless
120
+ from numbering_rules(new.company_id) r;
121
+ select j.code into v_code from journals j where j.id = new.journal_id;
122
+ v_period := case when v_format ~ '\{(YYYY|YY)\}'
123
+ then extract(year from new.entry_date)::smallint
124
+ else 0::smallint end;
125
+ select s.last_number into v_last
126
+ from journal_sequences s
127
+ where s.journal_id = new.journal_id and s.year = v_period;
128
+
129
+ if v_last is not null and new.number = format_number(v_format, v_code, new.entry_date, v_last) then
130
+ return new; -- drawn from the counter, a moment ago, by this transaction
131
+ end if;
132
+
133
+ -- A number chosen by hand: on the draft beforehand, or in this statement.
134
+ if v_gapless and not (auth.uid() is not null and has_capability(new.company_id, 'entries.import')) then
135
+ raise exception 'entry_posted_by_hand: % is not the number the counter of this journal delivered, and this country forbids a hole in the sequence. An entry is posted by post_entry(), which draws it; books that already have numbers are brought in by whoever holds entries.import.',
136
+ new.number
137
+ using errcode = '55006';
138
+ end if;
139
+ perform catch_up_journal_sequence(new.journal_id, new.entry_date, new.number);
140
+ end if;
141
+ return coalesce(new, old);
142
+ end if;
143
+
144
+ if tg_op = 'DELETE' then
145
+ if not exists (select 1 from companies c where c.id = old.company_id) then
146
+ return old;
147
+ end if;
148
+ raise exception 'entry_posted: entry % is % and cannot be deleted. A posted entry is undone by a reversal that names it.',
149
+ coalesce(old.number, old.id::text), old.state
150
+ using errcode = '55006';
151
+ end if;
152
+
153
+ if new.state is distinct from old.state then
154
+ raise exception 'entry_posted: entry % is % and stays so. A posted entry is undone by a reversal that names it, never by changing its state to %.',
155
+ coalesce(old.number, old.id::text), old.state, new.state
156
+ using errcode = '55006';
157
+ end if;
158
+
159
+ select array_agg(n.key order by n.key)
160
+ into v_moved
161
+ from jsonb_each(to_jsonb(new)) n
162
+ join jsonb_each(to_jsonb(old)) o on o.key = n.key
163
+ where n.value is distinct from o.value
164
+ -- `is_balanced` is generated, and not yet computed in a `before` trigger.
165
+ and n.key not in ('updated_at', 'is_balanced');
166
+
167
+ if v_moved is not null then
168
+ raise exception 'entry_posted: entry % was posted and keeps its % (%). A posted entry is undone by a reversal that names it.',
169
+ coalesce(old.number, old.id::text),
170
+ case when array_length(v_moved, 1) = 1 then 'value of' else 'values of' end,
171
+ array_to_string(v_moved, ', ')
172
+ using errcode = '55006';
173
+ end if;
174
+ return new;
175
+ end;
176
+ $$;
177
+
178
+ -- ---------------------------------------------------------------------------
179
+ -- 2. The guard of a document, asking whose entry it is
180
+ --
181
+ -- `20260918161204` as it was; the transition reads the entry instead of only
182
+ -- finding it.
183
+ -- ---------------------------------------------------------------------------
184
+
185
+ create or replace function documents_guard_posted()
186
+ returns trigger
187
+ language plpgsql
188
+ security definer
189
+ set search_path = public, pg_temp
190
+ as $$
191
+ declare
192
+ -- What may still move once the state has left `draft`. A closed list: a
193
+ -- column that is not named here is frozen, including one that does not
194
+ -- exist yet.
195
+ c_still_moves constant text[] := array[
196
+ 'amount_paid', 'amount_residual', 'payment_state',
197
+ 'sent_at', 'peppol_status', 'peppol_message_id',
198
+ 'updated_at'
199
+ ];
200
+ v_moved text[];
201
+ v_entry entries%rowtype;
202
+ begin
203
+ if tg_op = 'INSERT' then
204
+ if new.state <> 'draft' then
205
+ raise exception 'document_born_posted: a document is written as a draft and posted by post_document(). One that is already % arrives with a company being loaded, by import_company(), and in no other way.',
206
+ new.state
207
+ using errcode = '55006';
208
+ end if;
209
+ return new;
210
+ end if;
211
+
212
+ if tg_op = 'DELETE' then
213
+ if old.state = 'draft'
214
+ or not exists (select 1 from companies c where c.id = old.company_id) then
215
+ return old;
216
+ end if;
217
+ raise exception 'document_posted: % % was issued and cannot be deleted. It is undone by a credit note that names it.',
218
+ old.doc_type, coalesce(old.number, old.id::text)
219
+ using errcode = '55006';
220
+ end if;
221
+
222
+ if old.state = 'draft' then
223
+ -- The one transition that produces an entry has to have produced one.
224
+ if new.state = 'posted' then
225
+ select e.* into v_entry from entries e where e.id = new.entry_id and e.state = 'posted';
226
+ if not found then
227
+ raise exception 'document_posted_without_entry: % has no posted entry. A document is posted by post_document(), which builds its entry and posts it first.',
228
+ coalesce(new.number, new.id::text)
229
+ using errcode = '55006';
230
+ end if;
231
+ -- What post_document() leaves behind, read on the two rows: the entry
232
+ -- was built for this document and for no other, the document is booked
233
+ -- on the day its entry is, and it is numbered as it was — or, where it
234
+ -- had no number, as its entry.
235
+ if v_entry.document_id is distinct from new.id
236
+ or new.accounting_date is distinct from v_entry.entry_date
237
+ or new.number is distinct from coalesce(old.number, v_entry.number) then
238
+ raise exception 'document_posted_by_hand: % is not what post_document() writes — its entry % was not built for it, or is not booked on its day, or did not give it its number. A document is posted by post_document() and by nothing else.',
239
+ coalesce(new.number, new.id::text), coalesce(v_entry.number, v_entry.id::text)
240
+ using errcode = '55006';
241
+ end if;
242
+ end if;
243
+ return new;
244
+ end if;
245
+
246
+ if new.state is distinct from old.state then
247
+ raise exception 'document_posted: % % is % and stays so. It is undone by a credit note that names it, never by changing its state to %.',
248
+ old.doc_type, coalesce(old.number, old.id::text), old.state, new.state
249
+ using errcode = '55006';
250
+ end if;
251
+
252
+ select array_agg(n.key order by n.key)
253
+ into v_moved
254
+ from jsonb_each(to_jsonb(new)) n
255
+ join jsonb_each(to_jsonb(old)) o on o.key = n.key
256
+ where n.value is distinct from o.value
257
+ and n.key <> all (c_still_moves);
258
+
259
+ if v_moved is not null then
260
+ raise exception 'document_posted: % % was issued and keeps its % (%). What produced an entry does not change after it; credit it and issue another.',
261
+ old.doc_type, coalesce(old.number, old.id::text),
262
+ case when array_length(v_moved, 1) = 1 then 'value of' else 'values of' end,
263
+ array_to_string(v_moved, ', ')
264
+ using errcode = '55006';
265
+ end if;
266
+
267
+ if new.amount_paid is distinct from old.amount_paid
268
+ and new.amount_paid is distinct from document_amount_paid(new.id) then
269
+ raise exception 'document_amount_paid_is_derived: % has been settled by %, which is what its matching says. The figure is never keyed.',
270
+ coalesce(old.number, old.id::text), document_amount_paid(new.id)
271
+ using errcode = '55006';
272
+ end if;
273
+
274
+ if new.payment_state is distinct from old.payment_state
275
+ and new.amount_paid is not distinct from old.amount_paid then
276
+ raise exception 'document_payment_state_is_derived: the settlement of % follows from what was matched against it, and is never keyed.',
277
+ coalesce(old.number, old.id::text)
278
+ using errcode = '55006';
279
+ end if;
280
+
281
+ return new;
282
+ end;
283
+ $$;
284
+
285
+ comment on function documents_guard_posted() is
286
+ 'Once a document has left draft: its state does not change, it is not deleted, and no column moves but the closed list of what happens to a document after it is issued — amount_paid (to the figure the matching gives, and no other), payment_state, sent_at, peppol_status, peppol_message_id. Refuses document_posted, by name, to everybody. Becoming posted is held to what post_document() writes — a posted entry built for this document, booked on its day, that gave it its number — document_posted_by_hand otherwise; and nobody inserts a document that is already posted (document_born_posted).';
287
+
288
+ comment on function entries_guard_posted() is
289
+ 'Once an entry has left draft: it is not deleted, its state does not change and no column moves (entry_posted); nobody inserts one that is already posted (entry_born_posted). Becoming posted is held to what post_entry() produces, read on the row and in the counter — an instant it was posted at, no financial year but the one its date falls in, at least one line, an open period, and, where the country numbers without a hole and the caller does not hold entries.import, the number the counter of its journal has just delivered: entry_posted_by_hand otherwise.';
@@ -0,0 +1,53 @@
1
+ -- Ekwo OS — the schema this release defines is 0.4.0.
2
+ --
3
+ -- `ekwo_schema_version()` is the one place the number lives, and a migration
4
+ -- is the only thing that moves it. A database then answers for the files it
5
+ -- has actually run rather than for what somebody believed it had run: the
6
+ -- column default and `init_instance()` both call this function, so a fresh
7
+ -- install records the new number without a second edit, and `ekwo migrate`
8
+ -- reads it back onto `instance.schema_version` for a database that was
9
+ -- installed at an older one.
10
+ --
11
+ -- Between 0.3.0 and here the schema gained the whole life of a declaration,
12
+ -- a firm that keeps the books of its clients, a statement a bank sent, and a
13
+ -- posted row that does not move. The packages of this release read all of it,
14
+ -- and a 0.3.0 database has none of it: `tax_filings` with `tax_filing_boxes`
15
+ -- and `tax_filing_deposits`, and `file_filing()`, `record_filing_outcome()`,
16
+ -- `reopen_filing()`, `upcoming_filings()` and `filing_drift()` around them;
17
+ -- the `client` preset with `documents.deposit`, `company.export`, and
18
+ -- `portfolio_upcoming_filings()` and `portfolio_filings_touched_since()`;
19
+ -- `export_company()` and `import_company()` with `company_archive_registry`;
20
+ -- `import_bank_statement()` with `bank_statement_lines` and
21
+ -- `bank_transactions.import_key`; `document_lines.vat_category` and
22
+ -- `vat_rate`, there since the first schema and written at last, then frozen
23
+ -- at posting, and `companies.peppol_scheme` and
24
+ -- `peppol_identifier`, which the invoice written for the network is read
25
+ -- from; `documents.client_ref` and `rehearse_post_document()`, which the
26
+ -- command line keeps books through; `companies_with_capability()`, which
27
+ -- every policy of a company's table now asks once per statement; and
28
+ -- `document_amount_paid()` with the four guards that refuse, by name, a change
29
+ -- to a posted document, to a posted entry, and a posting that did not go
30
+ -- through `post_entry()` — `document_posted`, `entry_posted`,
31
+ -- `document_born_posted`, `entry_born_posted`, `entry_posted_by_hand`,
32
+ -- `document_posted_by_hand` — which the command line and the MCP server
33
+ -- explain rather than report as a failure. So the packages declare 0.4.0 as
34
+ -- their floor and refuse an older database by name, rather than improvising
35
+ -- over a column that is not there.
36
+
37
+ create or replace function ekwo_schema_version()
38
+ returns text
39
+ language sql
40
+ immutable
41
+ as $$
42
+ select '0.4.0'::text;
43
+ $$;
44
+
45
+ comment on function ekwo_schema_version() is
46
+ 'Schema version of the installed release. Bumped by a migration, never by hand.';
47
+
48
+ -- `create or replace` keeps the privileges the function already had, and the
49
+ -- revoke is what every migration of this directory ends with: a function that
50
+ -- comes out with the built-in default is published by Supabase as an
51
+ -- anonymous RPC endpoint.
52
+ revoke execute on all functions in schema public from public;
53
+ grant execute on function ekwo_schema_version() to authenticated, service_role;
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://ekwo.ai/schemas/module/1.json",
3
+ "code": "assets",
4
+ "name": "Fixed assets",
5
+ "description": "Fixed assets, their depreciation schedule and their disposal. The durations, the declining coefficients and the prorata convention of a country are pack data; the engine knows no country.",
6
+ "schema": "assets",
7
+ "version": "1.0.0",
8
+ "status": "available",
9
+ "requires_socle_min": "20260913075903",
10
+ "posts": true,
11
+ "mcp": { "prefix": "assets" },
12
+ "pack": { "section": "assets" }
13
+ }