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,75 @@
1
+ -- Ekwo OS — two definer functions checked nobody.
2
+ --
3
+ -- Both were found by `tests/client_preset.test.ts`, which calls every volatile
4
+ -- function a signed-in person may execute, as a guest of the company, and
5
+ -- compares the whole database before and after. Two of them returned quietly.
6
+ --
7
+ -- **`catch_up_journal_sequence()`.** It is SECURITY DEFINER because
8
+ -- `journal_sequences` has a select policy and no other, and its header said
9
+ -- "the caller has already been checked by `post_entry()`". That is true of the
10
+ -- call `post_entry()` makes and of no other: the function is executable by
11
+ -- `authenticated`, takes a journal id, and advanced the counter of that
12
+ -- journal to whatever number it was handed — for a member who only reads, and
13
+ -- for somebody who is not a member of the company at all. `greatest()` kept it
14
+ -- from going backwards, so what it allowed was a hole: the next entry of that
15
+ -- journal numbered 9999 after 0042, in a country whose law forbids exactly
16
+ -- that. It is the sentence `next_entry_number()` was given a guard for on 11
17
+ -- September — "a way to burn numbers in somebody else's journal" — left open
18
+ -- on the function beside it.
19
+ --
20
+ -- It now asks what `next_entry_number()` asks, in the same words: the
21
+ -- installer, or `entries.post` on the company of the journal. Whoever posts an
22
+ -- imported entry holds it, since posting is what calls this. Revoking EXECUTE
23
+ -- instead was not possible: `post_entry()` runs as its caller.
24
+ --
25
+ -- **`touch_api_key()`.** Called by `use_api_key()`, which is definer, so the
26
+ -- inner call runs as the owner and needs no grant of its own. It had one
27
+ -- anyway, from the default privileges of the schema, and stamped
28
+ -- `last_used_at` on any key whose id it was given. The id is not secret the
29
+ -- way the key is, but "this key has not been used for a year" is the signal an
30
+ -- operator withdraws keys on, and anybody signed in could reset it. Nobody but
31
+ -- `use_api_key()` has a reason to call it, so nobody else may.
32
+
33
+ create or replace function catch_up_journal_sequence(p_journal_id uuid, p_date date, p_number text)
34
+ returns void
35
+ language plpgsql
36
+ security definer
37
+ set search_path = public, pg_temp
38
+ as $$
39
+ declare
40
+ v_company uuid;
41
+ v_format text;
42
+ v_period smallint;
43
+ v_counter integer;
44
+ begin
45
+ select j.company_id into v_company from journals j where j.id = p_journal_id;
46
+ if v_company is null then
47
+ return;
48
+ end if;
49
+
50
+ if not is_installer() and not has_capability(v_company, 'entries.post') then
51
+ raise exception 'not_allowed: moving a counter in this company needs entries.post'
52
+ using errcode = '42501';
53
+ end if;
54
+
55
+ select number_format into v_format from numbering_rules(v_company);
56
+ v_counter := number_counter(v_format, p_number);
57
+ if v_counter is null then
58
+ return;
59
+ end if;
60
+
61
+ v_period := case when v_format ~ '\{(YYYY|YY)\}'
62
+ then extract(year from p_date)::smallint
63
+ else 0::smallint end;
64
+
65
+ insert into journal_sequences (journal_id, year, last_number)
66
+ values (p_journal_id, v_period, v_counter)
67
+ on conflict (journal_id, year)
68
+ do update set last_number = greatest(journal_sequences.last_number, excluded.last_number);
69
+ end;
70
+ $$;
71
+
72
+ comment on function catch_up_journal_sequence(uuid, date, text) is
73
+ 'Advances the counter of a journal to the counter inside a number that was written by hand, so the next automatic number continues the series. Definer, because nobody writes `journal_sequences` directly — and guarded like `next_entry_number()`: the installer, or entries.post on the company of the journal.';
74
+
75
+ revoke execute on function touch_api_key(uuid) from public, anon, authenticated;
@@ -0,0 +1,177 @@
1
+ -- Ekwo OS — every company somebody keeps, read at once.
2
+ --
3
+ -- `upcoming_filings()` and `filings_touched_since()` take a company. Several
4
+ -- companies inside one instance is the normal case — a firm with its clients —
5
+ -- and a firm that keeps forty of them does not ask "what is due" forty times.
6
+ -- It asks once: *all the companies I keep, what falls due in the fortnight,
7
+ -- and what moved after it went.*
8
+ --
9
+ -- **There is no firm in this file, and no list of clients.** The portfolio is
10
+ -- not an object: it is what the caller may read. Both functions walk the
11
+ -- companies on which the caller holds `filings.read`, and that one sentence is
12
+ -- the whole model — an accountant of forty companies gets forty, the person
13
+ -- who runs one of them gets one, and a group that keeps three of its own gets
14
+ -- three without anybody having been called a firm.
15
+ --
16
+ -- **Both are `security invoker`.** They read nothing a caller could not have
17
+ -- read by asking company by company: row level security on `tax_filings`, on
18
+ -- `company_filing_periods` and on the ledger does the sorting, as it does for
19
+ -- the two functions these are built on. The one thing the policies do not
20
+ -- settle is *which companies to walk* — `companies` is readable by a member
21
+ -- who has had `filings.read` taken away, and by the administrator of the
22
+ -- instance, who creates companies and keeps none of their books. Walking those
23
+ -- would list a calendar with every state empty, which reads as "nothing has
24
+ -- been prepared" and means "you may not know". So the list is filtered on the
25
+ -- capability, in the open, and the test takes the capability away to see the
26
+ -- company leave.
27
+ --
28
+ -- **Silence is not an output.** A reading that leaves a company out when it
29
+ -- has nothing to say cannot be told from a reading that never looked at it, and
30
+ -- over forty companies that is the difference that matters. So every company
31
+ -- of the portfolio is in every answer, and a row with no date says why:
32
+ --
33
+ -- no_deadline_rule the pack names no day for this form — a country whose
34
+ -- schedule depends on who is filing — and the period is
35
+ -- listed without one rather than dropped
36
+ -- nothing_due the company files, and nothing of it falls in the window
37
+ -- no_form no cadence recorded and no periodic return the
38
+ -- installation could name for its fiscal country
39
+ --
40
+ -- **The window is on the day a return is due**, which is the question a
41
+ -- portfolio is asked, and not on the period as `upcoming_filings()` reads it.
42
+ -- A period whose pack names no day has no date to test, so it is listed while
43
+ -- the month that follows it overlaps the window — the only place either rule
44
+ -- of the closed vocabulary ever puts a date.
45
+
46
+ -- ---------------------------------------------------------------------------
47
+ -- portfolio_upcoming_filings
48
+ -- ---------------------------------------------------------------------------
49
+
50
+ create or replace function portfolio_upcoming_filings(
51
+ p_from date,
52
+ p_to date
53
+ )
54
+ returns table (
55
+ company_id uuid,
56
+ company_name text,
57
+ report_code text,
58
+ period_start date,
59
+ period_end date,
60
+ due_date date,
61
+ state tax_filing_state,
62
+ filing_id uuid,
63
+ reason text
64
+ )
65
+ language sql
66
+ stable
67
+ security invoker
68
+ as $$
69
+ with portfolio as (
70
+ select c.id, c.name
71
+ from companies c
72
+ where has_capability(c.id, 'filings.read')
73
+ ),
74
+ -- A return is due in the month that follows its period, plus the days an
75
+ -- administration grants. So the periods that can fall due from `p_from` on
76
+ -- ended at most two months and that many days before it, and the calendar
77
+ -- of a company is asked for once, from there.
78
+ reach as (
79
+ select (p_from - interval '2 month'
80
+ - make_interval(days => coalesce(max(t.deadline_plus_days), 0)))::date as start
81
+ from tax_report_templates t
82
+ ),
83
+ listed as (
84
+ select p.id as company_id, u.*
85
+ from portfolio p
86
+ cross join reach r
87
+ cross join lateral upcoming_filings(p.id, r.start, p_to) u
88
+ where case
89
+ when u.due_date is not null then u.due_date between p_from and p_to
90
+ else u.period_end < p_to
91
+ and (date_trunc('month', u.period_end + interval '2 month')
92
+ - interval '1 day')::date >= p_from
93
+ end
94
+ )
95
+ select p.id,
96
+ p.name,
97
+ l.report_code,
98
+ l.period_start,
99
+ l.period_end,
100
+ l.due_date,
101
+ l.state,
102
+ l.filing_id,
103
+ case
104
+ when l.company_id is not null and l.due_date is null then 'no_deadline_rule'
105
+ when l.company_id is not null then null
106
+ when periodic_return_code(p.id) is null
107
+ and not exists (select 1 from company_filing_periods f
108
+ where f.company_id = p.id) then 'no_form'
109
+ else 'nothing_due'
110
+ end
111
+ from portfolio p
112
+ left join listed l on l.company_id = p.id
113
+ order by l.due_date nulls last, l.period_end nulls last, p.name, p.id, l.report_code;
114
+ $$;
115
+
116
+ comment on function portfolio_upcoming_filings(date, date) is
117
+ 'What falls due between two dates in every company the caller holds filings.read on, and in no other: the periods upcoming_filings() produces, kept when the day they are due is in the window. Every company of the portfolio is in the answer at least once, and a row without a date says why in `reason` — no_deadline_rule where the pack names no day for the form, nothing_due where nothing of the company falls in the window, no_form where the installation carries no return for it. Invoker: it reads what the caller could have read company by company.';
118
+
119
+ revoke execute on function portfolio_upcoming_filings(date, date) from public, anon;
120
+ grant execute on function portfolio_upcoming_filings(date, date) to authenticated, service_role;
121
+
122
+ -- ---------------------------------------------------------------------------
123
+ -- portfolio_filings_touched_since
124
+ --
125
+ -- `filed` is how many declarations of the company had gone and were looked
126
+ -- at, so that a company with no row of disturbance still says what was
127
+ -- examined: six returns and none moved is not the same news as no return.
128
+ -- ---------------------------------------------------------------------------
129
+
130
+ create or replace function portfolio_filings_touched_since(
131
+ p_from date default null,
132
+ p_to date default null
133
+ )
134
+ returns table (
135
+ company_id uuid,
136
+ company_name text,
137
+ filed integer,
138
+ filing_id uuid,
139
+ report_code text,
140
+ period_start date,
141
+ period_end date,
142
+ state tax_filing_state,
143
+ filed_at timestamptz,
144
+ entries integer,
145
+ last_entry_at timestamptz,
146
+ boxes_moved integer
147
+ )
148
+ language sql
149
+ stable
150
+ security invoker
151
+ as $$
152
+ with portfolio as (
153
+ select c.id, c.name
154
+ from companies c
155
+ where has_capability(c.id, 'filings.read')
156
+ )
157
+ select p.id,
158
+ p.name,
159
+ (select count(*)::integer
160
+ from tax_filings f
161
+ where f.company_id = p.id
162
+ and f.filed_at is not null
163
+ and f.state <> 'superseded'
164
+ and (p_from is null or f.period_end >= p_from)
165
+ and (p_to is null or f.period_start <= p_to)),
166
+ t.filing_id, t.report_code, t.period_start, t.period_end, t.state,
167
+ t.filed_at, t.entries, t.last_entry_at, t.boxes_moved
168
+ from portfolio p
169
+ left join lateral filings_touched_since(p.id, p_from, p_to) t on true
170
+ order by t.last_entry_at desc nulls last, p.name, p.id, t.period_start, t.report_code;
171
+ $$;
172
+
173
+ comment on function portfolio_filings_touched_since(date, date) is
174
+ 'Declarations that have gone and whose period the ledger moved afterwards, in every company the caller holds filings.read on, and in no other — filings_touched_since() with the company named, the latest disturbance first. Every company of the portfolio is in the answer at least once: one that was not disturbed is a row with no filing, and `filed` says how many of its declarations were looked at. Invoker: it reads what the caller could have read company by company.';
175
+
176
+ revoke execute on function portfolio_filings_touched_since(date, date) from public, anon;
177
+ grant execute on function portfolio_filings_touched_since(date, date) to authenticated, service_role;
@@ -0,0 +1,40 @@
1
+ -- ---------------------------------------------------------------------------
2
+ -- A session nobody prepared is not the installer
3
+ -- ---------------------------------------------------------------------------
4
+ -- `is_installer()` ended on `current_setting('ekwo.installing', true) = 'on'`.
5
+ -- A setting that was set and emptied answers '', and '' = 'on' is false. A
6
+ -- setting that was NEVER set answers NULL, and NULL = 'on' is NULL — so on a
7
+ -- new connection the function answered NULL, and every guard written
8
+ --
9
+ -- if not is_installer() and not has_capability(…) then raise …
10
+ --
11
+ -- evaluated `NULL and true`, which is NULL, which `if` does not take. The
12
+ -- guard was skipped. A person always carries a `sub` and is answered by
13
+ -- `has_capability()`; the caller with neither a session nor a key — the
14
+ -- `service_role` through PostgREST, a direct connection — walked through
15
+ -- create_company(), the counters, the API keys and the module switches.
16
+ --
17
+ -- The migration that introduced the function was the one about guards that
18
+ -- could answer NULL, and its tests never saw this one: the harness always set
19
+ -- the variable, to 'on' for the owner and to '' for a person. A test now
20
+ -- reloads the database into an instance where nothing was ever set
21
+ -- (tests/fresh_session.test.ts) and asks every argument-less boolean helper
22
+ -- the same question.
23
+ --
24
+ -- `service_role` bypasses row level security on Supabase, by design of the
25
+ -- platform. It does not bypass a function that raises — which is why the
26
+ -- functions are where an installation's rules are, and why this mattered.
27
+ -- ---------------------------------------------------------------------------
28
+
29
+ create or replace function is_installer()
30
+ returns boolean
31
+ language sql
32
+ stable
33
+ as $$
34
+ select auth.uid() is null
35
+ and nullif(current_setting('ekwo.api_key', true), '') is null
36
+ and coalesce(current_setting('ekwo.installing', true) = 'on', false);
37
+ $$;
38
+
39
+ comment on function is_installer() is
40
+ 'Whether the caller is the installation itself — the migration runner, the seeds, the CLI — rather than a person or a machine key. Set by the runner on its own connection; a session or a key can never be it, and neither can a connection where the setting was never made: the answer is false there, never NULL, because the guards negate it.';
@@ -0,0 +1,256 @@
1
+ -- Ekwo OS — a line keeps the category and the rate it was posted with.
2
+ --
3
+ -- `document_lines.vat_category` and `.vat_rate` have been there since the
4
+ -- table was, and three migrations have cited them as the precedent for a
5
+ -- snapshot: `20260915191200` for the language of a document, `20260915195000`
6
+ -- for a price that holds its tax, `20260915181000` for a territory. Nothing
7
+ -- ever wrote them. Every line of every golden year has them null, the view
8
+ -- publishes them as BT-151 and BT-152 all the same, and the first reader that
9
+ -- needed them — the brick that writes a Peppol invoice — found nothing and
10
+ -- went round by `tax_id`. `tests/vat_category.test.ts` had found it earlier
11
+ -- and said so in a comment: "Nothing in the core writes it."
12
+ --
13
+ -- The two ways out were to fill the columns or to take them out of the view.
14
+ -- They are filled, and the reason is the one those three migrations gave
15
+ -- while believing it already true: **what is posted does not move.** A tax
16
+ -- belongs to a company and a pack upgrade rewrites it in place — `amount`,
17
+ -- `vat_category` and the rest, in one `update taxes` — so a view that reads
18
+ -- the category and the rate of a sent invoice through `tax_id` reads what the
19
+ -- pack says today. A rate that changes on 1 January would restate every
20
+ -- invoice of December the first time anybody rendered one.
21
+ --
22
+ -- Four decisions.
23
+ --
24
+ -- **Derived, never keyed.** The category and the rate are facts about the tax
25
+ -- and not choices of the line, which is what `unit_price_includes_tax` is
26
+ -- already: a caller that writes them is overwritten while the document is a
27
+ -- draft. `vat_rate` is a percentage, so a tax that is a fixed amount leaves it
28
+ -- null — BT-152 has no room for an amount, and `post_document()` refuses such
29
+ -- a tax anyway.
30
+ --
31
+ -- **A draft follows its tax; a posted line refuses to move.** The same shape
32
+ -- as `documents_guard_language`: the line trigger reads the tax on every write
33
+ -- of a draft, a tax that changes reaches the drafts that carry it, and from
34
+ -- the moment the document is no longer a draft a change to either column is
35
+ -- refused by name, `document_line_tax_frozen`. Refused rather than quietly
36
+ -- ignored, because the one caller with a reason to write these columns is an
37
+ -- application that used to do the core's job, and it should learn that it no
38
+ -- longer has to.
39
+ --
40
+ -- **The breakdown reads the line, not the tax.** `document_tax_summary` is
41
+ -- BT-118 and BT-119, and it is also where `documents_refresh_totals()` and
42
+ -- `post_document()` take their figures. Reading the category and the rate
43
+ -- from the tax there while the lines carry a snapshot would have made the two
44
+ -- halves of one invoice disagree the day a rate moves — and would have kept
45
+ -- the older hazard, which is that touching a line of a posted document
46
+ -- recomputes its totals at today's rate. The view now groups on what the lines
47
+ -- carry. For a draft that is the tax, by construction; for a posted document
48
+ -- it is the tax as it stood. Same columns, same order, same types.
49
+ --
50
+ -- **The lines already here take the tax of today, once.** Nobody knows what
51
+ -- rate a tax had on the day an old invoice was posted: the ledger kept the
52
+ -- amounts and not the percentage. The tax as it stands is the only evidence
53
+ -- the installation holds, it is what every read of these documents has
54
+ -- answered until now, and it is what the totals were last computed from — so
55
+ -- the backfill changes no figure. It is a guess for the same reason the
56
+ -- backfill of `documents.language` was, and from here on nothing is guessed.
57
+ -- A line somebody's application had already stamped keeps its stamp.
58
+ --
59
+ -- What this does not close, and says so: the *share* of a tax that reaches the
60
+ -- other party (`tax_charged`) is still read from `tax_postings` as they stand,
61
+ -- and the exemption code and the legal reference of a tax are still read from
62
+ -- the tax. None of the three changes a base or a rate; all three are a pack
63
+ -- correcting itself under a document already sent. And no trigger refuses a
64
+ -- change of `quantity`, `unit_price` or `tax_id` on a posted line: the ledger
65
+ -- is immutable and the document that produced it is only partly so. That is
66
+ -- older than this file and wider than it.
67
+
68
+ -- ---------------------------------------------------------------------------
69
+ -- 1. What the lines already here carry
70
+ --
71
+ -- Before the guard exists, because it would refuse exactly this. The row
72
+ -- triggers fire, as they did for the backfill of `documents.language`: the
73
+ -- totals they recompute are read from the same tax the snapshot is being
74
+ -- taken from, so they come back unchanged.
75
+ -- ---------------------------------------------------------------------------
76
+
77
+ update document_lines l
78
+ set vat_category = t.vat_category,
79
+ vat_rate = case when t.amount_type = 'percent' then t.amount end
80
+ from taxes t, documents d
81
+ where t.id = l.tax_id
82
+ and d.id = l.document_id
83
+ and l.line_type = 'product'
84
+ and (d.state = 'draft' or (l.vat_category is null and l.vat_rate is null))
85
+ and (l.vat_category is distinct from t.vat_category
86
+ or l.vat_rate is distinct from case when t.amount_type = 'percent' then t.amount end);
87
+
88
+ -- ---------------------------------------------------------------------------
89
+ -- 2. Writing it, and refusing to rewrite it
90
+ -- ---------------------------------------------------------------------------
91
+
92
+ create or replace function document_lines_snapshot_tax()
93
+ returns trigger
94
+ language plpgsql
95
+ as $$
96
+ declare
97
+ v_state doc_state;
98
+ v_number text;
99
+ begin
100
+ select d.state, d.number into v_state, v_number
101
+ from documents d where d.id = new.document_id;
102
+
103
+ if tg_op = 'UPDATE' and v_state <> 'draft' then
104
+ if new.vat_category is distinct from old.vat_category
105
+ or new.vat_rate is distinct from old.vat_rate then
106
+ raise exception 'document_line_tax_frozen: line % of % was posted under category % at % per cent, and keeps both. A document already issued is not rewritten because its tax changed; credit it and issue another.',
107
+ new.sequence, coalesce(v_number, new.document_id::text),
108
+ coalesce(old.vat_category, 'none'), coalesce(old.vat_rate::text, 'no')
109
+ using errcode = '55006';
110
+ end if;
111
+ return new;
112
+ end if;
113
+
114
+ if new.line_type <> 'product' or new.tax_id is null then
115
+ new.vat_category := null;
116
+ new.vat_rate := null;
117
+ return new;
118
+ end if;
119
+
120
+ select t.vat_category,
121
+ case when t.amount_type = 'percent' then t.amount end
122
+ into new.vat_category, new.vat_rate
123
+ from taxes t where t.id = new.tax_id;
124
+ return new;
125
+ end;
126
+ $$;
127
+
128
+ comment on function document_lines_snapshot_tax() is
129
+ 'Writes BT-151 and BT-152 on a line from its tax for as long as the document is a draft, and refuses document_line_tax_frozen on a change to either once it is not.';
130
+
131
+ create trigger document_lines_snapshot_tax
132
+ before insert or update on document_lines
133
+ for each row execute function document_lines_snapshot_tax();
134
+
135
+ -- A trigger body is invoked by its table, never called.
136
+ revoke execute on function document_lines_snapshot_tax() from public, anon, authenticated, service_role;
137
+
138
+ comment on column document_lines.vat_category is
139
+ 'EN 16931 BT-151 as the line carries it. Derived from the tax while the document is a draft and frozen when it is posted, so a pack upgrade that recategorises a tax cannot rewrite an invoice that has been sent. Never keyed.';
140
+
141
+ comment on column document_lines.vat_rate is
142
+ 'EN 16931 BT-152 as the line carries it: the percentage of the tax, derived while the document is a draft and frozen when it is posted, so a later change of rate cannot rewrite an invoice that has been sent. Null where the tax is not a percentage. Never keyed.';
143
+
144
+ -- ---------------------------------------------------------------------------
145
+ -- 3. A tax that changes, and the drafts that carry it
146
+ --
147
+ -- The other half of "a draft follows its tax": a draft nobody touches again
148
+ -- would keep an answer that is no longer the tax's. Bounded to the draft lines
149
+ -- of that one tax — `document_lines_tax_idx` is the index it reads — and the
150
+ -- write goes through the line's own triggers, so the totals of those drafts
151
+ -- follow the new rate too, which until now they did not until somebody edited
152
+ -- a line.
153
+ --
154
+ -- `price_include` is deliberately not among the columns that fire it. The
155
+ -- lines are rewritten one at a time, and a group caught half way between a
156
+ -- price with its tax and a price without is what `mixed_price_include`
157
+ -- refuses: the flag would make the correction of a tax fail on the first
158
+ -- draft with two lines. That snapshot keeps the rule it was published with.
159
+ --
160
+ -- `security definer`, for the reason `contacts_language_reaches_drafts` gives:
161
+ -- whoever may correct a tax is not thereby allowed to write a document, and
162
+ -- should not be refused because a draft sits behind a policy they do not
163
+ -- satisfy. What is written is derived data on rows nothing has issued yet.
164
+ -- ---------------------------------------------------------------------------
165
+
166
+ create or replace function taxes_reach_draft_lines()
167
+ returns trigger
168
+ language plpgsql
169
+ security definer
170
+ set search_path = public, pg_temp
171
+ as $$
172
+ begin
173
+ update document_lines l
174
+ set vat_category = new.vat_category
175
+ from documents d
176
+ where l.tax_id = new.id
177
+ and d.id = l.document_id
178
+ and d.state = 'draft';
179
+ return null;
180
+ end;
181
+ $$;
182
+
183
+ comment on function taxes_reach_draft_lines() is
184
+ 'A tax whose category, rate or kind of amount changes rewrites the snapshot of the draft lines that carry it, and nothing else: a posted line keeps what it was posted with.';
185
+
186
+ create trigger taxes_reach_draft_lines
187
+ after update of vat_category, amount, amount_type on taxes
188
+ for each row
189
+ when (new.vat_category is distinct from old.vat_category
190
+ or new.amount is distinct from old.amount
191
+ or new.amount_type is distinct from old.amount_type)
192
+ execute function taxes_reach_draft_lines();
193
+
194
+ revoke execute on function taxes_reach_draft_lines() from public, anon, authenticated, service_role;
195
+
196
+ -- ---------------------------------------------------------------------------
197
+ -- 4. The breakdown groups on what the lines carry
198
+ --
199
+ -- `20260915195000` unchanged but for where the category and the rate come
200
+ -- from. `tax_rate` keeps the type it had — a view column cannot change type
201
+ -- under `create or replace` — and the arithmetic reads the same figure it
202
+ -- publishes. `security_invoker`, the comment and the grants are those of the
203
+ -- object it replaces; the grants are restated because saying so again is
204
+ -- cheaper than trusting it.
205
+ -- ---------------------------------------------------------------------------
206
+
207
+ create or replace view document_tax_summary
208
+ with (security_invoker = true) as
209
+ select l.document_id,
210
+ l.company_id,
211
+ d.doc_type,
212
+ l.tax_id,
213
+ t.code as tax_code,
214
+ t.name as tax_name,
215
+ l.vat_category,
216
+ l.vat_rate::numeric(12, 4) as tax_rate,
217
+ sum(l.amount_untaxed) as base_amount,
218
+ -- Gross tax: what the VAT return reports.
219
+ round_amount(
220
+ case when bool_and(l.unit_price_includes_tax)
221
+ then sum(l.amount_incl_tax)
222
+ - sum(l.amount_incl_tax) / (1 + coalesce(l.vat_rate, 0) / 100)
223
+ else sum(l.amount_untaxed) * coalesce(l.vat_rate, 0) / 100
224
+ end,
225
+ rounding_of(l.company_id, d.currency_code)) as tax_amount,
226
+ -- Charged tax: what the other party actually pays. Zero when the tax
227
+ -- postings net out, which is exactly what self-assessment means.
228
+ round_amount(
229
+ case when bool_and(l.unit_price_includes_tax)
230
+ then sum(l.amount_incl_tax)
231
+ - sum(l.amount_incl_tax) / (1 + coalesce(l.vat_rate, 0) / 100)
232
+ else sum(l.amount_untaxed) * coalesce(l.vat_rate, 0) / 100
233
+ end
234
+ * coalesce((
235
+ select sum(tp.factor_percent)
236
+ from tax_postings tp
237
+ where tp.tax_id = t.id
238
+ and tp.posting_type in ('tax', 'tax_on_base')
239
+ and tp.document_kind = case
240
+ when d.doc_type in ('sale_credit_note', 'purchase_credit_note')
241
+ then 'credit_note'::tax_document_kind
242
+ else 'invoice'::tax_document_kind
243
+ end
244
+ ), 100) / 100,
245
+ rounding_of(l.company_id, d.currency_code)) as tax_charged
246
+ from document_lines l
247
+ join documents d on d.id = l.document_id
248
+ left join taxes t on t.id = l.tax_id
249
+ where l.line_type = 'product'
250
+ group by l.document_id, l.company_id, d.doc_type, d.currency_code, l.tax_id,
251
+ t.id, t.code, t.name, l.vat_category, l.vat_rate;
252
+
253
+ comment on view document_tax_summary is
254
+ 'VAT breakdown of a document, one row per tax, rounded once on the group (EN 16931 BR-CO-14) — on the group''s base, or on the gross it was quoted at where the price includes the tax. The category and the rate are the ones the lines carry: the tax of today for a draft, the tax as it stood for a document that was posted.';
255
+
256
+ grant select on table document_tax_summary to authenticated, service_role;
@@ -0,0 +1,66 @@
1
+ -- Ekwo OS — the company that sends an invoice has an address to send it from.
2
+ --
3
+ -- `contacts` has carried `peppol_scheme` and `peppol_identifier` since the
4
+ -- first day: the electronic address of the buyer, BT-49. The seller had
5
+ -- neither. BT-34 is mandatory on the network, so the first brick that wrote a
6
+ -- Peppol invoice had to be *told* the seller's address by its caller, and the
7
+ -- end-to-end test registered the company under its VAT number and said that
8
+ -- it was the test's choice. It is nobody's choice: which identifier a
9
+ -- participant is registered under is a fact of its registration with an access
10
+ -- point, and the only party that knows it is the company.
11
+ --
12
+ -- **The same two columns, under the same two names.** A scheme and a value,
13
+ -- which is the shape `docs/international.md` asks every identifier of this
14
+ -- schema to take. `peppol_` is the name `contacts` already uses and a second
15
+ -- name for the same thing would be a join somebody has to remember; the scheme
16
+ -- is a code of the Electronic Address Scheme list (EAS) that EN 16931 cites
17
+ -- for BT-34-1 and BT-49-1, whichever network delivers.
18
+ --
19
+ -- **No list of schemes.** `contacts` has none and the core gets none here: the
20
+ -- list is published by the Connecting Europe Facility, revised twice a year,
21
+ -- and differs between EN 16931 and Peppol — the brick that writes the file
22
+ -- carries both and names the rule a scheme breaks. A list here would be a
23
+ -- third copy, and the one most likely to be stale.
24
+ --
25
+ -- **One constraint `contacts` does not have: the pair is whole or absent.** A
26
+ -- scheme without an identifier addresses nobody and an identifier without a
27
+ -- scheme cannot be written at all (BR-62). The columns are new on `companies`,
28
+ -- so the check refuses nothing that exists. On `contacts` they are not, and
29
+ -- an installation may hold half a pair somebody typed in 2026; a constraint
30
+ -- added there would either refuse this migration or, `not valid`, refuse the
31
+ -- next unrelated edit of that contact. It is left for the release that can
32
+ -- look at the data first.
33
+ --
34
+ -- **Who may write it** is who may write the company: `companies_update`
35
+ -- judges the row, `company.write`, and there is nothing in an electronic
36
+ -- address that a member allowed to change the VAT number should not change.
37
+ --
38
+ -- The second half of this file corrects two comments. `party_scheme` and
39
+ -- `vat_scheme` were documented as "ISO 6523 ICD", and the packs — rightly —
40
+ -- fill them with codes of the EAS list, which contains the ICD list and more:
41
+ -- the VAT schemes of the 99xx range are not ISO 6523 identifiers at all. Read
42
+ -- as documented, `party_scheme` was taken for the scheme a registration number
43
+ -- is written in (BT-30-1) and produced BR-CL-11 for two packs out of four.
44
+
45
+ alter table companies
46
+ add column if not exists peppol_scheme text,
47
+ add column if not exists peppol_identifier text;
48
+
49
+ alter table companies
50
+ add constraint companies_electronic_address_whole
51
+ check ((peppol_scheme is null) = (peppol_identifier is null));
52
+
53
+ comment on column companies.peppol_scheme is
54
+ 'Scheme of the electronic address this company receives and sends under (EN 16931 BT-34-1): a code of the Electronic Address Scheme list, e.g. 0088 for a GLN. A fact of the company''s registration with its access point, never derived from its VAT or registration number. Null together with peppol_identifier.';
55
+ comment on column companies.peppol_identifier is
56
+ 'The electronic address itself (EN 16931 BT-34), in the scheme peppol_scheme names.';
57
+
58
+ comment on column contacts.peppol_scheme is
59
+ 'Scheme of the electronic address this party is reached at (EN 16931 BT-49-1): a code of the Electronic Address Scheme list. A fact of the party''s registration, never derived from its VAT or registration number.';
60
+ comment on column contacts.peppol_identifier is
61
+ 'The electronic address itself (EN 16931 BT-49), in the scheme peppol_scheme names.';
62
+
63
+ comment on column country_defaults.party_scheme is
64
+ 'The Electronic Address Scheme (EAS) code under which a party of this country is usually *addressed* on the network — the scheme of an electronic address, BT-34-1 and BT-49-1. Four characters; the pack carries the value and the core never guesses one. It is a default an application may propose, not the address of anybody: that is companies.peppol_scheme and contacts.peppol_scheme. And it is not the scheme a registration number is written in (BT-30-1, BT-47-1), which has to be an ISO 6523 ICD: several EAS codes are not.';
65
+ comment on column country_defaults.vat_scheme is
66
+ 'The Electronic Address Scheme (EAS) code under which a party of this country is addressed by its VAT number, where the network allows it. Distinct from party_scheme: a company is addressed by its registration number or by its VAT number, and they are not the same identifier. Not an ISO 6523 ICD in general — the VAT schemes of the EAS list are outside that standard.';