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,143 @@
1
+ -- Ekwo OS — a country template that wires the financial journals too.
2
+ --
3
+ -- `country_defaults.bank_account_code` was declared from the first release and
4
+ -- read by nothing: `install_country_template` wired the receivable, payable,
5
+ -- suspense, rounding and retained-earnings accounts and the three journals of
6
+ -- the company, and left `journals.default_account_id` null on every one of
7
+ -- them. A country model whose financial journal points at no account is not a
8
+ -- model — the demo seed had to wire it by hand, which was the symptom.
9
+ --
10
+ -- It surfaced on the first payment booked through a client: `post_payment()`
11
+ -- looks for the bank side on the payment's bank account, then on the default
12
+ -- account of its journal, and found neither on a freshly installed company.
13
+ -- The refusal, `no_bank_account`, was correct and the configuration was what
14
+ -- was wrong.
15
+ --
16
+ -- So the bank journal and the cash journal get their account from the country
17
+ -- model, like everything else. `cash_account_code` is added because the cash
18
+ -- journal needs its own — `570000` in the PCMN, `530000` in the PCG — and
19
+ -- these two codes do not derive from one another any more than `550000` and
20
+ -- `512000` do.
21
+ --
22
+ -- Only new columns and a `create or replace`: nothing already published is
23
+ -- edited, and a company that already has a default account keeps it.
24
+
25
+ alter table country_defaults add column if not exists cash_account_code text;
26
+
27
+ comment on column country_defaults.cash_account_code is
28
+ 'Ledger account behind the cash journal of this country, from the pack of that country.';
29
+
30
+ -- No backfill here. The value of this column is pack data, and the compiled
31
+ -- seed of every country upserts `country_defaults` — so re-applying the seeds
32
+ -- fills it, on a fresh install and on an existing one alike. A migration that
33
+ -- wrote it would have to name a country and a code, and a country is data.
34
+
35
+ create or replace function install_country_template(
36
+ p_company_id uuid,
37
+ p_country char(2)
38
+ )
39
+ returns void
40
+ language plpgsql
41
+ as $$
42
+ declare
43
+ v_defaults country_defaults%rowtype;
44
+ r record;
45
+ v_tax_id uuid;
46
+ begin
47
+ if not exists (select 1 from companies where id = p_company_id) then
48
+ raise exception 'unknown_company: %', p_company_id;
49
+ end if;
50
+ if not exists (select 1 from account_templates where country = p_country) then
51
+ raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
52
+ end if;
53
+
54
+ -- 1. Accounts, without the hierarchy.
55
+ insert into accounts (company_id, code, name, account_type, reconcilable)
56
+ select p_company_id, t.code, t.name, t.account_type, t.reconcilable
57
+ from account_templates t
58
+ where t.country = p_country
59
+ on conflict (company_id, code) do nothing;
60
+
61
+ -- 2. Hierarchy, now that every code exists.
62
+ update accounts a
63
+ set parent_id = p.id
64
+ from account_templates t
65
+ join accounts p on p.company_id = p_company_id and p.code = t.parent_code
66
+ where a.company_id = p_company_id
67
+ and a.code = t.code
68
+ and t.country = p_country
69
+ and t.parent_code is not null
70
+ and a.parent_id is null;
71
+
72
+ -- 3. Journals.
73
+ insert into journals (company_id, code, name, journal_type)
74
+ select p_company_id, t.code, t.name, t.journal_type
75
+ from journal_templates t
76
+ where t.country = p_country
77
+ on conflict (company_id, code) do nothing;
78
+
79
+ -- 4. Taxes and their postings.
80
+ for r in
81
+ select * from tax_templates where country = p_country order by sequence, code
82
+ loop
83
+ v_tax_id := null;
84
+ insert into taxes (company_id, code, name, description, amount_type, amount,
85
+ applies_to, treatment, country, valid_from, valid_to,
86
+ legal_reference, vat_category, exemption_code, sequence)
87
+ values (p_company_id, r.code, r.name, r.description, r.amount_type, r.amount,
88
+ r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
89
+ r.legal_reference, r.vat_category, r.exemption_code, r.sequence)
90
+ on conflict (company_id, code) do nothing
91
+ returning id into v_tax_id;
92
+
93
+ if v_tax_id is null then
94
+ continue;
95
+ end if;
96
+
97
+ insert into tax_postings (tax_id, company_id, document_kind, posting_type,
98
+ factor_percent, account_id, declaration_box,
99
+ box_factor_percent, sequence)
100
+ select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
101
+ tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
102
+ tp.declaration_box, tp.box_factor_percent, tp.sequence
103
+ from tax_posting_templates tp
104
+ where tp.tax_template_id = r.id
105
+ order by tp.sequence;
106
+ end loop;
107
+
108
+ -- 5. Roles.
109
+ select * into v_defaults from country_defaults where country = p_country;
110
+ if found then
111
+ update companies c
112
+ set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
113
+ payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
114
+ suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
115
+ rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
116
+ retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
117
+ sales_journal_id = coalesce(c.sales_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.sales_journal_code)),
118
+ purchase_journal_id = coalesce(c.purchase_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.purchase_journal_code)),
119
+ miscellaneous_journal_id = coalesce(c.miscellaneous_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.misc_journal_code))
120
+ where c.id = p_company_id;
121
+
122
+ -- 6. The financial journals point at their account, so money booked
123
+ -- through them lands somewhere without the operator wiring it first.
124
+ -- `default_account_id is null` keeps a company that already chose one.
125
+ update journals j
126
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
127
+ where j.company_id = p_company_id
128
+ and j.journal_type = 'bank'
129
+ and j.default_account_id is null
130
+ and v_defaults.bank_account_code is not null;
131
+
132
+ update journals j
133
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
134
+ where j.company_id = p_company_id
135
+ and j.journal_type = 'cash'
136
+ and j.default_account_id is null
137
+ and v_defaults.cash_account_code is not null;
138
+ end if;
139
+ end;
140
+ $$;
141
+
142
+ comment on function install_country_template(uuid, char) is
143
+ 'Copies a country chart of accounts, journals and taxes into a company, wires the default roles and points the financial journals at their account.';
@@ -0,0 +1,274 @@
1
+ -- Ekwo OS — the account a document line falls back to.
2
+ --
3
+ -- `country_defaults` shipped with three columns nothing ever read:
4
+ -- `sales_account_code`, `purchase_account_code` and `currency_code`. Keeping
5
+ -- a column "in case" is the state the naming policy forbids, so the choice
6
+ -- was between deleting them and giving them a reader. They get a reader,
7
+ -- because the thing they describe is real: every chart of accounts has one
8
+ -- account a sale lands on by default and one a purchase lands on, and
9
+ -- somebody has to suggest it. `currency_code` gets its reader in the CLI —
10
+ -- `ekwo init` offers it as the currency of the company, before the insert,
11
+ -- because `companies.currency_code` is `not null default 'EUR'` and is
12
+ -- therefore never empty by the time `install_country_template` runs.
13
+ --
14
+ -- The order of resolution for the account of a document line, from the most
15
+ -- specific to the least:
16
+ --
17
+ -- 1. the line itself — `document_lines.account_id`;
18
+ -- 2. the product, once `products` exists (added by a later migration, which
19
+ -- replaces the trigger body to consult it);
20
+ -- 3. the company default — `companies.default_sales_account_id` /
21
+ -- `default_purchase_account_id`, new here;
22
+ -- 4. the country model — `country_defaults.sales_account_code` /
23
+ -- `purchase_account_code`, resolved against the company's own chart.
24
+ --
25
+ -- Nothing is guessed from a code prefix, which is the rule the rest of the
26
+ -- schema already keeps: `411` is *customers* on the French chart and
27
+ -- *recoverable VAT* on the Belgian one.
28
+ --
29
+ -- The resolution runs in a trigger rather than in each client. Lines are
30
+ -- created by whoever holds a token — the MCP server, PostgREST, psql — and
31
+ -- `document_lines_product_has_account` refuses a product line with no
32
+ -- account, so a null account can only ever mean "resolve it". A null *tax*
33
+ -- is a different matter: it means no tax at all, which is a real answer, so
34
+ -- nothing here fills it in.
35
+
36
+ alter table companies
37
+ add column if not exists default_sales_account_id uuid,
38
+ add column if not exists default_purchase_account_id uuid;
39
+
40
+ alter table companies
41
+ add constraint companies_default_sales_account_fkey
42
+ foreign key (default_sales_account_id) references accounts(id) on delete set null,
43
+ add constraint companies_default_purchase_account_fkey
44
+ foreign key (default_purchase_account_id) references accounts(id) on delete set null;
45
+
46
+ comment on column companies.default_sales_account_id is
47
+ 'Income account a sales line falls back to when it names none. Wired from country_defaults.sales_account_code at install.';
48
+ comment on column companies.default_purchase_account_id is
49
+ 'Expense account a purchase line falls back to when it names none. Wired from country_defaults.purchase_account_code at install.';
50
+
51
+ -- ---------------------------------------------------------------------------
52
+ -- resolve_line_account
53
+ --
54
+ -- `stable` and not `security definer`: it reads `companies` and
55
+ -- `country_defaults`, both of which the caller may read anyway — a member of
56
+ -- the company for the first, anyone signed in for the second. A definer here
57
+ -- would widen nothing and hide the policy that already decides.
58
+ -- ---------------------------------------------------------------------------
59
+
60
+ create or replace function resolve_line_account(
61
+ p_company_id uuid,
62
+ p_doc_type doc_type,
63
+ p_account_id uuid
64
+ )
65
+ returns uuid
66
+ language plpgsql
67
+ stable
68
+ as $$
69
+ declare
70
+ v_is_sale boolean;
71
+ v_account uuid;
72
+ v_country char(2);
73
+ v_code text;
74
+ begin
75
+ if p_account_id is not null then
76
+ return p_account_id;
77
+ end if;
78
+
79
+ v_is_sale := p_doc_type in ('sale_invoice', 'sale_credit_note', 'sale_quote');
80
+
81
+ select case when v_is_sale then c.default_sales_account_id
82
+ else c.default_purchase_account_id end,
83
+ c.country
84
+ into v_account, v_country
85
+ from companies c
86
+ where c.id = p_company_id;
87
+
88
+ if v_account is not null then
89
+ return v_account;
90
+ end if;
91
+
92
+ select case when v_is_sale then d.sales_account_code else d.purchase_account_code end
93
+ into v_code
94
+ from country_defaults d
95
+ where d.country = v_country;
96
+
97
+ if v_code is null then
98
+ return null;
99
+ end if;
100
+
101
+ return account_id_by_code(p_company_id, v_code);
102
+ end;
103
+ $$;
104
+
105
+ comment on function resolve_line_account(uuid, doc_type, uuid) is
106
+ 'Account of a document line: the line, then the company default, then the country model. Never a code prefix.';
107
+
108
+ -- ---------------------------------------------------------------------------
109
+ -- The trigger
110
+ --
111
+ -- One path for every client. A line that names its account keeps it; a line
112
+ -- that names none gets the default, and when there is no default anywhere the
113
+ -- existing check constraint raises, which is the honest outcome.
114
+ -- ---------------------------------------------------------------------------
115
+
116
+ create or replace function document_lines_resolve_account()
117
+ returns trigger
118
+ language plpgsql
119
+ as $$
120
+ declare
121
+ v_doc_type doc_type;
122
+ begin
123
+ if new.line_type <> 'product' or new.account_id is not null then
124
+ return new;
125
+ end if;
126
+
127
+ select d.doc_type into v_doc_type from documents d where d.id = new.document_id;
128
+ if v_doc_type is null then
129
+ return new;
130
+ end if;
131
+
132
+ new.account_id := resolve_line_account(new.company_id, v_doc_type, null);
133
+ return new;
134
+ end;
135
+ $$;
136
+
137
+ create trigger document_lines_resolve_account
138
+ before insert or update on document_lines
139
+ for each row execute function document_lines_resolve_account();
140
+
141
+ -- ---------------------------------------------------------------------------
142
+ -- The country model now wires the two company defaults as well.
143
+ --
144
+ -- Only step 5 changes; the rest of the function is the one migration
145
+ -- `…183000` published, repeated because Postgres replaces a function whole.
146
+ -- A company that already chose an account keeps it, as everywhere else here.
147
+ -- ---------------------------------------------------------------------------
148
+
149
+ create or replace function install_country_template(
150
+ p_company_id uuid,
151
+ p_country char(2)
152
+ )
153
+ returns void
154
+ language plpgsql
155
+ as $$
156
+ declare
157
+ v_defaults country_defaults%rowtype;
158
+ r record;
159
+ v_tax_id uuid;
160
+ begin
161
+ if not exists (select 1 from companies where id = p_company_id) then
162
+ raise exception 'unknown_company: %', p_company_id;
163
+ end if;
164
+ if not exists (select 1 from account_templates where country = p_country) then
165
+ raise exception 'unknown_country_template: no chart of accounts seeded for %', p_country;
166
+ end if;
167
+
168
+ -- 1. Accounts, without the hierarchy.
169
+ insert into accounts (company_id, code, name, account_type, reconcilable)
170
+ select p_company_id, t.code, t.name, t.account_type, t.reconcilable
171
+ from account_templates t
172
+ where t.country = p_country
173
+ on conflict (company_id, code) do nothing;
174
+
175
+ -- 2. Hierarchy, now that every code exists.
176
+ update accounts a
177
+ set parent_id = p.id
178
+ from account_templates t
179
+ join accounts p on p.company_id = p_company_id and p.code = t.parent_code
180
+ where a.company_id = p_company_id
181
+ and a.code = t.code
182
+ and t.country = p_country
183
+ and t.parent_code is not null
184
+ and a.parent_id is null;
185
+
186
+ -- 3. Journals.
187
+ insert into journals (company_id, code, name, journal_type)
188
+ select p_company_id, t.code, t.name, t.journal_type
189
+ from journal_templates t
190
+ where t.country = p_country
191
+ on conflict (company_id, code) do nothing;
192
+
193
+ -- 4. Taxes and their postings.
194
+ for r in
195
+ select * from tax_templates where country = p_country order by sequence, code
196
+ loop
197
+ v_tax_id := null;
198
+ insert into taxes (company_id, code, name, description, amount_type, amount,
199
+ applies_to, treatment, country, valid_from, valid_to,
200
+ legal_reference, vat_category, exemption_code, sequence)
201
+ values (p_company_id, r.code, r.name, r.description, r.amount_type, r.amount,
202
+ r.applies_to, r.treatment, p_country, r.valid_from, r.valid_to,
203
+ r.legal_reference, r.vat_category, r.exemption_code, r.sequence)
204
+ on conflict (company_id, code) do nothing
205
+ returning id into v_tax_id;
206
+
207
+ if v_tax_id is null then
208
+ continue;
209
+ end if;
210
+
211
+ insert into tax_postings (tax_id, company_id, document_kind, posting_type,
212
+ factor_percent, account_id, declaration_box,
213
+ box_factor_percent, sequence)
214
+ select v_tax_id, p_company_id, tp.document_kind, tp.posting_type,
215
+ tp.factor_percent, account_id_by_code(p_company_id, tp.account_code),
216
+ tp.declaration_box, tp.box_factor_percent, tp.sequence
217
+ from tax_posting_templates tp
218
+ where tp.tax_template_id = r.id
219
+ order by tp.sequence;
220
+ end loop;
221
+
222
+ -- 5. Roles.
223
+ select * into v_defaults from country_defaults where country = p_country;
224
+ if found then
225
+ update companies c
226
+ set receivable_account_id = coalesce(c.receivable_account_id, account_id_by_code(p_company_id, v_defaults.receivable_code)),
227
+ payable_account_id = coalesce(c.payable_account_id, account_id_by_code(p_company_id, v_defaults.payable_code)),
228
+ suspense_account_id = coalesce(c.suspense_account_id, account_id_by_code(p_company_id, v_defaults.suspense_code)),
229
+ rounding_account_id = coalesce(c.rounding_account_id, account_id_by_code(p_company_id, v_defaults.rounding_code)),
230
+ retained_earnings_account_id = coalesce(c.retained_earnings_account_id, account_id_by_code(p_company_id, v_defaults.retained_earnings_code)),
231
+ default_sales_account_id = coalesce(c.default_sales_account_id, account_id_by_code(p_company_id, v_defaults.sales_account_code)),
232
+ default_purchase_account_id = coalesce(c.default_purchase_account_id, account_id_by_code(p_company_id, v_defaults.purchase_account_code)),
233
+ sales_journal_id = coalesce(c.sales_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.sales_journal_code)),
234
+ purchase_journal_id = coalesce(c.purchase_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.purchase_journal_code)),
235
+ miscellaneous_journal_id = coalesce(c.miscellaneous_journal_id, (select id from journals where company_id = p_company_id and code = v_defaults.misc_journal_code))
236
+ where c.id = p_company_id;
237
+
238
+ -- 6. The financial journals point at their account, so money booked
239
+ -- through them lands somewhere without the operator wiring it first.
240
+ update journals j
241
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.bank_account_code)
242
+ where j.company_id = p_company_id
243
+ and j.journal_type = 'bank'
244
+ and j.default_account_id is null
245
+ and v_defaults.bank_account_code is not null;
246
+
247
+ update journals j
248
+ set default_account_id = account_id_by_code(p_company_id, v_defaults.cash_account_code)
249
+ where j.company_id = p_company_id
250
+ and j.journal_type = 'cash'
251
+ and j.default_account_id is null
252
+ and v_defaults.cash_account_code is not null;
253
+ end if;
254
+ end;
255
+ $$;
256
+
257
+ comment on function install_country_template(uuid, char) is
258
+ 'Copies a country chart of accounts, journals and taxes into a company, wires the default roles — third parties, sales and purchase imputation, financial journals.';
259
+
260
+ -- Backfill for a company installed before this migration: it already has the
261
+ -- chart, so the two defaults can be filled from its own country model.
262
+ update companies c
263
+ set default_sales_account_id = account_id_by_code(c.id, d.sales_account_code)
264
+ from country_defaults d
265
+ where d.country = c.country
266
+ and c.default_sales_account_id is null
267
+ and d.sales_account_code is not null;
268
+
269
+ update companies c
270
+ set default_purchase_account_id = account_id_by_code(c.id, d.purchase_account_code)
271
+ from country_defaults d
272
+ where d.country = c.country
273
+ and c.default_purchase_account_id is null
274
+ and d.purchase_account_code is not null;
@@ -0,0 +1,221 @@
1
+ -- Ekwo OS — products, in the core rather than in a module beside it.
2
+ --
3
+ -- A product here is what a line is filled in from: a code, a name, a
4
+ -- description, a unit, a price, the account it books to and the tax it
5
+ -- carries. It is deliberately not an article of stock — no quantity on hand,
6
+ -- no valuation, no movements — because that is a different piece of software
7
+ -- and merging the two is how a catalogue ends up owning the ledger.
8
+ --
9
+ -- Two rules it keeps, and they are the whole design:
10
+ --
11
+ -- * **A product pre-fills a line; it never constrains one.** The line keeps
12
+ -- its own text, its own price and its own account, and anything typed
13
+ -- over the product's suggestion stays. An invoice is a statement about
14
+ -- what was sold on that day, and a catalogue edited next month must not
15
+ -- be able to change it.
16
+ -- * **A line with no product is still a line.** `product_id` is nullable
17
+ -- and always will be. Free text is how most invoices are written, and a
18
+ -- schema that forces a catalogue row for every line forces the operator
19
+ -- to invent one.
20
+ --
21
+ -- The unit lives where it already lived: `document_lines.unit_code`, UN/ECE
22
+ -- recommendation 20, present since the first release and mapped to BT-130. A
23
+ -- second `unit` column next to it would be two answers to one question, so
24
+ -- the product carries `unit_code` too and fills that one in.
25
+
26
+ create type product_kind as enum ('service', 'goods');
27
+
28
+ comment on type product_kind is
29
+ 'What is being sold. Services and goods differ in VAT treatment, in the declaration boxes they feed and in the account they book to.';
30
+
31
+ create table products (
32
+ id uuid primary key default gen_random_uuid(),
33
+ company_id uuid not null references companies(id) on delete cascade,
34
+ -- BT-155, the seller's own identifier for the item.
35
+ code text not null,
36
+ -- BT-153 when the line takes it.
37
+ name text not null,
38
+ -- BT-154.
39
+ description text,
40
+ kind product_kind not null default 'service',
41
+ -- UN/ECE recommendation 20, BT-130. `C62` is "one", `HUR` an hour, `DAY` a
42
+ -- day, `KGM` a kilogram, `LTR` a litre, `MTR` a metre. The check is on the
43
+ -- shape only: the recommendation has some eighteen hundred codes and a
44
+ -- short list in the database would refuse legitimate ones.
45
+ unit_code text not null default 'C62',
46
+ currency_code char(3) not null default 'EUR' references currencies(code),
47
+ -- Six decimals, like `document_lines.unit_price`: a unit price is not an
48
+ -- amount of money, it is what an amount is computed from.
49
+ sale_price numeric(16, 6),
50
+ purchase_price numeric(16, 6),
51
+ sale_account_id uuid references accounts(id) on delete restrict,
52
+ purchase_account_id uuid references accounts(id) on delete restrict,
53
+ sale_tax_id uuid references taxes(id) on delete restrict,
54
+ purchase_tax_id uuid references taxes(id) on delete restrict,
55
+ active boolean not null default true,
56
+ created_at timestamptz not null default now(),
57
+ updated_at timestamptz not null default now(),
58
+ unique (company_id, code),
59
+ constraint products_code_not_blank check (length(btrim(code)) > 0),
60
+ constraint products_unit_code_format check (unit_code ~ '^[A-Z0-9]{1,3}$'),
61
+ constraint products_sale_price_positive check (sale_price is null or sale_price >= 0),
62
+ constraint products_purchase_price_positive check (purchase_price is null or purchase_price >= 0),
63
+ foreign key (sale_account_id, company_id) references accounts(id, company_id),
64
+ foreign key (purchase_account_id, company_id) references accounts(id, company_id),
65
+ foreign key (sale_tax_id, company_id) references taxes(id, company_id),
66
+ foreign key (purchase_tax_id, company_id) references taxes(id, company_id)
67
+ );
68
+
69
+ comment on table products is
70
+ 'What a document line is filled in from: code, name, unit, price, account and tax. Not stock: no quantity on hand and no valuation.';
71
+ comment on column products.code is 'The seller''s own item identifier, EN 16931 BT-155. Unique in the company.';
72
+ comment on column products.name is 'Item name, EN 16931 BT-153, copied onto the line it fills in.';
73
+ comment on column products.description is 'Item description, EN 16931 BT-154.';
74
+ comment on column products.unit_code is 'Unit of measure, UN/ECE recommendation 20 (BT-130). C62 is "one".';
75
+ comment on column products.sale_price is 'Suggested net unit price on a sale. A line may carry another.';
76
+
77
+ create unique index products_id_company_idx on products (id, company_id);
78
+ create index products_company_active_idx on products (company_id, active);
79
+ create index products_company_name_idx on products (company_id, lower(name));
80
+
81
+ create trigger products_set_updated_at
82
+ before update on products
83
+ for each row execute function set_updated_at();
84
+
85
+ -- ---------------------------------------------------------------------------
86
+ -- The line points at the product, and keeps everything it was given.
87
+ -- ---------------------------------------------------------------------------
88
+
89
+ alter table document_lines
90
+ add column if not exists product_id uuid,
91
+ add column if not exists description text;
92
+
93
+ alter table document_lines
94
+ add constraint document_lines_product_fkey
95
+ foreign key (product_id) references products(id) on delete restrict,
96
+ add constraint document_lines_product_company_fkey
97
+ foreign key (product_id, company_id) references products(id, company_id);
98
+
99
+ comment on column document_lines.product_id is
100
+ 'The catalogue row this line was filled in from, when there was one. Nullable for ever: free text is how most invoices are written.';
101
+ comment on column document_lines.description is
102
+ 'Item description, EN 16931 BT-154. `name` is BT-153.';
103
+
104
+ create index document_lines_product_idx on document_lines (product_id);
105
+
106
+ -- `on delete restrict` rather than `set null`: a product referenced by a
107
+ -- posted invoice is part of what that invoice said, and deleting it out from
108
+ -- under the line would quietly rewrite a document. Retiring one is `active`.
109
+
110
+ -- ---------------------------------------------------------------------------
111
+ -- Row level security, in the same migration as the table.
112
+ -- ---------------------------------------------------------------------------
113
+
114
+ alter table products enable row level security;
115
+
116
+ create policy products_select on products
117
+ for select using (is_company_member(company_id));
118
+ create policy products_write on products
119
+ for all using (can_write_company(company_id)) with check (can_write_company(company_id));
120
+
121
+ -- ---------------------------------------------------------------------------
122
+ -- The product joins the resolution of the line's account.
123
+ --
124
+ -- Order, most specific first: the line, the product, the company default, the
125
+ -- country model. The three-argument form published in `…193853` is kept and
126
+ -- called by this one, so there is one place where the last two steps live.
127
+ -- ---------------------------------------------------------------------------
128
+
129
+ create or replace function resolve_line_account(
130
+ p_company_id uuid,
131
+ p_doc_type doc_type,
132
+ p_product_id uuid,
133
+ p_account_id uuid
134
+ )
135
+ returns uuid
136
+ language plpgsql
137
+ stable
138
+ as $$
139
+ declare
140
+ v_is_sale boolean;
141
+ v_account uuid;
142
+ begin
143
+ if p_account_id is not null then
144
+ return p_account_id;
145
+ end if;
146
+
147
+ if p_product_id is not null then
148
+ v_is_sale := p_doc_type in ('sale_invoice', 'sale_credit_note', 'sale_quote');
149
+ select case when v_is_sale then p.sale_account_id else p.purchase_account_id end
150
+ into v_account
151
+ from products p
152
+ where p.id = p_product_id;
153
+ if v_account is not null then
154
+ return v_account;
155
+ end if;
156
+ end if;
157
+
158
+ return resolve_line_account(p_company_id, p_doc_type, null::uuid);
159
+ end;
160
+ $$;
161
+
162
+ comment on function resolve_line_account(uuid, doc_type, uuid, uuid) is
163
+ 'Account of a document line: the line, the product, the company default, the country model. Never a code prefix.';
164
+
165
+ create or replace function document_lines_resolve_account()
166
+ returns trigger
167
+ language plpgsql
168
+ as $$
169
+ declare
170
+ v_doc_type doc_type;
171
+ begin
172
+ if new.line_type <> 'product' or new.account_id is not null then
173
+ return new;
174
+ end if;
175
+
176
+ select d.doc_type into v_doc_type from documents d where d.id = new.document_id;
177
+ if v_doc_type is null then
178
+ return new;
179
+ end if;
180
+
181
+ new.account_id := resolve_line_account(new.company_id, v_doc_type, new.product_id, null::uuid);
182
+ return new;
183
+ end;
184
+ $$;
185
+
186
+ -- ---------------------------------------------------------------------------
187
+ -- The invoice line as EN 16931 describes it.
188
+ --
189
+ -- BT-153 is the item name, BT-154 its description, BT-155 the seller's own
190
+ -- identifier — which is `products.code` and lives nowhere else, so a line
191
+ -- that names no product has none, and that is the correct answer rather than
192
+ -- a gap. `security_invoker` so the view is read under the policies of
193
+ -- `document_lines`, exactly like `document_tax_summary`.
194
+ -- ---------------------------------------------------------------------------
195
+
196
+ create or replace view document_line_items
197
+ with (security_invoker = true) as
198
+ select l.id as document_line_id,
199
+ l.document_id,
200
+ l.company_id,
201
+ l.sequence,
202
+ l.line_type,
203
+ l.name as item_name, -- BT-153
204
+ l.description as item_description, -- BT-154
205
+ p.code as seller_item_identifier, -- BT-155
206
+ l.product_id,
207
+ p.kind as product_kind,
208
+ l.quantity, -- BT-129
209
+ l.unit_code, -- BT-130
210
+ l.unit_price, -- BT-146
211
+ l.discount_percent,
212
+ l.amount_untaxed, -- BT-131
213
+ l.tax_id,
214
+ l.vat_category, -- BT-151
215
+ l.vat_rate, -- BT-152
216
+ l.account_id
217
+ from document_lines l
218
+ left join products p on p.id = l.product_id;
219
+
220
+ comment on view document_line_items is
221
+ 'Document lines with the EN 16931 item terms: BT-153 name, BT-154 description, BT-155 the seller identifier, which is the product code.';