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,428 @@
1
+ -- Ekwo OS — moving a company from one version of a country pack to the next.
2
+ --
3
+ -- A pack is versioned and a company records the version it copied, but until
4
+ -- now nothing could say what separated the two. `ekwo status` printed the
5
+ -- warning — "this company copied BE 1.0.0, this installation holds 1.4.0" —
6
+ -- and stopped there, because a chart of accounts is not a file to overwrite:
7
+ -- the company has been booking on those accounts for a year.
8
+ --
9
+ -- The difference is computed by natural key — an account code, a journal
10
+ -- code, a tax code — and every difference falls into one of three rules:
11
+ --
12
+ -- addition the pack has something the company does not. Copied in. A new
13
+ -- account or a new tax takes nothing away and breaks nothing.
14
+ -- closure the pack has closed the validity of a tax the company still
15
+ -- holds open. Applied. A rate that changes is a new tax plus a
16
+ -- `valid_to` on the old one, never an edit, so this is how a rate
17
+ -- change reaches a company: the old one stops, the new one is an
18
+ -- addition.
19
+ -- review everything else — a label that differs, a rate that differs on
20
+ -- the same code, a posting that books somewhere else, a row the
21
+ -- company holds and the pack does not. Listed, never applied
22
+ -- without the caller saying so.
23
+ --
24
+ -- The third rule is the point of the whole thing. Silently overwriting a
25
+ -- company's chart of accounts from a pack is how an upgrade destroys a year
26
+ -- of bookkeeping, and there is no way to be sure from here which of the two is
27
+ -- right: an operator may have renamed an account deliberately. So it is shown
28
+ -- and it waits.
29
+ --
30
+ -- One row the caller never gets applied whatever they ask: a row the company
31
+ -- holds and the pack does not. Nothing is deleted from a company's books by an
32
+ -- upgrade — an account may carry entries, a tax may be on a posted document —
33
+ -- and a pack that retires a code retires it, which is a `valid_to`, not a
34
+ -- deletion.
35
+
36
+ create type pack_change_rule as enum ('addition', 'closure', 'review');
37
+
38
+ comment on type pack_change_rule is
39
+ 'What an upgrade does with a difference: copy it in, close a validity, or show it and wait.';
40
+
41
+ -- ---------------------------------------------------------------------------
42
+ -- The difference
43
+ -- ---------------------------------------------------------------------------
44
+
45
+ create or replace function pack_upgrade_diff(
46
+ p_company_id uuid,
47
+ p_country char(2) default null
48
+ )
49
+ returns table (
50
+ object text,
51
+ key text,
52
+ change text,
53
+ rule pack_change_rule,
54
+ detail jsonb
55
+ )
56
+ language plpgsql
57
+ stable
58
+ as $$
59
+ declare
60
+ v_country char(2);
61
+ v_chart text;
62
+ begin
63
+ select coalesce(p_country, c.country) into v_country
64
+ from companies c where c.id = p_company_id;
65
+ if v_country is null then
66
+ raise exception 'unknown_company: %', p_company_id;
67
+ end if;
68
+
69
+ select cp.chart_code into v_chart
70
+ from company_packs cp
71
+ where cp.company_id = p_company_id and cp.country = v_country;
72
+ if v_chart is null then
73
+ raise exception 'no_pack_installed: company % holds no % pack, so there is nothing to upgrade from', p_company_id, v_country;
74
+ end if;
75
+
76
+ -- Accounts -----------------------------------------------------------------
77
+ return query
78
+ select 'account', t.code, 'missing', 'addition'::pack_change_rule,
79
+ jsonb_build_object('pack', jsonb_build_object('name', t.name, 'account_type', t.account_type))
80
+ from account_templates t
81
+ where t.country = v_country and t.chart_code = v_chart
82
+ and not exists (select 1 from accounts a where a.company_id = p_company_id and a.code = t.code);
83
+
84
+ return query
85
+ select 'account', a.code, 'differs', 'review'::pack_change_rule,
86
+ jsonb_build_object(
87
+ 'pack', jsonb_build_object('name', t.name, 'account_type', t.account_type),
88
+ 'company', jsonb_build_object('name', a.name, 'account_type', a.account_type))
89
+ from accounts a
90
+ join account_templates t
91
+ on t.country = v_country and t.chart_code = v_chart and t.code = a.code
92
+ where a.company_id = p_company_id
93
+ and (label_for(t.name, t.name_i18n, preferred_languages(p_company_id)) is distinct from a.name
94
+ or t.account_type is distinct from a.account_type);
95
+
96
+ return query
97
+ select 'account', a.code, 'company_only', 'review'::pack_change_rule,
98
+ jsonb_build_object('company', jsonb_build_object('name', a.name, 'account_type', a.account_type))
99
+ from accounts a
100
+ where a.company_id = p_company_id
101
+ and not exists (select 1 from account_templates t
102
+ where t.country = v_country and t.chart_code = v_chart and t.code = a.code);
103
+
104
+ -- Journals -----------------------------------------------------------------
105
+ return query
106
+ select 'journal', t.code, 'missing', 'addition'::pack_change_rule,
107
+ jsonb_build_object('pack', jsonb_build_object('name', t.name, 'journal_type', t.journal_type))
108
+ from journal_templates t
109
+ where t.country = v_country
110
+ and not exists (select 1 from journals j where j.company_id = p_company_id and j.code = t.code);
111
+
112
+ return query
113
+ select 'journal', j.code, 'differs', 'review'::pack_change_rule,
114
+ jsonb_build_object(
115
+ 'pack', jsonb_build_object('name', t.name, 'journal_type', t.journal_type),
116
+ 'company', jsonb_build_object('name', j.name, 'journal_type', j.journal_type))
117
+ from journals j
118
+ join journal_templates t on t.country = v_country and t.code = j.code
119
+ where j.company_id = p_company_id
120
+ and (t.name is distinct from j.name or t.journal_type is distinct from j.journal_type);
121
+
122
+ -- Taxes --------------------------------------------------------------------
123
+ return query
124
+ select 'tax', t.code, 'missing', 'addition'::pack_change_rule,
125
+ jsonb_build_object('pack', jsonb_build_object(
126
+ 'name', t.name, 'amount', t.amount, 'amount_type', t.amount_type,
127
+ 'valid_from', t.valid_from, 'valid_to', t.valid_to))
128
+ from tax_templates t
129
+ where t.country = v_country
130
+ and not exists (select 1 from taxes x where x.company_id = p_company_id and x.code = t.code);
131
+
132
+ -- A validity the pack has closed and the company still holds open, or holds
133
+ -- open longer. This is a rate change arriving: the old tax stops on the day
134
+ -- the pack says, and the new rate is an addition above.
135
+ return query
136
+ select 'tax', x.code, 'valid_to', 'closure'::pack_change_rule,
137
+ jsonb_build_object('pack', jsonb_build_object('valid_to', t.valid_to),
138
+ 'company', jsonb_build_object('valid_to', x.valid_to))
139
+ from taxes x
140
+ join tax_templates t on t.country = v_country and t.code = x.code
141
+ where x.company_id = p_company_id
142
+ and t.valid_to is not null
143
+ and (x.valid_to is null or x.valid_to > t.valid_to);
144
+
145
+ return query
146
+ select 'tax', x.code, 'differs', 'review'::pack_change_rule,
147
+ jsonb_build_object(
148
+ 'pack', jsonb_build_object('name', t.name, 'amount', t.amount,
149
+ 'amount_type', t.amount_type, 'treatment', t.treatment,
150
+ 'applies_to', t.applies_to, 'tax_kind', t.tax_kind,
151
+ 'recoverable', t.recoverable, 'valid_from', t.valid_from),
152
+ 'company', jsonb_build_object('name', x.name, 'amount', x.amount,
153
+ 'amount_type', x.amount_type, 'treatment', x.treatment,
154
+ 'applies_to', x.applies_to, 'tax_kind', x.tax_kind,
155
+ 'recoverable', x.recoverable, 'valid_from', x.valid_from))
156
+ from taxes x
157
+ join tax_templates t on t.country = v_country and t.code = x.code
158
+ where x.company_id = p_company_id
159
+ and (label_for(t.name, null, preferred_languages(p_company_id)) is distinct from x.name
160
+ or t.amount is distinct from x.amount
161
+ or t.amount_type is distinct from x.amount_type
162
+ or t.treatment is distinct from x.treatment
163
+ or t.applies_to is distinct from x.applies_to
164
+ or t.tax_kind is distinct from x.tax_kind
165
+ or t.recoverable is distinct from x.recoverable
166
+ or t.valid_from is distinct from x.valid_from);
167
+
168
+ return query
169
+ select 'tax', x.code, 'company_only', 'review'::pack_change_rule,
170
+ jsonb_build_object('company', jsonb_build_object('name', x.name, 'amount', x.amount))
171
+ from taxes x
172
+ where x.company_id = p_company_id
173
+ and x.country = v_country
174
+ and not exists (select 1 from tax_templates t where t.country = v_country and t.code = x.code);
175
+
176
+ -- What a tax books. Compared as a whole per tax rather than line by line: a
177
+ -- posting has no identity of its own, and "this tax now books its
178
+ -- non-deductible share somewhere else" is one fact, not four.
179
+ return query
180
+ with pack as (
181
+ select t.code,
182
+ jsonb_agg(jsonb_build_object('document_kind', tp.document_kind,
183
+ 'posting_type', tp.posting_type,
184
+ 'factor_percent', tp.factor_percent,
185
+ 'account_code', tp.account_code,
186
+ 'declaration_box', tp.declaration_box,
187
+ 'box_factor_percent', tp.box_factor_percent,
188
+ 'report_code', tp.report_code)
189
+ order by tp.document_kind, tp.posting_type, tp.sequence) as postings
190
+ from tax_templates t
191
+ join tax_posting_templates tp on tp.tax_template_id = t.id
192
+ where t.country = v_country
193
+ group by t.code
194
+ ), held as (
195
+ select x.code,
196
+ jsonb_agg(jsonb_build_object('document_kind', p.document_kind,
197
+ 'posting_type', p.posting_type,
198
+ 'factor_percent', p.factor_percent,
199
+ 'account_code', a.code,
200
+ 'declaration_box', p.declaration_box,
201
+ 'box_factor_percent', p.box_factor_percent,
202
+ 'report_code', p.report_code)
203
+ order by p.document_kind, p.posting_type, p.sequence) as postings
204
+ from taxes x
205
+ join tax_postings p on p.tax_id = x.id
206
+ left join accounts a on a.id = p.account_id
207
+ where x.company_id = p_company_id
208
+ group by x.code
209
+ )
210
+ select 'tax_posting', pack.code, 'differs', 'review'::pack_change_rule,
211
+ jsonb_build_object('pack', pack.postings, 'company', held.postings)
212
+ from pack
213
+ join held on held.code = pack.code
214
+ where pack.postings is distinct from held.postings;
215
+ end;
216
+ $$;
217
+
218
+ comment on function pack_upgrade_diff(uuid, char) is
219
+ 'What separates a company from the country pack this installation now holds, by natural key, each difference carrying the rule that decides what an upgrade does with it.';
220
+
221
+ -- ---------------------------------------------------------------------------
222
+ -- The upgrade
223
+ -- ---------------------------------------------------------------------------
224
+
225
+ create or replace function pack_upgrade(
226
+ p_company_id uuid,
227
+ p_country char(2) default null,
228
+ p_apply boolean default false
229
+ )
230
+ returns jsonb
231
+ language plpgsql
232
+ as $$
233
+ declare
234
+ v_country char(2);
235
+ v_chart text;
236
+ v_from text;
237
+ v_to text;
238
+ v_applied jsonb := '[]'::jsonb;
239
+ v_listed jsonb := '[]'::jsonb;
240
+ v_refused jsonb := '[]'::jsonb;
241
+ d record;
242
+ v_tax_id uuid;
243
+ v_template tax_templates%rowtype;
244
+ begin
245
+ select coalesce(p_country, c.country) into v_country
246
+ from companies c where c.id = p_company_id;
247
+ if v_country is null then
248
+ raise exception 'unknown_company: %', p_company_id;
249
+ end if;
250
+
251
+ if auth.uid() is not null and not has_capability(p_company_id, 'company.write') then
252
+ raise exception 'not_allowed: upgrading a country pack needs company.write';
253
+ end if;
254
+
255
+ select cp.version, cp.chart_code into v_from, v_chart
256
+ from company_packs cp
257
+ where cp.company_id = p_company_id and cp.country = v_country;
258
+ if v_chart is null then
259
+ raise exception 'no_pack_installed: company % holds no % pack, so there is nothing to upgrade from', p_company_id, v_country;
260
+ end if;
261
+
262
+ select version into v_to from country_packs where country = v_country;
263
+ if v_to is null then
264
+ raise exception 'no_pack_loaded: this installation holds no % pack. Apply the seeds of this release first.', v_country;
265
+ end if;
266
+
267
+ for d in select * from pack_upgrade_diff(p_company_id, v_country) loop
268
+ -- Never, whatever the caller asked. An upgrade adds and closes; it does
269
+ -- not take a row out of a company's books.
270
+ if d.change = 'company_only' then
271
+ v_refused := v_refused || jsonb_build_object('object', d.object, 'key', d.key,
272
+ 'change', d.change, 'rule', d.rule,
273
+ 'detail', d.detail);
274
+ continue;
275
+ end if;
276
+
277
+ if d.rule = 'review' and not p_apply then
278
+ v_listed := v_listed || jsonb_build_object('object', d.object, 'key', d.key,
279
+ 'change', d.change, 'rule', d.rule,
280
+ 'detail', d.detail);
281
+ continue;
282
+ end if;
283
+
284
+ if d.object = 'account' and d.change = 'missing' then
285
+ insert into accounts (company_id, code, name, name_i18n, statement_hint, account_type, reconcilable)
286
+ select p_company_id, t.code, label_for(t.name, t.name_i18n, preferred_languages(p_company_id)),
287
+ t.name_i18n, t.statement_hint, t.account_type, t.reconcilable
288
+ from account_templates t
289
+ where t.country = v_country and t.chart_code = v_chart and t.code = d.key
290
+ on conflict (company_id, code) do nothing;
291
+
292
+ elsif d.object = 'account' and d.change = 'differs' then
293
+ update accounts a
294
+ set name = label_for(t.name, t.name_i18n, preferred_languages(p_company_id)),
295
+ name_i18n = t.name_i18n,
296
+ account_type = t.account_type
297
+ from account_templates t
298
+ where t.country = v_country and t.chart_code = v_chart and t.code = a.code
299
+ and a.company_id = p_company_id and a.code = d.key;
300
+
301
+ elsif d.object = 'journal' and d.change = 'missing' then
302
+ insert into journals (company_id, code, name, journal_type)
303
+ select p_company_id, t.code, t.name, t.journal_type
304
+ from journal_templates t
305
+ where t.country = v_country and t.code = d.key
306
+ on conflict (company_id, code) do nothing;
307
+
308
+ elsif d.object = 'journal' and d.change = 'differs' then
309
+ update journals j
310
+ set name = t.name, journal_type = t.journal_type
311
+ from journal_templates t
312
+ where t.country = v_country and t.code = j.code
313
+ and j.company_id = p_company_id and j.code = d.key;
314
+
315
+ elsif d.object = 'tax' and d.change = 'missing' then
316
+ select * into v_template from tax_templates where country = v_country and code = d.key;
317
+ insert into taxes (company_id, code, name, description, amount_type, amount,
318
+ applies_to, treatment, country, valid_from, valid_to,
319
+ legal_reference, vat_category, exemption_code, sequence,
320
+ tax_kind, recoverable, jurisdiction, price_include,
321
+ cash_basis, cash_basis_transition_account_id)
322
+ values (p_company_id, v_template.code, v_template.name, v_template.description,
323
+ v_template.amount_type, v_template.amount, v_template.applies_to,
324
+ v_template.treatment, v_country, v_template.valid_from, v_template.valid_to,
325
+ v_template.legal_reference, v_template.vat_category, v_template.exemption_code,
326
+ v_template.sequence, v_template.tax_kind, v_template.recoverable,
327
+ v_template.jurisdiction, v_template.price_include, v_template.cash_basis,
328
+ account_id_by_code(p_company_id, v_template.cash_basis_transition_account_code))
329
+ on conflict (company_id, code) do nothing
330
+ returning id into v_tax_id;
331
+
332
+ if v_tax_id is not null then
333
+ insert into tax_postings (tax_id, company_id, document_kind, posting_type,
334
+ factor_percent, account_id, declaration_box,
335
+ box_factor_percent, report_code, sequence)
336
+ select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
337
+ tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
338
+ tp.declaration_box, tp.box_factor_percent, tp.report_code, tp.sequence
339
+ from tax_posting_templates tp
340
+ where tp.tax_template_id = v_template.id
341
+ order by tp.sequence;
342
+ end if;
343
+
344
+ elsif d.object = 'tax' and d.change = 'valid_to' then
345
+ update taxes x
346
+ set valid_to = t.valid_to
347
+ from tax_templates t
348
+ where t.country = v_country and t.code = x.code
349
+ and x.company_id = p_company_id and x.code = d.key;
350
+
351
+ elsif d.object = 'tax' and d.change = 'differs' then
352
+ update taxes x
353
+ set name = t.name, description = t.description, amount = t.amount,
354
+ amount_type = t.amount_type, applies_to = t.applies_to,
355
+ treatment = t.treatment, valid_from = t.valid_from,
356
+ legal_reference = t.legal_reference, vat_category = t.vat_category,
357
+ exemption_code = t.exemption_code, tax_kind = t.tax_kind,
358
+ recoverable = t.recoverable, jurisdiction = t.jurisdiction,
359
+ price_include = t.price_include, cash_basis = t.cash_basis
360
+ from tax_templates t
361
+ where t.country = v_country and t.code = x.code
362
+ and x.company_id = p_company_id and x.code = d.key;
363
+
364
+ elsif d.object = 'tax_posting' and d.change = 'differs' then
365
+ select id into v_tax_id from taxes where company_id = p_company_id and code = d.key;
366
+ delete from tax_postings where tax_id = v_tax_id;
367
+ insert into tax_postings (tax_id, company_id, document_kind, posting_type,
368
+ factor_percent, account_id, declaration_box,
369
+ box_factor_percent, report_code, sequence)
370
+ select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
371
+ tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
372
+ tp.declaration_box, tp.box_factor_percent, tp.report_code, tp.sequence
373
+ from tax_posting_templates tp
374
+ join tax_templates t on t.id = tp.tax_template_id
375
+ where t.country = v_country and t.code = d.key
376
+ order by tp.sequence;
377
+
378
+ else
379
+ -- A difference this function does not know how to apply is listed
380
+ -- rather than guessed at. Raising here would make a new kind of
381
+ -- difference break every upgrade.
382
+ v_listed := v_listed || jsonb_build_object('object', d.object, 'key', d.key,
383
+ 'change', d.change, 'rule', d.rule,
384
+ 'detail', d.detail);
385
+ continue;
386
+ end if;
387
+
388
+ v_applied := v_applied || jsonb_build_object('object', d.object, 'key', d.key,
389
+ 'change', d.change, 'rule', d.rule,
390
+ 'detail', d.detail);
391
+ end loop;
392
+
393
+ -- The version moves only when nothing is left waiting. A company that still
394
+ -- holds a difference it has not decided on has not finished upgrading, and
395
+ -- saying otherwise would hide the difference at the next run.
396
+ if jsonb_array_length(v_listed) = 0 then
397
+ update company_packs
398
+ set version = v_to, upgraded_at = now()
399
+ where company_id = p_company_id and country = v_country;
400
+ end if;
401
+
402
+ perform audit_record(
403
+ p_company_id, 'company_packs', null, v_country, 'update', 'pack_upgraded',
404
+ jsonb_build_object('version', v_from),
405
+ jsonb_build_object('version', case when jsonb_array_length(v_listed) = 0 then v_to else v_from end,
406
+ 'pack_version', v_to,
407
+ 'chart_code', v_chart,
408
+ 'applied', v_applied,
409
+ 'listed', v_listed,
410
+ 'never_applied', v_refused));
411
+
412
+ return jsonb_build_object(
413
+ 'company_id', p_company_id,
414
+ 'country', v_country,
415
+ 'chart_code', v_chart,
416
+ 'from_version', v_from,
417
+ 'to_version', v_to,
418
+ 'version_moved', jsonb_array_length(v_listed) = 0,
419
+ 'applied', v_applied,
420
+ 'listed', v_listed,
421
+ 'never_applied', v_refused);
422
+ end;
423
+ $$;
424
+
425
+ comment on function pack_upgrade(uuid, char, boolean) is
426
+ 'Moves a company to the country pack version this installation holds: additions copied in, closed validities applied, everything else listed and left alone unless the caller asks for it. Records what it did in the audit trail. The recorded version moves only when nothing is left waiting.';
427
+
428
+ revoke execute on all functions in schema public from public;
@@ -0,0 +1,206 @@
1
+ -- Ekwo OS — an amount is rounded at the decimals of its currency, by the
2
+ -- method of its country.
3
+ --
4
+ -- Two columns had been filled by every pack and read by nothing:
5
+ -- `currencies.decimal_places`, which says the yen has none and the dinar has
6
+ -- three, and `country_defaults.rounding_method`, which says how a country
7
+ -- turns a half into a figure. Everything in the schema rounded with
8
+ -- `round(x, 2)` instead — fifty-one times, in eighteen functions, a view and
9
+ -- a generated column. Two decimals is right for every currency the packs
10
+ -- carry and wrong for about a quarter of the world's; and "half up" was not
11
+ -- a decision anywhere, it was what `round()` happens to do.
12
+ --
13
+ -- What a hard-coded two costs is not theoretical. A yen invoice of 1 234,5
14
+ -- would be booked at 1 234,50 and settled at 1 235, and the cent that does
15
+ -- not exist in that currency would sit on a suspense account for ever. The
16
+ -- rule is the same one the three copies of `rounding.ts` already carry: half
17
+ -- up on the absolute value, at the currency's decimals, so that a credit note
18
+ -- is its invoice with the sign flipped.
19
+ --
20
+ -- This migration adds the vocabulary; the next one makes the schema speak it.
21
+ --
22
+ -- * `money_rounding` is the pair that answers "how is this amount
23
+ -- written": the decimals of a currency and the method of a country.
24
+ -- They travel together because neither is an answer on its own.
25
+ -- * `round_amount(amount, rounding)` is the arithmetic, and the only place
26
+ -- in the whole schema where a rounding method is named. It looks nothing
27
+ -- up, which is what lets it be immutable and used in a view.
28
+ -- * `rounding_of(company, currency)` is the lookup, and the only place
29
+ -- where the two columns are read. A currency it does not know, a company
30
+ -- it does not know, a country with no model: each is refused by name.
31
+ -- None of the three is guessed, and there is no fallback currency and no
32
+ -- fallback country anywhere in it.
33
+ -- * `currency_unit(rounding)` is the smallest amount the currency has — a
34
+ -- cent in the euro, a yen in the yen. The tolerances that used to be
35
+ -- written `0.005` are half a unit, which is the thing they always meant.
36
+ -- * `amount_text_format(rounding)` is the `to_char` mask that goes with it,
37
+ -- for the two places an amount is put into a sentence.
38
+
39
+ -- ---------------------------------------------------------------------------
40
+ -- The pair
41
+ -- ---------------------------------------------------------------------------
42
+
43
+ create type money_rounding as (
44
+ decimals smallint,
45
+ method rounding_method
46
+ );
47
+
48
+ comment on type money_rounding is
49
+ 'How an amount is written: the decimals of its currency and the rounding method of its country. Resolved by rounding_of(), applied by round_amount().';
50
+
51
+ -- ---------------------------------------------------------------------------
52
+ -- The arithmetic
53
+ -- ---------------------------------------------------------------------------
54
+
55
+ create or replace function round_amount(p_amount numeric, p_rounding money_rounding)
56
+ returns numeric
57
+ language plpgsql
58
+ immutable
59
+ as $$
60
+ declare
61
+ v_decimals smallint;
62
+ v_unit numeric;
63
+ v_value numeric;
64
+ v_floor numeric;
65
+ v_rest numeric;
66
+ v_result numeric;
67
+ begin
68
+ if p_amount is null then
69
+ return null;
70
+ end if;
71
+
72
+ v_decimals := (p_rounding).decimals;
73
+ if v_decimals is null then
74
+ raise exception 'no_rounding_scale: an amount cannot be rounded without the decimals of its currency';
75
+ end if;
76
+ if (p_rounding).method is null then
77
+ raise exception 'no_rounding_method: an amount cannot be rounded without the method of its country';
78
+ end if;
79
+
80
+ -- Everything happens on the absolute value and the sign is put back at the
81
+ -- end, so that the rounding of -x is the rounding of x signed back. A rule
82
+ -- that is not symmetric puts a unit between an invoice and its credit note.
83
+ v_unit := power(10::numeric, (-v_decimals)::numeric);
84
+ v_value := abs(p_amount);
85
+ v_floor := trunc(v_value, v_decimals);
86
+ v_rest := v_value - v_floor;
87
+
88
+ case (p_rounding).method
89
+ when 'half_up' then
90
+ -- Half away from zero, which on an absolute value is half up.
91
+ v_result := round(v_value, v_decimals);
92
+ when 'down' then
93
+ v_result := v_floor;
94
+ when 'up' then
95
+ v_result := case when v_rest = 0 then v_floor else v_floor + v_unit end;
96
+ when 'half_even' then
97
+ if v_rest * 2 > v_unit then
98
+ v_result := v_floor + v_unit;
99
+ elsif v_rest * 2 < v_unit then
100
+ v_result := v_floor;
101
+ else
102
+ -- Exactly half: towards the even neighbour, which is what "banker's
103
+ -- rounding" means and what a country that declares it is asking for.
104
+ v_result := case when (v_floor / v_unit) % 2 = 0 then v_floor else v_floor + v_unit end;
105
+ end if;
106
+ end case;
107
+
108
+ -- The scale of the answer is the currency's, whatever the arithmetic left
109
+ -- behind: an amount in yen is written with no decimals at all.
110
+ v_result := round(v_result, v_decimals);
111
+ return case when p_amount < 0 then -v_result else v_result end;
112
+ end;
113
+ $$;
114
+
115
+ comment on function round_amount(numeric, money_rounding) is
116
+ 'Rounds an amount at the decimals of its currency, by the method of its country. The only function of the schema that names a rounding method; every other one asks rounding_of() and passes the answer here.';
117
+
118
+ -- The smallest amount this currency has. A cent in the euro, a yen in the
119
+ -- yen, a millime in the dinar.
120
+ create or replace function currency_unit(p_rounding money_rounding)
121
+ returns numeric
122
+ language sql
123
+ immutable
124
+ as $$
125
+ select round(power(10::numeric, (-(p_rounding).decimals)::numeric), (p_rounding).decimals);
126
+ $$;
127
+
128
+ comment on function currency_unit(money_rounding) is
129
+ 'The smallest amount a currency has: a cent in the euro, a yen in the yen. A tolerance is written as a fraction of this rather than as a fraction of a cent.';
130
+
131
+ -- The mask `to_char` needs to write an amount of this currency. A yen result
132
+ -- reported as "1234.00" claims a precision the currency does not have.
133
+ create or replace function amount_text_format(p_rounding money_rounding)
134
+ returns text
135
+ language sql
136
+ immutable
137
+ as $$
138
+ select 'FM9999999999999990'
139
+ || case when (p_rounding).decimals > 0
140
+ then '.' || repeat('0', (p_rounding).decimals)
141
+ else '' end;
142
+ $$;
143
+
144
+ comment on function amount_text_format(money_rounding) is
145
+ 'The to_char mask an amount of this currency is written with. Two decimals for the euro, none for the yen, three for the dinar.';
146
+
147
+ -- ---------------------------------------------------------------------------
148
+ -- The lookup
149
+ -- ---------------------------------------------------------------------------
150
+
151
+ create or replace function rounding_of(p_company_id uuid, p_currency_code text default null)
152
+ returns money_rounding
153
+ language plpgsql
154
+ stable
155
+ as $$
156
+ declare
157
+ v_currency char(3);
158
+ v_country char(2);
159
+ v_out money_rounding;
160
+ begin
161
+ select coalesce(p_currency_code, c.currency_code), coalesce(c.fiscal_country, c.country)
162
+ into v_currency, v_country
163
+ from companies c
164
+ where c.id = p_company_id;
165
+
166
+ if not found then
167
+ raise exception 'unknown_company: % does not exist, and an amount is rounded by the country and the currency of a company', p_company_id;
168
+ end if;
169
+
170
+ -- The decimals are the currency's, from the instance's own reference data.
171
+ select cu.decimal_places into v_out.decimals
172
+ from currencies cu where cu.code = v_currency;
173
+ if v_out.decimals is null then
174
+ raise exception 'unknown_currency: % is not a currency of this installation, so the decimals an amount is written with are unknown', v_currency;
175
+ end if;
176
+
177
+ -- The method is the country's, from the pack. A pack that says nothing
178
+ -- about rounding got the column's own default when it was compiled, which
179
+ -- is a decision taken once in the schema and not a country borrowed here.
180
+ select cd.rounding_method into v_out.method
181
+ from country_defaults cd where cd.country = v_country;
182
+ if v_out.method is null then
183
+ raise exception 'no_country_model: this installation carries no country model for %, so the method its amounts are rounded by is unknown', v_country;
184
+ end if;
185
+
186
+ return v_out;
187
+ end;
188
+ $$;
189
+
190
+ comment on function rounding_of(uuid, text) is
191
+ 'How this company writes an amount in this currency, or in its own when none is named. The only place currencies.decimal_places and country_defaults.rounding_method are read.';
192
+
193
+ -- These three are read by every posting and every report, so they are open to
194
+ -- a signed-in member and to nobody else. `anon` reads the public surface of
195
+ -- an installation and books nothing.
196
+ revoke execute on function round_amount(numeric, money_rounding) from public, anon;
197
+ revoke execute on function currency_unit(money_rounding) from public, anon;
198
+ revoke execute on function amount_text_format(money_rounding) from public, anon;
199
+ revoke execute on function rounding_of(uuid, text) from public, anon;
200
+
201
+ grant execute on function round_amount(numeric, money_rounding) to authenticated, service_role;
202
+ grant execute on function currency_unit(money_rounding) to authenticated, service_role;
203
+ grant execute on function amount_text_format(money_rounding) to authenticated, service_role;
204
+ grant execute on function rounding_of(uuid, text) to authenticated, service_role;
205
+
206
+ revoke execute on all functions in schema public from public;