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,795 @@
1
+ -- Ekwo OS — a company records one cadence per declaration, not one cadence.
2
+ --
3
+ -- `20260914163943` gave a company a filing cadence and named the column after
4
+ -- the return it was about: `companies.vat_period`. That was one column short of
5
+ -- the fact. A company is subject to **several** declarations, and each of them
6
+ -- has a cadence of its own that the return's does not decide:
7
+ --
8
+ -- * the recapitulative statement of intra-Community supplies is monthly in
9
+ -- France from the first euro, monthly in Belgium above a threshold that
10
+ -- counts goods alone whatever the return's cadence, filed on separately
11
+ -- chosen cadences for goods and for services in Luxembourg, and filed with
12
+ -- the return in Estonia;
13
+ -- * the annual return, where a country files one beside the periodic one, is
14
+ -- annual by definition and says nothing about the periodic one;
15
+ -- * and a company registered in two countries files each country's form on
16
+ -- that country's cadence.
17
+ --
18
+ -- So `ec_sales_list()` refused no period at all, where `vat_return()` refused
19
+ -- one the company does not file on. Borrowing the return's guard would have
20
+ -- refused a lawful monthly statement from a quarterly Belgian filer, which is
21
+ -- the ordinary case — the statement had no cadence of its own to read, and one
22
+ -- column named after one form could not give it one. `docs/international.md`
23
+ -- recorded it under *A company records how often it files its return, and that
24
+ -- is not how often it files anything else*.
25
+ --
26
+ -- Three pieces here, and the third is the one that carries the change.
27
+ --
28
+ -- `tax_report_templates.period_default` what the **form** is filed on unless
29
+ -- the company says otherwise. It moves the proposal from the country to
30
+ -- the form, which is where it belongs: a country proposing one cadence
31
+ -- could only ever speak about one declaration, and the moment a company
32
+ -- records a cadence per form the proposal has to be per form too.
33
+ -- `company_filing_periods` what **this company** files, one row
34
+ -- per declaration it is subject to. Absent means not recorded, which is
35
+ -- not an error and not a cadence.
36
+ -- `companies.vat_period` kept, deprecated, and **derived**.
37
+ -- See below: it is a mirror of one row of the table and never a second
38
+ -- place the fact is decided.
39
+ --
40
+ -- **Why `companies.vat_period` is kept.** It has been published since v0.3.0:
41
+ -- `ekwo status` prints it, `ekwo init` writes it, and an installation's own
42
+ -- clients read it over PostgREST. Dropping it would break a reader outside this
43
+ -- repository for a column whose value is still exactly right — a company files
44
+ -- its periodic return on one cadence, and that is what it says.
45
+ --
46
+ -- **Why it is not a second truth.** The rule of this repository is one source
47
+ -- for one fact, and two writable columns holding one cadence is how they drift.
48
+ -- So the table is the source and the column is its mirror, held in step by two
49
+ -- triggers that each write only when the other is out of date: a row written
50
+ -- for the country's periodic return updates the column, and a write to the
51
+ -- column writes the row. That is the shape `20260915200000` used for
52
+ -- `declaration_box` and `declaration_boxes`, and it is here for the same
53
+ -- reason: a published column stays readable and a writer that knows only the
54
+ -- old name still writes the new fact.
55
+ --
56
+ -- The mirror needs to know **which** of a company's declarations is the one the
57
+ -- column is named after, and it finds it the way the rest of the core finds
58
+ -- anything about a country: by asking the pack. `periodic_return_code()`
59
+ -- returns the code of the form the company's fiscal country files periodically
60
+ -- when there is exactly one, and null when there is none or more than one — a
61
+ -- country that files two periodic returns at once has no single form the column
62
+ -- could mean, and the column then stays where it is while the table carries
63
+ -- both. No country is named anywhere in this file.
64
+ --
65
+ -- **What is not moved.** A company whose installation carries no periodic
66
+ -- return form for its country keeps whatever is in the column and gains no row:
67
+ -- there is no declaration to name, so there is nothing the value could be
68
+ -- recorded against. Nothing is lost and nothing is invented.
69
+
70
+ -- ---------------------------------------------------------------------------
71
+ -- What a form proposes
72
+ -- ---------------------------------------------------------------------------
73
+ --
74
+ -- `country_defaults.vat_period_default` asked the wrong question. It asked what
75
+ -- a *country* proposes, and a country proposes nothing: a law proposes a
76
+ -- cadence for a form. As long as a pack carried one form the two were the same
77
+ -- sentence, and the day a pack carries two the country-level answer cannot say
78
+ -- which one it is about.
79
+ --
80
+ -- It also made a rule out of an accident. `ekwo pack check` let a pack propose
81
+ -- a cadence only where its form accepts exactly one, on the reasoning that a
82
+ -- form filed on several means the answer depends on a fact about the company —
83
+ -- turnover, which is how Belgium, France and Luxembourg all set it. That
84
+ -- reasoning is about the *law* and the rule read the *length of a list*, and
85
+ -- the two part company: regulation 25(1) of the Value Added Tax Regulations
86
+ -- 1995 makes the prescribed accounting period three months for everybody, and a
87
+ -- month or a year is something the Commissioners allow or direct on
88
+ -- application. A form filed on three cadences whose law gives one default was
89
+ -- the case the rule could not express. With the proposal on the form, it can:
90
+ -- the form says what it accepts and what it is filed on when nobody has asked
91
+ -- for anything else, and `ekwo pack check` only refuses a proposal the form
92
+ -- does not accept.
93
+
94
+ alter table tax_report_templates
95
+ add column if not exists period_default declaration_period;
96
+
97
+ alter table tax_report_templates
98
+ add constraint tax_report_templates_default_is_offered check (
99
+ period_default is null or period_default = any (periods)
100
+ );
101
+
102
+ comment on column tax_report_templates.period_default is
103
+ 'Cadence this form is filed on unless the company has asked the administration for another, from packs/<cc>/tax_report.json. Null where the law makes the cadence depend on a fact about the company — turnover — because proposing one of several lawful answers there would be choosing a filing deadline for somebody the pack knows nothing about. Set where the law gives one answer to everybody, whatever else the form accepts. Read at install; never read by the return.';
104
+
105
+ -- What the packs already proposed, moved onto the form it was about. A country
106
+ -- default that names a cadence the form does not accept is left behind rather
107
+ -- than written into a column that refuses it: `ekwo pack check` has refused
108
+ -- that combination since the proposal existed, so there is none to move.
109
+ update tax_report_templates t
110
+ set period_default = d.vat_period_default
111
+ from country_defaults d
112
+ where d.country = t.country
113
+ and t.is_periodic_return
114
+ and d.vat_period_default is not null
115
+ and d.vat_period_default = any (t.periods)
116
+ and t.period_default is null;
117
+
118
+ comment on column country_defaults.vat_period_default is
119
+ 'Deprecated by tax_report_templates.period_default, which asks the same question of the form rather than of the country. Kept because it is published and still written by the pack compiler, with the value of the periodic return''s own default; a country that files two periodic returns has two answers and this column can hold one, which is why the form is now the place. Read nothing new from here.';
120
+
121
+ -- ---------------------------------------------------------------------------
122
+ -- Which of a company's declarations the deprecated column is named after
123
+ -- ---------------------------------------------------------------------------
124
+
125
+ create or replace function periodic_return_code(p_company_id uuid)
126
+ returns text
127
+ language sql
128
+ stable
129
+ as $$
130
+ select case when count(distinct t.code) = 1 then min(t.code) end
131
+ from companies c
132
+ join tax_report_templates t on t.country = c.fiscal_country
133
+ where c.id = p_company_id
134
+ and t.is_periodic_return;
135
+ $$;
136
+
137
+ comment on function periodic_return_code(uuid) is
138
+ 'The code of the periodic return the company''s fiscal country files, when the installation carries exactly one. Null when it carries none — no pack for that country — and null when it carries several, because then no single form is what a column named after "the" return could mean. It is what holds companies.vat_period and company_filing_periods in step, and it names the form by asking the pack rather than by knowing a country.';
139
+
140
+ revoke execute on function periodic_return_code(uuid) from public, anon;
141
+ grant execute on function periodic_return_code(uuid) to authenticated, service_role;
142
+
143
+ -- ---------------------------------------------------------------------------
144
+ -- What this company files, declaration by declaration
145
+ -- ---------------------------------------------------------------------------
146
+ --
147
+ -- The key is the company and the code of the form, because that is what the
148
+ -- fact is: *this company files that declaration every month*. No surrogate
149
+ -- identifier — the pair is the natural key, it is the key every reader joins
150
+ -- on, and `company_members` and the rest of the schema already key a row of a
151
+ -- company on what names it.
152
+ --
153
+ -- `report_code` carries no foreign key onto `tax_report_templates`, and that is
154
+ -- deliberate. The form is keyed on `(country, code)`, a company's fiscal
155
+ -- country can change, and a company registered in a second country files that
156
+ -- country's form while its own fiscal country is elsewhere — a foreign key on
157
+ -- the pair would refuse a lawful row and a foreign key on the code alone does
158
+ -- not exist. What replaces it is a trigger that refuses a code no form of this
159
+ -- installation carries, which is the typo a foreign key would have caught and
160
+ -- nothing more.
161
+
162
+ create table company_filing_periods (
163
+ company_id uuid not null references companies(id) on delete cascade,
164
+ report_code text not null,
165
+ period declaration_period not null,
166
+ created_at timestamptz not null default now(),
167
+ updated_at timestamptz not null default now(),
168
+ primary key (company_id, report_code),
169
+ constraint company_filing_periods_has_a_report check (length(report_code) > 0)
170
+ );
171
+
172
+ comment on table company_filing_periods is
173
+ 'How often this company files each declaration it is subject to: one row per form of tax_report_templates, and no row where nothing has been recorded. A company files its periodic return on one cadence and its recapitulative statement on another, and the second is not derivable from the first in any country read so far.';
174
+ comment on column company_filing_periods.report_code is
175
+ 'The form, by the code tax_report_templates gives it. Not a foreign key: the form is keyed on (country, code) and a company may file a form of a country that is not its own. A trigger refuses a code no form of this installation carries.';
176
+ comment on column company_filing_periods.period is
177
+ 'How often this company files that form. Not null: a row that recorded nothing would say the same thing as no row, in a second way.';
178
+
179
+ create trigger company_filing_periods_set_updated_at
180
+ before update on company_filing_periods
181
+ for each row execute function set_updated_at();
182
+
183
+ -- ---------------------------------------------------------------------------
184
+ -- A code that names no form is refused, by name
185
+ -- ---------------------------------------------------------------------------
186
+
187
+ create or replace function company_filing_periods_names_a_form()
188
+ returns trigger
189
+ language plpgsql
190
+ as $$
191
+ begin
192
+ if not exists (select 1 from tax_report_templates t where t.code = new.report_code) then
193
+ raise exception 'unknown_tax_report: % is not a declaration form of this installation', new.report_code;
194
+ end if;
195
+ return new;
196
+ end;
197
+ $$;
198
+
199
+ comment on function company_filing_periods_names_a_form() is
200
+ 'Refuses a filing cadence recorded against a form this installation does not carry. Raise, do not warn: a row nothing can resolve is a cadence nobody files on, and the guards that read it would simply never fire.';
201
+
202
+ revoke execute on function company_filing_periods_names_a_form() from public, anon, authenticated, service_role;
203
+
204
+ create trigger company_filing_periods_names_a_form
205
+ before insert or update on company_filing_periods
206
+ for each row execute function company_filing_periods_names_a_form();
207
+
208
+ -- ---------------------------------------------------------------------------
209
+ -- Reading one cadence
210
+ -- ---------------------------------------------------------------------------
211
+
212
+ create or replace function filing_period(p_company_id uuid, p_report_code text)
213
+ returns declaration_period
214
+ language sql
215
+ stable
216
+ as $$
217
+ select f.period
218
+ from company_filing_periods f
219
+ where f.company_id = p_company_id
220
+ and f.report_code = p_report_code;
221
+ $$;
222
+
223
+ comment on function filing_period(uuid, text) is
224
+ 'How often this company files that declaration, or null when it has not been recorded. Null is not a cadence and not an error: the books are kept the same either way, and a guard that reads it refuses nothing.';
225
+
226
+ revoke execute on function filing_period(uuid, text) from public, anon;
227
+ grant execute on function filing_period(uuid, text) to authenticated, service_role;
228
+
229
+ -- ---------------------------------------------------------------------------
230
+ -- What was recorded before, moved onto the declaration it was about
231
+ -- ---------------------------------------------------------------------------
232
+
233
+ insert into company_filing_periods (company_id, report_code, period)
234
+ select c.id, periodic_return_code(c.id), c.vat_period
235
+ from companies c
236
+ where c.vat_period is not null
237
+ and periodic_return_code(c.id) is not null
238
+ on conflict (company_id, report_code) do nothing;
239
+
240
+ -- ---------------------------------------------------------------------------
241
+ -- The deprecated column, kept in step
242
+ -- ---------------------------------------------------------------------------
243
+ --
244
+ -- Each trigger writes only when the other side is out of date, which is what
245
+ -- makes the pair terminate: the second write finds the value already there and
246
+ -- changes no row, so no third trigger fires.
247
+
248
+ create or replace function company_filing_periods_mirror()
249
+ returns trigger
250
+ language plpgsql
251
+ as $$
252
+ declare
253
+ v_company uuid;
254
+ v_code text;
255
+ v_was text;
256
+ v_return text;
257
+ v_now declaration_period;
258
+ begin
259
+ -- `old` and `new` are each assigned for two of the three operations, so both
260
+ -- are read in a branch that knows which one this is rather than in one
261
+ -- condition relying on the order an `or` happens to be evaluated in.
262
+ if tg_op = 'DELETE' then
263
+ v_company := old.company_id;
264
+ v_code := old.report_code;
265
+ v_was := old.report_code;
266
+ elsif tg_op = 'UPDATE' then
267
+ v_company := new.company_id;
268
+ v_code := new.report_code;
269
+ v_was := old.report_code;
270
+ else
271
+ v_company := new.company_id;
272
+ v_code := new.report_code;
273
+ v_was := new.report_code;
274
+ end if;
275
+
276
+ -- A row about any other declaration says nothing about a column named after
277
+ -- the periodic return. The code the row had is looked at too, so an update
278
+ -- that renames a row away from the return clears the column.
279
+ v_return := periodic_return_code(v_company);
280
+ if v_code is distinct from v_return and v_was is distinct from v_return then
281
+ return null;
282
+ end if;
283
+
284
+ v_now := filing_period(v_company, v_return);
285
+ update companies c
286
+ set vat_period = v_now
287
+ where c.id = v_company
288
+ and c.vat_period is distinct from v_now;
289
+ return null;
290
+ end;
291
+ $$;
292
+
293
+ comment on function company_filing_periods_mirror() is
294
+ 'Writes companies.vat_period from the row that records how often this company files its country''s periodic return. The column is a mirror and never a second decision: it is written here, from the table, and only when the two differ.';
295
+
296
+ revoke execute on function company_filing_periods_mirror() from public, anon, authenticated, service_role;
297
+
298
+ create trigger company_filing_periods_mirror
299
+ after insert or update or delete on company_filing_periods
300
+ for each row execute function company_filing_periods_mirror();
301
+
302
+ create or replace function companies_vat_period_is_a_filing_period()
303
+ returns trigger
304
+ language plpgsql
305
+ as $$
306
+ declare
307
+ v_code text;
308
+ begin
309
+ -- Nothing moved, so there is nothing to record. `old` is read only in the
310
+ -- branch where it exists.
311
+ if tg_op = 'UPDATE' then
312
+ if new.vat_period is not distinct from old.vat_period then
313
+ return null;
314
+ end if;
315
+ elsif new.vat_period is null then
316
+ return null;
317
+ end if;
318
+ v_code := periodic_return_code(new.id);
319
+ -- Nothing names the declaration this value is about, so there is nothing to
320
+ -- record it against. The column keeps what it was given.
321
+ if v_code is null then
322
+ return null;
323
+ end if;
324
+ if new.vat_period is null then
325
+ delete from company_filing_periods f
326
+ where f.company_id = new.id and f.report_code = v_code;
327
+ else
328
+ insert into company_filing_periods (company_id, report_code, period)
329
+ values (new.id, v_code, new.vat_period)
330
+ on conflict (company_id, report_code) do update
331
+ set period = excluded.period
332
+ where company_filing_periods.period is distinct from excluded.period;
333
+ end if;
334
+ return null;
335
+ end;
336
+ $$;
337
+
338
+ comment on function companies_vat_period_is_a_filing_period() is
339
+ 'Records a write to the deprecated companies.vat_period as what it is: how often this company files its country''s periodic return. Everything written before this migration named that column and nothing else, and this is what keeps such a writer correct without it learning a table.';
340
+
341
+ revoke execute on function companies_vat_period_is_a_filing_period() from public, anon, authenticated, service_role;
342
+
343
+ create trigger companies_vat_period_is_a_filing_period
344
+ after insert or update of vat_period on companies
345
+ for each row execute function companies_vat_period_is_a_filing_period();
346
+
347
+ comment on column companies.vat_period is
348
+ 'Deprecated by company_filing_periods, and derived from it: how often this company files its country''s periodic return, which is one of the several declarations it is subject to. Null means nothing has been recorded, which is not an error and not a cadence. Write the table; this column is written from it, and a write here is recorded there.';
349
+
350
+ -- ---------------------------------------------------------------------------
351
+ -- Who reads and who writes
352
+ -- ---------------------------------------------------------------------------
353
+ --
354
+ -- Reading is reading a company: the same reach as the row this used to be a
355
+ -- column of. Writing is `company.write`, which is what governs
356
+ -- `companies.vat_period` today, and the two match on purpose — a capability
357
+ -- that could write the table and not the column would make the mirror a write
358
+ -- that does not take, which is the worst kind.
359
+
360
+ alter table company_filing_periods enable row level security;
361
+
362
+ create policy company_filing_periods_select on company_filing_periods
363
+ for select using (is_company_member(company_id));
364
+ create policy company_filing_periods_write on company_filing_periods
365
+ for all using (has_capability(company_id, 'company.write'))
366
+ with check (has_capability(company_id, 'company.write'));
367
+
368
+ comment on policy company_filing_periods_select on company_filing_periods is
369
+ 'A member of the company reads how often it files, exactly as they read the company itself.';
370
+ comment on policy company_filing_periods_write on company_filing_periods is
371
+ 'company.write, the capability that governs the deprecated column this mirrors. Anything narrower would let somebody record a cadence the mirror could not write.';
372
+
373
+ grant select, insert, update, delete on table company_filing_periods
374
+ to authenticated, service_role;
375
+
376
+ create trigger company_filing_periods_audit
377
+ after insert or update or delete on company_filing_periods
378
+ for each row execute function audit_changes('{"company":"company_id","key":["report_code"]}');
379
+
380
+ -- ---------------------------------------------------------------------------
381
+ -- The return reads the cadence of the return it was asked for
382
+ -- ---------------------------------------------------------------------------
383
+ --
384
+ -- Replaced whole, because a function is replaced whole in PostgreSQL.
385
+ -- Everything but the guard is `20260915200000` unchanged.
386
+ --
387
+ -- The change is one word wide and it is the whole point of this migration: the
388
+ -- cadence read is the one recorded **for the form being asked for**, not the
389
+ -- one recorded for the company. A company that files its return quarterly and
390
+ -- its recapitulative statement monthly is two rows, and each guard reads its
391
+ -- own.
392
+
393
+ create or replace function vat_return(
394
+ p_company_id uuid,
395
+ p_from date,
396
+ p_to date,
397
+ p_report_code text default null
398
+ )
399
+ returns table (
400
+ box text,
401
+ kind text,
402
+ amount numeric,
403
+ computed boolean,
404
+ name text,
405
+ sequence integer,
406
+ hidden boolean,
407
+ report_code text
408
+ )
409
+ language plpgsql
410
+ stable
411
+ as $$
412
+ declare
413
+ -- 'box|kind' -> amount, for every box summed from the ledger, then the
414
+ -- totals `evaluate_totals()` derives from them. The key carries the kind
415
+ -- because the French CA3 puts a base and a tax on line 08 and a formula has
416
+ -- to be able to name one of them.
417
+ v_values jsonb := '{}'::jsonb;
418
+ v_formulas jsonb := '[]'::jsonb;
419
+ v_totals jsonb := '{}'::jsonb;
420
+ v_rows jsonb := '[]'::jsonb;
421
+ v_country char(2);
422
+ v_report text;
423
+ v_in char(2);
424
+ v_count integer;
425
+ v_codes text;
426
+ -- How often this company files, what the form accepts, and what the two
427
+ -- dates asked for actually are.
428
+ v_files declaration_period;
429
+ v_accepts declaration_period[];
430
+ v_asked declaration_period;
431
+ -- A declaration figure is not a ledger figure, but it is written in the
432
+ -- same currency and with the same decimals.
433
+ v_round money_rounding;
434
+ r record;
435
+ begin
436
+ select c.fiscal_country into v_country
437
+ from companies c where c.id = p_company_id;
438
+ if not found then
439
+ raise exception 'unknown_company: %', p_company_id;
440
+ end if;
441
+ v_round := rounding_of(p_company_id);
442
+
443
+ -- Which form. The caller names one, or the country files exactly one on
444
+ -- that date. Two and no name is a question only the caller can answer — a
445
+ -- Canadian company files the federal return and the Québec one at once — so
446
+ -- this asks instead of guessing.
447
+ if p_report_code is not null then
448
+ select t.country, t.code into v_in, v_report
449
+ from tax_report_templates t
450
+ where t.code = p_report_code
451
+ and t.valid_from <= p_to
452
+ and (t.valid_to is null or t.valid_to >= p_to)
453
+ order by t.country
454
+ limit 1;
455
+ if v_report is null then
456
+ raise exception 'unknown_tax_report: % is not a declaration form in force on %',
457
+ p_report_code, p_to;
458
+ end if;
459
+ else
460
+ select count(*), min(t.code), string_agg(t.code, ', ' order by t.code)
461
+ into v_count, v_report, v_codes
462
+ from tax_report_templates t
463
+ where t.country = v_country
464
+ and t.is_periodic_return
465
+ and t.valid_from <= p_to
466
+ and (t.valid_to is null or t.valid_to >= p_to);
467
+ if v_count > 1 then
468
+ raise exception 'ambiguous_tax_report: % files several declarations on % (%); name one',
469
+ v_country, p_to, v_codes;
470
+ end if;
471
+ v_in := v_country;
472
+ if v_count = 0 then
473
+ v_report := null; -- no pack for this country: the ledger boxes, and no total.
474
+ end if;
475
+ end if;
476
+
477
+ -- The period asked for against the one this company files **this form** on.
478
+ -- Four things have to hold before this refuses, and the fourth is what keeps
479
+ -- it out of everybody's way: the company has recorded a cadence for this
480
+ -- declaration, the form is filed on that cadence, the dates are themselves a
481
+ -- whole cadence of that form, and the two are not the same. A fortnight, a
482
+ -- half-year, a form the company has recorded nothing about — none of those is
483
+ -- a filing on the wrong cadence, and none of them is refused.
484
+ if v_report is not null then
485
+ v_files := filing_period(p_company_id, v_report);
486
+ if v_files is not null then
487
+ select t.periods into v_accepts
488
+ from tax_report_templates t
489
+ where t.country = v_in and t.code = v_report;
490
+ v_asked := declaration_period_of(p_from, p_to);
491
+ if v_asked is not null
492
+ and v_files = any(v_accepts)
493
+ and v_asked = any(v_accepts)
494
+ and v_asked <> v_files then
495
+ raise exception
496
+ 'wrong_declaration_period: this company files % returns on %; % to % is a %',
497
+ v_files, v_report, p_from, p_to, v_asked;
498
+ end if;
499
+ end if;
500
+ end if;
501
+
502
+ -- 1. What the tax postings wrote on the ledger, in every box each of them
503
+ -- names. A line carries the box it is known by; the posting behind it
504
+ -- carries the whole list, which is one box for all but the handful of
505
+ -- forms that print a figure twice. No country rule here either: the
506
+ -- expansion is `unnest`, and which boxes there are is the pack's answer.
507
+ for r in
508
+ with lines as (
509
+ select l.declaration_box as lbox,
510
+ case when l.tax_line then 'tax' else 'base' end as lkind,
511
+ l.tax_id as ltax,
512
+ l.box_amount as lamount
513
+ from entry_lines l
514
+ join entries e on e.id = l.entry_id
515
+ where l.company_id = p_company_id
516
+ and e.state = 'posted'
517
+ and e.entry_date between p_from and p_to
518
+ and l.declaration_box is not null
519
+ ),
520
+ -- The posting that wrote the line: which form it is on, and every box it
521
+ -- prints in. A line with no posting behind it — an entry keyed by hand,
522
+ -- a tax a company wrote itself — is on this form and in the one box it
523
+ -- names, which is what the left join leaves.
524
+ sourced as (
525
+ select ln.lbox, ln.lkind, ln.lamount,
526
+ coalesce(p.boxes, array[ln.lbox]) as lboxes
527
+ from lines ln
528
+ left join lateral (
529
+ select min(tp.report_code) as report_code,
530
+ array_agg(distinct b.box) as boxes
531
+ from tax_postings tp
532
+ cross join lateral unnest(tp.declaration_boxes) as b(box)
533
+ where tp.tax_id = ln.ltax
534
+ and tp.declaration_box = ln.lbox
535
+ and tp.posting_type = ln.lkind::tax_posting_type
536
+ ) p on true
537
+ -- A box number belongs to one form. A line whose posting names
538
+ -- another form is not on this declaration; one that names none is
539
+ -- the single-return case every European company is in.
540
+ where v_report is null or coalesce(p.report_code, v_report) = v_report
541
+ ),
542
+ ledger as (
543
+ select x.box as lbox, s.lkind,
544
+ round_amount(sum(s.lamount), v_round) as lamount
545
+ from sourced s
546
+ cross join lateral unnest(s.lboxes) as x(box)
547
+ group by 1, 2
548
+ having round_amount(sum(s.lamount), v_round) <> 0
549
+ )
550
+ select g.lbox, g.lkind, g.lamount, b.name as lname,
551
+ b.sequence as lsequence, coalesce(b.hidden, false) as lhidden
552
+ from ledger g
553
+ left join tax_report_box_templates b
554
+ on b.country = v_in and b.report_code = v_report
555
+ and b.box = g.lbox and b.kind = g.lkind
556
+ order by coalesce(b.sequence, 2147483647), g.lbox, g.lkind
557
+ loop
558
+ v_values := v_values || jsonb_build_object(r.lbox || '|' || r.lkind, r.lamount);
559
+ v_rows := v_rows || jsonb_build_array(jsonb_build_object(
560
+ 'box', r.lbox, 'kind', r.lkind, 'amount', r.lamount, 'computed', false,
561
+ 'name', r.lname, 'sequence', r.lsequence, 'hidden', r.lhidden,
562
+ 'report_code', v_report));
563
+ end loop;
564
+
565
+ -- 2. The totals of the form, through the evaluator the statements use. A
566
+ -- return prints what it has, so a nil total is left out of the answer —
567
+ -- and kept in the working set, so a later total that names it reads a
568
+ -- zero rather than a gap.
569
+ if v_report is not null then
570
+ select coalesce(jsonb_agg(jsonb_build_object(
571
+ 'key', b.box || '|total', 'plus', to_jsonb(b.plus_boxes),
572
+ 'minus', to_jsonb(b.minus_boxes), 'floor_zero', b.floor_zero,
573
+ 'sequence', b.sequence
574
+ ) order by b.sequence, b.box), '[]'::jsonb)
575
+ into v_formulas
576
+ from tax_report_box_templates b
577
+ where b.country = v_in
578
+ and b.report_code = v_report
579
+ and b.kind = 'total'
580
+ and (b.valid_from is null or b.valid_from <= p_to)
581
+ and (b.valid_to is null or b.valid_to >= p_to);
582
+
583
+ v_totals := evaluate_totals(v_values, v_formulas, v_round, false);
584
+
585
+ for r in
586
+ select b.box as tbox, b.name as tname, b.sequence as tsequence, b.hidden as thidden
587
+ from tax_report_box_templates b
588
+ where b.country = v_in
589
+ and b.report_code = v_report
590
+ and b.kind = 'total'
591
+ and (b.valid_from is null or b.valid_from <= p_to)
592
+ and (b.valid_to is null or b.valid_to >= p_to)
593
+ and v_totals ? (b.box || '|total')
594
+ order by b.sequence, b.box
595
+ loop
596
+ v_rows := v_rows || jsonb_build_array(jsonb_build_object(
597
+ 'box', r.tbox, 'kind', 'total',
598
+ 'amount', (v_totals ->> (r.tbox || '|total'))::numeric, 'computed', true,
599
+ 'name', r.tname, 'sequence', r.tsequence, 'hidden', r.thidden,
600
+ 'report_code', v_report));
601
+ end loop;
602
+ end if;
603
+
604
+ return query
605
+ select (x ->> 'box')::text,
606
+ (x ->> 'kind')::text,
607
+ (x ->> 'amount')::numeric,
608
+ (x ->> 'computed')::boolean,
609
+ (x ->> 'name')::text,
610
+ (x ->> 'sequence')::integer,
611
+ (x ->> 'hidden')::boolean,
612
+ (x ->> 'report_code')::text
613
+ from jsonb_array_elements(v_rows) as x;
614
+ end;
615
+ $$;
616
+
617
+ comment on function vat_return(uuid, date, date, text) is
618
+ 'Declaration boxes for a period: summed from the ledger, then the totals of the country''s form worked out by evaluate_totals(), the same evaluator financial_statement() uses. Refuses a period the company does not file **this form** on, when it has recorded one for it. No country rule lives in this function.';
619
+
620
+ revoke execute on function vat_return(uuid, date, date, text) from public, anon;
621
+ grant execute on function vat_return(uuid, date, date, text) to authenticated, service_role;
622
+
623
+ -- ---------------------------------------------------------------------------
624
+ -- The recapitulative statement gains the same guard
625
+ -- ---------------------------------------------------------------------------
626
+ --
627
+ -- The statement now has somewhere to read its own cadence from, so it can
628
+ -- refuse a period the company does not file it on — with the same reticence as
629
+ -- the return, and one condition more.
630
+ --
631
+ -- **It has to be told which declaration it is.** `vat_return()` finds its form
632
+ -- from the country, because a periodic return is flagged as one. Nothing flags
633
+ -- a recapitulative statement, and no pack of this repository declares one: the
634
+ -- pack format carries a single `tax_report.json`, which is the periodic return.
635
+ -- So the caller names the form, and a caller that names none is asking for the
636
+ -- figures rather than filing — nothing is refused. That is the prudent half of
637
+ -- the same principle: refuse only where the refusal is certain.
638
+ --
639
+ -- The gap that leaves — a country files several declarations and a pack
640
+ -- declares one form — is written up in `docs/international.md` rather than
641
+ -- patched here. Widening the pack format to a list of forms is a change to
642
+ -- every pack and to `ekwo pack check`, and it is not what this migration is.
643
+ --
644
+ -- The rest of the function is `20260915181500` unchanged.
645
+
646
+ drop function if exists ec_sales_list(uuid, date, date);
647
+
648
+ create or replace function ec_sales_list(
649
+ p_company_id uuid,
650
+ p_from date,
651
+ p_to date,
652
+ p_report_code text default null
653
+ )
654
+ returns table (
655
+ vat_country char(2),
656
+ vat_number text,
657
+ nature text,
658
+ amount numeric,
659
+ currency_code char(3),
660
+ documents integer,
661
+ contact_ids uuid[],
662
+ contact_names text[],
663
+ issue text
664
+ )
665
+ language plpgsql
666
+ stable
667
+ as $$
668
+ declare
669
+ v_country char(2);
670
+ v_prefix char(2);
671
+ v_currency char(3);
672
+ v_round money_rounding;
673
+ v_files declaration_period;
674
+ v_accepts declaration_period[];
675
+ v_asked declaration_period;
676
+ begin
677
+ select c.fiscal_country, c.currency_code into v_country, v_currency
678
+ from companies c where c.id = p_company_id;
679
+ if not found then
680
+ raise exception 'unknown_company: %', p_company_id;
681
+ end if;
682
+ if not exists (select 1 from territories) then
683
+ raise exception 'no_territories: apply supabase/seed/00_territories.sql';
684
+ end if;
685
+
686
+ -- Which period this is, when the caller said which statement they are
687
+ -- filing. Five things have to hold: a form was named, it exists, the company
688
+ -- has recorded a cadence for it, the dates are themselves a whole cadence the
689
+ -- form is filed on, and the two differ. A monthly statement from a quarterly
690
+ -- filer is the ordinary case in three of the four countries read while this
691
+ -- was written, and nothing here touches it: the cadence read is the
692
+ -- statement's own.
693
+ if p_report_code is not null then
694
+ select t.periods into v_accepts
695
+ from tax_report_templates t
696
+ where t.code = p_report_code
697
+ and t.valid_from <= p_to
698
+ and (t.valid_to is null or t.valid_to >= p_to)
699
+ order by t.country
700
+ limit 1;
701
+ if v_accepts is null then
702
+ raise exception 'unknown_tax_report: % is not a declaration form in force on %',
703
+ p_report_code, p_to;
704
+ end if;
705
+ v_files := filing_period(p_company_id, p_report_code);
706
+ v_asked := declaration_period_of(p_from, p_to);
707
+ if v_files is not null
708
+ and v_asked is not null
709
+ and v_files = any(v_accepts)
710
+ and v_asked = any(v_accepts)
711
+ and v_asked <> v_files then
712
+ raise exception
713
+ 'wrong_declaration_period: this company files % on %; % to % is a %',
714
+ v_files, p_report_code, p_from, p_to, v_asked;
715
+ end if;
716
+ end if;
717
+
718
+ v_prefix := vat_prefix_of(v_country);
719
+ v_round := rounding_of(p_company_id);
720
+
721
+ return query
722
+ with supply as (
723
+ select d.contact_id,
724
+ ct.name as contact_name,
725
+ regexp_replace(t.treatment::text, '^intracom_', '') as nature,
726
+ upper(regexp_replace(coalesce(ct.vat_number, ''), '[^A-Za-z0-9]', '', 'g')) as vat_raw,
727
+ ct.country as contact_country,
728
+ e.entry_date,
729
+ e.document_id,
730
+ l.credit - l.debit as amount
731
+ from entry_lines l
732
+ join entries e on e.id = l.entry_id
733
+ join taxes t on t.id = l.tax_id
734
+ left join documents d on d.id = e.document_id
735
+ left join contacts ct on ct.id = d.contact_id
736
+ where l.company_id = p_company_id
737
+ and e.state = 'posted'
738
+ and e.entry_date between p_from and p_to
739
+ and not l.tax_line
740
+ and t.treatment::text like 'intracom!_%' escape '!'
741
+ and t.treatment::text not like 'intracom!_acquisition!_%' escape '!'
742
+ ),
743
+ keyed as (
744
+ select s.*,
745
+ vat_prefix_of(
746
+ case when s.vat_raw ~ '^[A-Z]{2}' then substring(s.vat_raw from 1 for 2)
747
+ when s.vat_raw = '' then null
748
+ else s.contact_country
749
+ end
750
+ ) as vat_country,
751
+ case when s.vat_raw = '' then null
752
+ when s.vat_raw ~ '^[A-Z]{2}' then substring(s.vat_raw from 3)
753
+ else s.vat_raw
754
+ end as vat_number
755
+ from supply s
756
+ ),
757
+ scoped as (
758
+ select k.*, eu_vat_scope_of(k.vat_country, k.entry_date) as scope
759
+ from keyed k
760
+ ),
761
+ judged as (
762
+ select s.*,
763
+ case
764
+ when s.contact_id is null then 'no_customer'
765
+ when s.vat_number is null or s.vat_number = '' then 'no_vat_number'
766
+ when s.vat_country is null then 'no_vat_country'
767
+ when s.vat_country = v_prefix then 'vat_country_is_the_company_country'
768
+ when s.scope = 'none' then 'vat_country_outside_the_union'
769
+ when s.scope <> 'full' and s.nature = 'services'
770
+ then 'vat_country_outside_the_union_for_this_supply'
771
+ end as issue
772
+ from scoped s
773
+ )
774
+ select j.vat_country::char(2),
775
+ j.vat_number,
776
+ j.nature,
777
+ round_amount(sum(j.amount), v_round),
778
+ v_currency,
779
+ count(distinct j.document_id)::integer,
780
+ array_agg(distinct j.contact_id) filter (where j.contact_id is not null),
781
+ array_agg(distinct j.contact_name) filter (where j.contact_name is not null),
782
+ j.issue
783
+ from judged j
784
+ group by j.vat_country, j.vat_number, j.nature, j.issue,
785
+ case when j.issue is not null then j.contact_id end
786
+ having round_amount(sum(j.amount), v_round) <> 0
787
+ order by (j.issue is not null), j.vat_country, j.vat_number, j.nature;
788
+ end;
789
+ $$;
790
+
791
+ comment on function ec_sales_list(uuid, date, date, text) is
792
+ 'The recapitulative statement of intra-Community supplies for a period: one line per customer VAT identification number and per nature — goods, services, and whatever the treatment vocabulary gains next — summed from the posted ledger in the company''s currency, credit notes deducted. The country of a line is the prefix the customer''s numbers carry, read from `territories`, so a Greek customer is listed under EL. A supply that cannot be declared comes back with the reason in `issue` rather than being left out. Name the form in p_report_code to have the period checked against the cadence this company files **that statement** on, which is not the cadence of its periodic return in any country read so far; name none and nothing is refused. No country rule lives in this function.';
793
+
794
+ revoke execute on function ec_sales_list(uuid, date, date, text) from public, anon;
795
+ grant execute on function ec_sales_list(uuid, date, date, text) to authenticated, service_role;