ekwo-os 0.7.0 → 0.9.0

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 (90) hide show
  1. package/README.md +10 -5
  2. package/dist/assets/expected-objects.json +132 -2
  3. package/dist/assets/migrations/20260922143000_a_version_anybody_signed_in_may_read.sql +52 -0
  4. package/dist/assets/migrations/20260922144500_a_balance_sheet_without_knowing_the_pack.sql +117 -0
  5. package/dist/assets/migrations/20260922160000_a_key_reaches_the_api.sql +265 -0
  6. package/dist/assets/migrations/20260922161500_the_version_answers_a_key_as_well.sql +42 -0
  7. package/dist/assets/migrations/20260922162500_reference_data_for_a_caller_this_installation_knows.sql +127 -0
  8. package/dist/assets/migrations/20260922171000_a_key_is_presented_in_a_read_only_transaction.sql +82 -0
  9. package/dist/assets/migrations/20260923093000_schema_version_0_8_0.sql +42 -0
  10. package/dist/assets/migrations/20260923110000_an_archive_survives_being_printed_again.sql +648 -0
  11. package/dist/assets/migrations/20260928100000_schema_version_0_9_0.sql +42 -0
  12. package/dist/assets/modules/assets/supabase/migrations/20260922162600_assets_reference_for_a_known_caller.sql +24 -0
  13. package/dist/assets/seed/00_currencies.sql +42 -1
  14. package/dist/assets/seed/00_territories.sql +1264 -1
  15. package/dist/assets/seed/100_pack_uy.sql +735 -0
  16. package/dist/assets/seed/101_pack_ec.sql +534 -0
  17. package/dist/assets/seed/102_pack_bo.sql +565 -0
  18. package/dist/assets/seed/103_pack_py.sql +576 -0
  19. package/dist/assets/seed/104_pack_do.sql +539 -0
  20. package/dist/assets/seed/105_pack_cr.sql +581 -0
  21. package/dist/assets/seed/106_pack_gt.sql +601 -0
  22. package/dist/assets/seed/107_pack_pa.sql +634 -0
  23. package/dist/assets/seed/108_pack_in.sql +890 -0
  24. package/dist/assets/seed/109_pack_cn.sql +775 -0
  25. package/dist/assets/seed/110_pack_ge.sql +536 -0
  26. package/dist/assets/seed/111_pack_md.sql +672 -0
  27. package/dist/assets/seed/112_pack_rs.sql +761 -0
  28. package/dist/assets/seed/113_pack_ba.sql +638 -0
  29. package/dist/assets/seed/114_pack_al.sql +734 -0
  30. package/dist/assets/seed/115_pack_mk.sql +591 -0
  31. package/dist/assets/seed/116_pack_me.sql +673 -0
  32. package/dist/assets/seed/117_pack_bh.sql +561 -0
  33. package/dist/assets/seed/118_pack_om.sql +606 -0
  34. package/dist/assets/seed/119_pack_gh.sql +622 -0
  35. package/dist/assets/seed/120_pack_tz.sql +583 -0
  36. package/dist/assets/seed/121_pack_ug.sql +582 -0
  37. package/dist/assets/seed/122_pack_rw.sql +588 -0
  38. package/dist/assets/seed/123_pack_xk.sql +567 -0
  39. package/dist/assets/seed/124_pack_kz.sql +600 -0
  40. package/dist/assets/seed/38_pack_id.sql +531 -0
  41. package/dist/assets/seed/39_pack_my.sql +625 -0
  42. package/dist/assets/seed/44_pack_ph.sql +553 -0
  43. package/dist/assets/seed/45_pack_ar.sql +587 -0
  44. package/dist/assets/seed/46_pack_cl.sql +746 -0
  45. package/dist/assets/seed/47_pack_co.sql +579 -0
  46. package/dist/assets/seed/48_pack_pe.sql +632 -0
  47. package/dist/assets/seed/49_pack_ng.sql +616 -0
  48. package/dist/assets/seed/57_pack_ua.sql +654 -0
  49. package/dist/assets/seed/58_pack_ca.sql +768 -0
  50. package/dist/assets/seed/61_pack_se.sql +784 -0
  51. package/dist/assets/seed/65_pack_dk.sql +595 -0
  52. package/dist/assets/seed/66_pack_no.sql +584 -0
  53. package/dist/assets/seed/67_pack_fi.sql +611 -0
  54. package/dist/assets/seed/68_pack_cz.sql +679 -0
  55. package/dist/assets/seed/69_pack_sk.sql +662 -0
  56. package/dist/assets/seed/70_pack_hu.sql +628 -0
  57. package/dist/assets/seed/72_pack_ro.sql +754 -0
  58. package/dist/assets/seed/73_pack_bg.sql +561 -0
  59. package/dist/assets/seed/74_pack_hr.sql +716 -0
  60. package/dist/assets/seed/75_pack_si.sql +656 -0
  61. package/dist/assets/seed/76_pack_gr.sql +597 -0
  62. package/dist/assets/seed/77_pack_lt.sql +611 -0
  63. package/dist/assets/seed/78_pack_lv.sql +564 -0
  64. package/dist/assets/seed/79_pack_cy.sql +685 -0
  65. package/dist/assets/seed/80_pack_mt.sql +833 -0
  66. package/dist/assets/seed/81_pack_is.sql +532 -0
  67. package/dist/assets/seed/82_pack_tr.sql +663 -0
  68. package/dist/assets/seed/83_pack_eg.sql +546 -0
  69. package/dist/assets/seed/84_pack_ma.sql +666 -0
  70. package/dist/assets/seed/85_pack_tn.sql +609 -0
  71. package/dist/assets/seed/86_pack_dz.sql +693 -0
  72. package/dist/assets/seed/87_pack_il.sql +568 -0
  73. package/dist/assets/seed/88_pack_za.sql +666 -0
  74. package/dist/assets/seed/89_pack_ke.sql +596 -0
  75. package/dist/cli.d.ts.map +1 -1
  76. package/dist/cli.js +8 -6
  77. package/dist/cli.js.map +1 -1
  78. package/dist/commands/import.d.ts +16 -0
  79. package/dist/commands/import.d.ts.map +1 -1
  80. package/dist/commands/import.js +4 -0
  81. package/dist/commands/import.js.map +1 -1
  82. package/dist/doctor.d.ts.map +1 -1
  83. package/dist/doctor.js +40 -0
  84. package/dist/doctor.js.map +1 -1
  85. package/dist/pack/describe.d.ts +1 -1
  86. package/dist/pack/upgrade.d.ts +1 -1
  87. package/dist/pack/upgrade.js +1 -1
  88. package/dist/schema.d.ts +1 -1
  89. package/dist/schema.js +1 -1
  90. package/package.json +37 -10
package/README.md CHANGED
@@ -65,9 +65,10 @@ either.
65
65
  three questions whose wrong answer is expensive: the country, the chart and
66
66
  the language.
67
67
 
68
- 4. **Sign in** to your project as that administrator and start booking. Until
69
- the Community web application lands, the interface is the REST API Supabase
70
- generates from the schema, or `psql`, or `@ekwo-ai/core`.
68
+ 4. **Sign in** to your project as that administrator and start booking — in
69
+ the web application, [Ekwo Cloud](https://cloud.ekwo.ai), which opens your
70
+ own instance, or through the REST API Supabase generates from the schema,
71
+ `psql` or `@ekwo-ai/core`.
71
72
 
72
73
  5. **Do the four things below**, while the dashboard is still open. The
73
74
  installer prints them at the end of a successful run, because three of them
@@ -323,7 +324,7 @@ instead if the account already exists, and no key is needed.
323
324
  | `ekwo reverse <entry>` | Undoes a posted entry keyed by hand, through `reverse_entry()`: its mirror, posted and matched against it. By id or by number; `--date` as for `cancel`. |
324
325
  | `ekwo payment record` | Money in or out, booked and matched. With `--doc`, against that document. |
325
326
  | `ekwo match <transaction> <document>` | A bank statement line pays a document, through `settle_from_statement()`. |
326
- | `ekwo import <source> <file>…` | Books kept elsewhere — `trial-balance`, `fec`, `journal-items`, `journal-report` — whole or not at all, through `import_books()`, with a correspondence of accounts and journals you save and give back; or a bank statement — `camt.053`, `coda`, `cfonb120` — as pending lines. `--dry-run` rehearses. See [taking over books](#taking-over-books-ekwo-import). |
327
+ | `ekwo import <source> <file>…` | Books kept elsewhere — `trial-balance`, `fec`, `journal-items`, `journal-report`, `transaction-journal`, `xaf` — whole or not at all, through `import_books()`, with a correspondence of accounts and journals you save and give back; or a bank statement — `camt.053`, `coda`, `cfonb120` — as pending lines. `--dry-run` rehearses. See [taking over books](#taking-over-books-ekwo-import). |
327
328
  | `ekwo doc list` / `show` | What exists, and with `--unpaid` what is posted and still owed. See [keeping books](#keeping-books). |
328
329
 
329
330
  There is no `eject`, because there is nothing to eject from. The schema is in
@@ -338,7 +339,7 @@ place, so the output reads the same in a file or a CI log — and takes `--json`
338
339
  for a program.
339
340
 
340
341
  `ekwo help --json` answers the list of commands and, in `usage`, the whole
341
- text of `--help` without colour: every flag and variable, for an assistant
342
+ text of `--help` without colour: every flag and variable, for an agent
342
343
  that reads one document rather than a terminal.
343
344
 
344
345
  Under `--json` the standard output is **one JSON document and nothing else**;
@@ -572,6 +573,8 @@ ekwo import fec 123456789FEC20251231.txt --mapping map.json --open-years
572
573
  ekwo import trial-balance balance.csv --opening-date 2026-01-01 --dry-run --save-mapping map.json
573
574
  ekwo import journal-items items.csv accounts.csv partners.csv --dry-run --save-mapping map.json
574
575
  ekwo import journal-report report.csv chart.csv --date-order dmy --dry-run --save-mapping map.json
576
+ ekwo import transaction-journal journal.csv account-list.csv --date-order mdy --dry-run --save-mapping map.json
577
+ ekwo import xaf books.xaf --open-years --dry-run --save-mapping map.json
575
578
  ekwo import camt.053 statement.xml # or coda, cfonb120: pending lines for `ekwo match`
576
579
  ```
577
580
 
@@ -581,6 +584,8 @@ ekwo import camt.053 statement.xml # or coda, cfonb120: pending
581
584
  | `fec` | A *fichier des écritures comptables*: the eighteen columns of the arrêté of 29 July 2013, tab or bar separated |
582
585
  | `journal-items` | The lines of every entry exported as CSV from the list view of an ERP whose ledger is a table of lines, with the chart of accounts and the partners exported beside it |
583
586
  | `journal-report` | A journal report or a general ledger detail, saved as CSV from a cloud service's spreadsheet export, with its Journal ID and Account Code columns, and the chart and the contacts beside it |
587
+ | `transaction-journal` | A transaction journal, saved as CSV from a spreadsheet export: each transaction a run of rows under one date, type and number, closed by its total; the list of accounts beside it |
588
+ | `xaf` | An XML Audit File Financial, version 3.2 or 4.0: the accounts, the parties, the opening balance — on the day the file gives it — and every transaction of a year, in one file |
584
589
  | `camt.053`, `coda`, `cfonb120` | A bank statement: its lines, pending, ready for `ekwo match` |
585
590
 
586
591
  Each reader is a brick of [`packages/formats/`](../formats/README.md), named
@@ -1,6 +1,6 @@
1
1
  {
2
- "schemaVersion": "0.7.0",
3
- "migration": "20260922124012",
2
+ "schemaVersion": "0.9.0",
3
+ "migration": "20260928100000",
4
4
  "socle": {
5
5
  "schema": "public",
6
6
  "tables": [
@@ -3743,6 +3743,10 @@
3743
3743
  "name": "amount_text_format",
3744
3744
  "arguments": "p_rounding money_rounding"
3745
3745
  },
3746
+ {
3747
+ "name": "api_key_company",
3748
+ "arguments": ""
3749
+ },
3746
3750
  {
3747
3751
  "name": "assert_may_close_year",
3748
3752
  "arguments": ""
@@ -3815,6 +3819,10 @@
3815
3819
  "name": "cancel_document",
3816
3820
  "arguments": "p_document_id uuid, p_date date"
3817
3821
  },
3822
+ {
3823
+ "name": "canonical_json",
3824
+ "arguments": "p_value jsonb"
3825
+ },
3818
3826
  {
3819
3827
  "name": "catch_up_journal_sequence",
3820
3828
  "arguments": "p_journal_id uuid, p_date date, p_number text"
@@ -3931,6 +3939,10 @@
3931
3939
  "name": "declaration_period_start",
3932
3940
  "arguments": "p_period declaration_period, p_day date"
3933
3941
  },
3942
+ {
3943
+ "name": "default_statement_code",
3944
+ "arguments": "p_company_id uuid, p_kind text, p_at date"
3945
+ },
3934
3946
  {
3935
3947
  "name": "disable_module",
3936
3948
  "arguments": "p_company_id uuid, p_code text"
@@ -3999,6 +4011,10 @@
3999
4011
  "name": "ec_sales_list",
4000
4012
  "arguments": "p_company_id uuid, p_from date, p_to date, p_report_code text"
4001
4013
  },
4014
+ {
4015
+ "name": "ekwo_pre_request",
4016
+ "arguments": ""
4017
+ },
4002
4018
  {
4003
4019
  "name": "ekwo_schema_version",
4004
4020
  "arguments": ""
@@ -4095,6 +4111,10 @@
4095
4111
  "name": "financial_statement",
4096
4112
  "arguments": "p_company_id uuid, p_statement_code text, p_from date, p_to date"
4097
4113
  },
4114
+ {
4115
+ "name": "financial_statement_of_kind",
4116
+ "arguments": "p_company_id uuid, p_kind text, p_from date, p_to date"
4117
+ },
4098
4118
  {
4099
4119
  "name": "fiscal_year_at",
4100
4120
  "arguments": "p_company_id uuid, p_date date"
@@ -4151,6 +4171,10 @@
4151
4171
  "name": "install_country_template",
4152
4172
  "arguments": "p_company_id uuid, p_country character, p_language character, p_chart_code text"
4153
4173
  },
4174
+ {
4175
+ "name": "installed_schema_version",
4176
+ "arguments": ""
4177
+ },
4154
4178
  {
4155
4179
  "name": "instance_has_no_admin",
4156
4180
  "arguments": ""
@@ -4183,6 +4207,10 @@
4183
4207
  "name": "is_instance_admin",
4184
4208
  "arguments": ""
4185
4209
  },
4210
+ {
4211
+ "name": "is_known_caller",
4212
+ "arguments": ""
4213
+ },
4186
4214
  {
4187
4215
  "name": "label_for",
4188
4216
  "arguments": "p_name text, p_i18n jsonb, p_languages text[]"
@@ -4315,6 +4343,10 @@
4315
4343
  "name": "prepare_filing",
4316
4344
  "arguments": "p_company_id uuid, p_from date, p_to date, p_report_code text"
4317
4345
  },
4346
+ {
4347
+ "name": "present_api_key",
4348
+ "arguments": "p_secret text"
4349
+ },
4318
4350
  {
4319
4351
  "name": "purge_audit_log",
4320
4352
  "arguments": "p_before date"
@@ -6817,6 +6849,19 @@
6817
6849
  "execute"
6818
6850
  ]
6819
6851
  },
6852
+ {
6853
+ "name": "api_key_company",
6854
+ "arguments": "",
6855
+ "anon": [
6856
+ "execute"
6857
+ ],
6858
+ "authenticated": [
6859
+ "execute"
6860
+ ],
6861
+ "service_role": [
6862
+ "execute"
6863
+ ]
6864
+ },
6820
6865
  {
6821
6866
  "name": "assert_may_close_year",
6822
6867
  "arguments": "",
@@ -6985,6 +7030,17 @@
6985
7030
  "execute"
6986
7031
  ]
6987
7032
  },
7033
+ {
7034
+ "name": "canonical_json",
7035
+ "arguments": "p_value jsonb",
7036
+ "anon": [],
7037
+ "authenticated": [
7038
+ "execute"
7039
+ ],
7040
+ "service_role": [
7041
+ "execute"
7042
+ ]
7043
+ },
6988
7044
  {
6989
7045
  "name": "catch_up_journal_sequence",
6990
7046
  "arguments": "p_journal_id uuid, p_date date, p_number text",
@@ -7281,6 +7337,17 @@
7281
7337
  "execute"
7282
7338
  ]
7283
7339
  },
7340
+ {
7341
+ "name": "default_statement_code",
7342
+ "arguments": "p_company_id uuid, p_kind text, p_at date",
7343
+ "anon": [],
7344
+ "authenticated": [
7345
+ "execute"
7346
+ ],
7347
+ "service_role": [
7348
+ "execute"
7349
+ ]
7350
+ },
7284
7351
  {
7285
7352
  "name": "disable_module",
7286
7353
  "arguments": "p_company_id uuid, p_code text",
@@ -7441,6 +7508,19 @@
7441
7508
  "execute"
7442
7509
  ]
7443
7510
  },
7511
+ {
7512
+ "name": "ekwo_pre_request",
7513
+ "arguments": "",
7514
+ "anon": [
7515
+ "execute"
7516
+ ],
7517
+ "authenticated": [
7518
+ "execute"
7519
+ ],
7520
+ "service_role": [
7521
+ "execute"
7522
+ ]
7523
+ },
7444
7524
  {
7445
7525
  "name": "ekwo_schema_version",
7446
7526
  "arguments": "",
@@ -7684,6 +7764,17 @@
7684
7764
  "execute"
7685
7765
  ]
7686
7766
  },
7767
+ {
7768
+ "name": "financial_statement_of_kind",
7769
+ "arguments": "p_company_id uuid, p_kind text, p_from date, p_to date",
7770
+ "anon": [],
7771
+ "authenticated": [
7772
+ "execute"
7773
+ ],
7774
+ "service_role": [
7775
+ "execute"
7776
+ ]
7777
+ },
7687
7778
  {
7688
7779
  "name": "fiscal_year_at",
7689
7780
  "arguments": "p_company_id uuid, p_date date",
@@ -7834,6 +7925,19 @@
7834
7925
  "execute"
7835
7926
  ]
7836
7927
  },
7928
+ {
7929
+ "name": "installed_schema_version",
7930
+ "arguments": "",
7931
+ "anon": [
7932
+ "execute"
7933
+ ],
7934
+ "authenticated": [
7935
+ "execute"
7936
+ ],
7937
+ "service_role": [
7938
+ "execute"
7939
+ ]
7940
+ },
7837
7941
  {
7838
7942
  "name": "instance_has_no_admin",
7839
7943
  "arguments": "",
@@ -7932,6 +8036,19 @@
7932
8036
  "execute"
7933
8037
  ]
7934
8038
  },
8039
+ {
8040
+ "name": "is_known_caller",
8041
+ "arguments": "",
8042
+ "anon": [
8043
+ "execute"
8044
+ ],
8045
+ "authenticated": [
8046
+ "execute"
8047
+ ],
8048
+ "service_role": [
8049
+ "execute"
8050
+ ]
8051
+ },
7935
8052
  {
7936
8053
  "name": "label_for",
7937
8054
  "arguments": "p_name text, p_i18n jsonb, p_languages text[]",
@@ -8294,6 +8411,19 @@
8294
8411
  "execute"
8295
8412
  ]
8296
8413
  },
8414
+ {
8415
+ "name": "present_api_key",
8416
+ "arguments": "p_secret text",
8417
+ "anon": [
8418
+ "execute"
8419
+ ],
8420
+ "authenticated": [
8421
+ "execute"
8422
+ ],
8423
+ "service_role": [
8424
+ "execute"
8425
+ ]
8426
+ },
8297
8427
  {
8298
8428
  "name": "purge_audit_log",
8299
8429
  "arguments": "p_before date",
@@ -0,0 +1,52 @@
1
+ -- Ekwo OS — the version of the schema, for somebody who is on no company yet.
2
+ --
3
+ -- A client of this schema has one question to ask before it draws anything:
4
+ -- is this installation new enough for the code I ship? The answer is
5
+ -- `instance.schema_version`, and the policy on that table admits members of
6
+ -- at least one company and instance administrators — for the reason written
7
+ -- in `20260911140000`: self sign-up is on by default on a Supabase project,
8
+ -- so a signed-in stranger is ordinary and the organisation's name, its country
9
+ -- and the day it was installed are not theirs to read.
10
+ --
11
+ -- A freshly created account is exactly that stranger. It signs in, reads no
12
+ -- row, and the client cannot tell an instance behind its schema from one
13
+ -- whose row it was never allowed to see. Both are silence, and the two call
14
+ -- for opposite behaviour: print `ekwo migrate`, or say nothing and carry on.
15
+ -- A web application meets this on its first screen, with an account whose
16
+ -- invitation has not arrived yet.
17
+ --
18
+ -- So the version leaves the table and becomes a question of its own.
19
+ -- `installed_schema_version()` is definer — it reads the singleton row past a
20
+ -- policy that was written about the rest of it — and it returns two fields
21
+ -- and no others: the version the migrations wrote, and the edition, which is
22
+ -- `community` or `cloud` and gates nothing anywhere in this repository.
23
+ -- Whoever the caller is, what they learn is what release this software is at.
24
+ -- Nothing about the customer comes with it.
25
+ --
26
+ -- **`anon` does not get it**, and the exception is not worth making. Decision
27
+ -- 0002 keeps the anonymous surface closed, and the list of what `anon` may
28
+ -- execute is the policy helpers, which answer about `auth.uid()` and therefore
29
+ -- say nothing, plus `shared_document()`, which somebody was handed a link to.
30
+ -- The version is not in that shape: nobody asks it before signing in — the
31
+ -- client asks it *after*, which is the moment the old answer went missing —
32
+ -- and granting it would hand an unauthenticated scanner the exact release an
33
+ -- installation runs, for a convenience nothing needs. A reader who needs it
34
+ -- signs in first.
35
+
36
+ create or replace function installed_schema_version()
37
+ returns table (schema_version text, edition text)
38
+ language sql
39
+ stable
40
+ security definer
41
+ set search_path = public, pg_temp
42
+ as $$
43
+ select i.schema_version, i.edition::text from instance i where i.id = 1;
44
+ $$;
45
+
46
+ comment on function installed_schema_version() is
47
+ 'The schema version this installation runs, and its edition. Definer, so a signed-in account that is on no company yet can still tell an instance behind its schema from one it may not read the row of — which is the same silence otherwise. Nothing else of the instance row comes with it.';
48
+
49
+ revoke execute on all functions in schema public from public;
50
+
51
+ revoke execute on function installed_schema_version() from public, anon;
52
+ grant execute on function installed_schema_version() to authenticated, service_role;
@@ -0,0 +1,117 @@
1
+ -- Ekwo OS — asking for a balance sheet without knowing which pack answers.
2
+ --
3
+ -- `financial_statement()` takes `p_statement_code`, and a code belongs to a
4
+ -- country pack: `BE-BNB-ABBR-BS`, `FR-2050`, the generic `IFRS-SME-BS`. A
5
+ -- client that knows which company it is looking at does not know that string,
6
+ -- and it must not learn it — writing `FR-2050` into an application is writing
7
+ -- a country into code, one screen at a time, which is the thing
8
+ -- `docs/decisions/0025` exists to stop.
9
+ --
10
+ -- `available_statements(company, at)` has been the way out since the day the
11
+ -- statements landed: it gives every scheme that company may ask for, with its
12
+ -- kind and whether its chart declares it. What was missing is the step after
13
+ -- it — going from *a balance sheet* to the one statement that answers, and
14
+ -- doing it the same way twice. Every caller that wanted one wrote its own
15
+ -- choice: take the first row, prefer `is_default`, prefer the country over the
16
+ -- generic. Three readers, three orders, and the day a pack carries two balance
17
+ -- sheets they disagree about which is the company's.
18
+ --
19
+ -- Two functions, and no new rule about accounting:
20
+ --
21
+ -- `default_statement_code(company, kind, at)` — the one scheme of that kind
22
+ -- this company reports on. The order is the pack's own preferences,
23
+ -- stated once: a statement the company's chart declares
24
+ -- (`chart_templates.statements`, which is what `is_default` marks) comes
25
+ -- first; then one of the company's country, because a national scheme is
26
+ -- what an administration reads; then the generic framework, which fits
27
+ -- any chart and is the fallback and never the first answer. `code`
28
+ -- settles a tie, so the answer does not depend on the order rows were
29
+ -- loaded in.
30
+ --
31
+ -- `financial_statement_of_kind(company, kind, from, to)` — the same table
32
+ -- `financial_statement()` returns, resolved that way, with the code it
33
+ -- chose in the first column. The code comes back because a reader has to
34
+ -- be able to say which scheme it is printing, and because the next call
35
+ -- — `unmapped_accounts()`, an XBRL export — takes the code.
36
+ --
37
+ -- The kind is the one the schema already has: `balance_sheet`,
38
+ -- `income_statement`, `allocation`, `cash_flow` — the vocabulary of
39
+ -- `statement_templates.kind`, not a new one. A company with no statement of
40
+ -- the kind asked for is told so, and told what it does have; a company that
41
+ -- installed no pack at all has the generic framework and so has both of the
42
+ -- two that are produced.
43
+ --
44
+ -- The date the validity is read at is `p_to`: a statement is the one that was
45
+ -- in force at the end of the period being reported, not the one in force
46
+ -- today. A scheme replaced last month still prints last year.
47
+
48
+ create or replace function default_statement_code(
49
+ p_company_id uuid,
50
+ p_kind text,
51
+ p_at date default null
52
+ )
53
+ returns text
54
+ language sql
55
+ stable
56
+ as $$
57
+ select s.code
58
+ from available_statements(p_company_id, p_at) s
59
+ where s.kind = p_kind
60
+ order by s.is_default desc, (s.country is null), s.code
61
+ limit 1;
62
+ $$;
63
+
64
+ comment on function default_statement_code(uuid, text, date) is
65
+ 'The statement of a kind this company reports on: one its chart declares, else one of its country, else the generic framework. Null when it has none of that kind. The one place that choice is made, so two readers cannot disagree about which balance sheet is the company''s.';
66
+
67
+ create or replace function financial_statement_of_kind(
68
+ p_company_id uuid,
69
+ p_kind text,
70
+ p_from date,
71
+ p_to date
72
+ )
73
+ returns table (
74
+ statement_code text,
75
+ line_code text,
76
+ parent_code text,
77
+ name text,
78
+ sequence integer,
79
+ is_total boolean,
80
+ amount numeric,
81
+ xbrl_element text
82
+ )
83
+ language plpgsql
84
+ stable
85
+ as $$
86
+ declare
87
+ v_code text;
88
+ v_kinds text;
89
+ begin
90
+ if not exists (select 1 from companies c where c.id = p_company_id) then
91
+ raise exception 'unknown_company: %', p_company_id;
92
+ end if;
93
+
94
+ v_code := default_statement_code(p_company_id, p_kind, p_to);
95
+ if v_code is null then
96
+ select string_agg(distinct s.kind, ', ' order by s.kind) into v_kinds
97
+ from available_statements(p_company_id, p_to) s;
98
+ raise exception 'unknown_statement: this company reports no % — it reports %. available_statements() names each one by code.',
99
+ p_kind, coalesce(v_kinds, 'nothing');
100
+ end if;
101
+
102
+ return query
103
+ select v_code, f.line_code, f.parent_code, f.name, f.sequence,
104
+ f.is_total, f.amount, f.xbrl_element
105
+ from financial_statement(p_company_id, v_code, p_from, p_to) f;
106
+ end;
107
+ $$;
108
+
109
+ comment on function financial_statement_of_kind(uuid, text, date, date) is
110
+ 'One financial statement of a company by kind — balance_sheet, income_statement, allocation, cash_flow — for a caller that does not know which scheme its country pack carries. Resolves through default_statement_code() at the end date of the period, then answers as financial_statement() does, with the code it chose in the first column.';
111
+
112
+ revoke execute on all functions in schema public from public;
113
+
114
+ revoke execute on function default_statement_code(uuid, text, date) from public, anon;
115
+ grant execute on function default_statement_code(uuid, text, date) to authenticated, service_role;
116
+ revoke execute on function financial_statement_of_kind(uuid, text, date, date) from public, anon;
117
+ grant execute on function financial_statement_of_kind(uuid, text, date, date) to authenticated, service_role;