feat(reference-data): legal-document + company-info modules + country.displayOrder#3734
Open
Blume1977 wants to merge 2 commits into
Open
feat(reference-data): legal-document + company-info modules + country.displayOrder#3734Blume1977 wants to merge 2 commits into
Blume1977 wants to merge 2 commits into
Conversation
|
2 tasks
Blume1977
added a commit
to Blume1977/realunit-app
that referenced
this pull request
May 21, 2026
…o, country.displayOrder Wave 4.4 of the API-as-Decision-Authority audit, companion to API PR DFXswiss/api#3734. Retires three hardcoded reference-data sites by consuming the new backend endpoints. Services + DTOs --------------- - `DfxLegalDocumentService` — calls `GET /v1/legal-document` with optional `?type=` and `?language=` filters, caches per query key. - `DfxCompanyInfoService` — calls `GET /v1/company-info/:brand`, caches per brand (case-insensitive). - DTO mirrors: `DfxLegalDocumentDto`, `DfxCompanyInfoDto` (with nested `DfxCompanyInfoAddressDto`). - `DfxCountryDto` + `Country` domain model gain `displayOrder` (default 999); pre-PR backends keep their alphabetic order. DI -- - Both services registered as lazy singletons in `lib/setup/di.dart`. Consumers --------- - `country_field.dart` — drops the hardcoded `priorityCountries = ['CH','DE','IT','FR']` and trusts the order the backend ships (sorted by `displayOrder` then `name` server-side). - `settings_contact_page.dart` — every "phone / email / website / imprint" tile is rebuilt from `SettingsContactState.companyInfo`. If a field is null, the tile is omitted. The imprint block reads `info.name` + the assembled `address` lines instead of the hardcoded `'RealUnit Schweiz AG'` / `'Schochenmühlestrasse 6\n6340 Baar, Schweiz'` block. - `SettingsContactCubit` now takes a second argument (`DfxCompanyInfoService`), loads both `/v2/user` and `/v1/company-info/RealUnit` in parallel, and exposes `companyInfo: DfxCompanyInfoDto?` on the Success state. Tests ----- - `dfx_legal_document_service_test.dart` — covers happy path, filter query parameters, cache reuse, different-filter cache misses, language-null pass-through, and the 500 error path. - `dfx_company_info_service_test.dart` — happy path with full payload, case-insensitive cache hit, payload without an address block, and the 404 error path. - `settings_contact_cubit_test.dart` — extends every fixture with a mock `DfxCompanyInfoService`, adds explicit cases for the company-info failure path and the multi-call setup. Verification ------------ - `flutter analyze` — clean - `flutter test` — **1426 / 1426 passing** Closes V14 (country priority), V17 (registration-agreement URLs), V18 (company contact info) on the realunit-app audit.
….displayOrder Wave 4 of the realunit-app API-as-Decision-Authority plan (`DFXswiss/realunit-app:docs/api-authority-plan.md`). Three additions that retire the last three reference-data items the app used to ship hardcoded. LegalDocument module -------------------- New module `src/shared/models/legal-document/` (entity + repository + service + controller + DTO + mapper). `GET /v1/legal-document` accepts `?type=` and `?language=` filters and returns the enabled documents the backend currently owns. The entity carries `type` (enum), `language` (ISO 639-1, lowercase, nullable for language-agnostic), `version`, `url`, and `enabled`. A partial unique index enforces `(type, language)` uniqueness only across enabled rows so historical versions can stay in the table. CompanyInfo module ------------------ New module `src/shared/models/company-info/` with the same shape. `GET /v1/company-info/:brand` returns one record per brand (`RealUnit`, `DFX`, …) so a single backend can serve multiple white-labeled apps from one endpoint. Country.displayOrder -------------------- New column on the existing `country` table, default 999, mapped to `CountryDto.displayOrder`. `CountryService.getAllCountry` now sorts by `displayOrder` ascending with `name` as the tiebreaker — the realunit-app's country picker can drop its `priorityCountries = ['CH','DE','IT','FR']` constant and consume the backend-tagged order. Migration --------- `migration/1779370800171-AddLegalDocumentAndCompanyInfo.js` (TypeORM schema migration, follows the pattern of `AddSupportNode`): 1. Creates the `legal_document` and `company_info` tables. 2. Adds the `displayOrder` column to `country` with default 999. 3. Seeds the priority order CH=1, DE=2, IT=3, FR=4 to match the realunit-app's previous hardcoded list. 4. Seeds the legal-document URLs and the RealUnit company-info row from the app's hardcoded data (`legal_documents_config.dart:69-122`, `settings_contact_page.dart:82-134`). `down` reverses every step. Tests ----- - `legal-document.service.spec.ts` — covers the four query branches: no-filter, type-only filter, type+language filter, empty result. - `company-info.service.spec.ts` — happy path + 404 on unknown brand. - `country.service.spec.ts` — verifies the `getAllCountry` sort order including the `displayOrder`-then-name tiebreaker, and that the cached repository array is not mutated. Backwards compatibility ----------------------- All additions are additive. Old clients that don't query the new endpoints see no change; the `displayOrder` column has a sensible default and old clients ignore the new `CountryDto.displayOrder` field. Local verification ------------------ - `npm run type-check` — clean - `npm run lint` — clean - `npm test` — **946 / 946 passing** (+8 new tests for the three service spec files; 938 baseline preserved) Closes V14 (country priority), V17 (legal documents), V18 (company contact info) on the realunit-app audit.
f8d2832 to
0b8f822
Compare
TaprootFreak
pushed a commit
to Blume1977/realunit-app
that referenced
this pull request
May 21, 2026
…o, country.displayOrder Wave 4.4 of the API-as-Decision-Authority audit, companion to API PR DFXswiss/api#3734. Retires three hardcoded reference-data sites by consuming the new backend endpoints. Services + DTOs --------------- - `DfxLegalDocumentService` — calls `GET /v1/legal-document` with optional `?type=` and `?language=` filters, caches per query key. - `DfxCompanyInfoService` — calls `GET /v1/company-info/:brand`, caches per brand (case-insensitive). - DTO mirrors: `DfxLegalDocumentDto`, `DfxCompanyInfoDto` (with nested `DfxCompanyInfoAddressDto`). - `DfxCountryDto` + `Country` domain model gain `displayOrder` (default 999); pre-PR backends keep their alphabetic order. DI -- - Both services registered as lazy singletons in `lib/setup/di.dart`. Consumers --------- - `country_field.dart` — drops the hardcoded `priorityCountries = ['CH','DE','IT','FR']` and trusts the order the backend ships (sorted by `displayOrder` then `name` server-side). - `settings_contact_page.dart` — every "phone / email / website / imprint" tile is rebuilt from `SettingsContactState.companyInfo`. If a field is null, the tile is omitted. The imprint block reads `info.name` + the assembled `address` lines instead of the hardcoded `'RealUnit Schweiz AG'` / `'Schochenmühlestrasse 6\n6340 Baar, Schweiz'` block. - `SettingsContactCubit` now takes a second argument (`DfxCompanyInfoService`), loads both `/v2/user` and `/v1/company-info/RealUnit` in parallel, and exposes `companyInfo: DfxCompanyInfoDto?` on the Success state. Tests ----- - `dfx_legal_document_service_test.dart` — covers happy path, filter query parameters, cache reuse, different-filter cache misses, language-null pass-through, and the 500 error path. - `dfx_company_info_service_test.dart` — happy path with full payload, case-insensitive cache hit, payload without an address block, and the 404 error path. - `settings_contact_cubit_test.dart` — extends every fixture with a mock `DfxCompanyInfoService`, adds explicit cases for the company-info failure path and the multi-call setup. Verification ------------ - `flutter analyze` — clean - `flutter test` — **1426 / 1426 passing** Closes V14 (country priority), V17 (registration-agreement URLs), V18 (company contact info) on the realunit-app audit.
TaprootFreak
pushed a commit
to Blume1977/realunit-app
that referenced
this pull request
May 21, 2026
…o, country.displayOrder Wave 4.4 of the API-as-Decision-Authority audit, companion to API PR DFXswiss/api#3734. Retires three hardcoded reference-data sites by consuming the new backend endpoints. Services + DTOs --------------- - `DfxLegalDocumentService` — calls `GET /v1/legal-document` with optional `?type=` and `?language=` filters, caches per query key. - `DfxCompanyInfoService` — calls `GET /v1/company-info/:brand`, caches per brand (case-insensitive). - DTO mirrors: `DfxLegalDocumentDto`, `DfxCompanyInfoDto` (with nested `DfxCompanyInfoAddressDto`). - `DfxCountryDto` + `Country` domain model gain `displayOrder` (default 999); pre-PR backends keep their alphabetic order. DI -- - Both services registered as lazy singletons in `lib/setup/di.dart`. Consumers --------- - `country_field.dart` — drops the hardcoded `priorityCountries = ['CH','DE','IT','FR']` and trusts the order the backend ships (sorted by `displayOrder` then `name` server-side). - `settings_contact_page.dart` — every "phone / email / website / imprint" tile is rebuilt from `SettingsContactState.companyInfo`. If a field is null, the tile is omitted. The imprint block reads `info.name` + the assembled `address` lines instead of the hardcoded `'RealUnit Schweiz AG'` / `'Schochenmühlestrasse 6\n6340 Baar, Schweiz'` block. - `SettingsContactCubit` now takes a second argument (`DfxCompanyInfoService`), loads both `/v2/user` and `/v1/company-info/RealUnit` in parallel, and exposes `companyInfo: DfxCompanyInfoDto?` on the Success state. Tests ----- - `dfx_legal_document_service_test.dart` — covers happy path, filter query parameters, cache reuse, different-filter cache misses, language-null pass-through, and the 500 error path. - `dfx_company_info_service_test.dart` — happy path with full payload, case-insensitive cache hit, payload without an address block, and the 404 error path. - `settings_contact_cubit_test.dart` — extends every fixture with a mock `DfxCompanyInfoService`, adds explicit cases for the company-info failure path and the multi-call setup. Verification ------------ - `flutter analyze` — clean - `flutter test` — **1426 / 1426 passing** Closes V14 (country priority), V17 (registration-agreement URLs), V18 (company contact info) on the realunit-app audit.
TaprootFreak
added a commit
to DFXswiss/realunit-app
that referenced
this pull request
May 21, 2026
…o, country.displayOrder (#499) ## Summary Wave 4.4 of the API-as-Decision-Authority audit ([`docs/api-authority-plan.md`](docs/api-authority-plan.md)). Companion to API PR [DFXswiss/api#3734](DFXswiss/api#3734). Retires the last hardcoded reference-data sites by consuming the three new backend endpoints. ## Changes ### Services + DTOs - `DfxLegalDocumentService` — calls `GET /v1/legal-document` with optional `?type=` and `?language=` filters, caches per query key (`${type ?? '*'}:${language ?? '*'}`). - `DfxCompanyInfoService` — calls `GET /v1/company-info/:brand`, caches per brand (case-insensitive). - DTO mirrors: `DfxLegalDocumentDto`, `DfxCompanyInfoDto` (with nested `DfxCompanyInfoAddressDto`). - `DfxCountryDto` + `Country` domain model gain `displayOrder` (default 999); pre-PR backends keep alphabetic order. ### Consumers - **`country_field.dart`** — drops the hardcoded `priorityCountries = ['CH','DE','IT','FR']` and trusts the order the backend ships (sorted by `displayOrder` then `name` server-side). - **`settings_contact_page.dart`** — every "phone / email / website / imprint" tile is rebuilt from `SettingsContactState.companyInfo`. If a field is null, the tile is omitted. The imprint block reads `info.name` + the assembled `address` lines instead of the hardcoded `'RealUnit Schweiz AG'` / `'Schochenmühlestrasse 6\n6340 Baar, Schweiz'` block. - **`SettingsContactCubit`** now takes a second argument (`DfxCompanyInfoService`), loads both `/v2/user` and `/v1/company-info/RealUnit` **in parallel**, and exposes `companyInfo: DfxCompanyInfoDto?` on the Success state. A company-info failure no longer kills the support tile — the cubit degrades gracefully and surfaces the user data anyway. - **`legal_document_page.dart`** — the hardcoded `_registrationAgreementPdfUrls` map is **gone entirely** (no last-resort fallback). The page renders a 3-state UI: - **loading** (spinner + localized `pdfLoading` label), - **available** (the inline PDF viewer with the URL returned by `/v1/legal-document`), - **unavailable** (tap-to-retry surface with localized `pdfNotAvailable` label) when the API returns no document for any of the candidate languages. The language picker is deterministic: requested → `en` → `de` → first available, no random order. ### i18n - New keys `pdfLoading`, `pdfNotAvailable` in both `strings_en.arb` and `strings_de.arb`. ## Tests - `dfx_legal_document_service_test.dart` — happy path, filter query parameters, cache reuse, different-filter cache misses, language-null pass-through, 500 error path. - `dfx_company_info_service_test.dart` — happy path with full payload, case-insensitive cache hit, payload without an address block, 404 error path. - `settings_contact_cubit_test.dart` — extends every fixture with a mock `DfxCompanyInfoService`, adds explicit cases for the company-info failure path and the multi-call setup. - `legal_document_page_test.dart` / cubit tests — cover the 3-state UI (loading / available / unavailable + retry) and the deterministic language fallback chain. ## Verification - [x] `flutter analyze` — clean - [x] `flutter test` — **1450 / 1450 passing** ## Closes (audit, P2) - **V14** — country priority (`country_field.dart:65-79`) - **V17** — registration-agreement URLs (`legal_documents_config.dart:69-122` — hardcoded map fully removed; the static WebDocumentConfig links to realunit.de/.ch downloads pages remain as accepted boundary cases below) - **V18** — company contact info (`settings_contact_page.dart:82-134`) ## Dependency Requires API PR DFXswiss/api#3734 to be merged + deployed first. The DTOs default to safe values (`displayOrder = 999`, missing fields tolerated), so against an older API the country picker still renders alphabetically and contact tiles surface no info — the app degrades gracefully rather than crashing. The PDF page falls through to its "unavailable" state instead of showing a stale hardcoded URL. ## Boundary cases left as documented exceptions - The five WebDocumentConfig URLs in `legal_documents_config.dart` (eu_securities prospectus pages, articles of association, investment regulations) point to anchors on realunit.de/.ch download pages, not file URLs. They are RealUnit-brand static content managed outside the API by RealUnit's marketing/legal team; lifting them into a generic `LegalDocument` table would creep schema. Accept as P3. - `assetBaseName: 'privacy_policy'` loads a local bundled asset, not a remote URL — local asset, not an API concern. --------- Co-authored-by: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wave 4 of the realunit-app API-as-Decision-Authority plan (
DFXswiss/realunit-app:docs/api-authority-plan.md). Three additions that retire the last reference-data items the app used to ship hardcoded.What changes
LegalDocumentmoduleNew module
src/shared/models/legal-document/(entity + repository + service + controller + DTO + mapper).GET /v1/legal-documentaccepts?type=and?language=filters and returns the enabled documents the backend owns.type(enum),language(ISO 639-1, lowercase, nullable for language-agnostic),version,url,enabled.(type, language)uniqueness only across enabled rows so historical versions can stay in the table.CompanyInfomoduleNew module
src/shared/models/company-info/with the same shape.GET /v1/company-info/:brandreturns one record per brand (RealUnit,DFX, …) so a single backend can serve multiple white-labeled apps from one endpoint.Country.displayOrderNew column on the existing
countrytable, default999, mapped toCountryDto.displayOrder.CountryService.getAllCountrynow sorts bydisplayOrderascending withnameas the tiebreaker — the realunit-app's country picker can drop itspriorityCountries = ['CH','DE','IT','FR']constant and consume the backend-tagged order.Migration
migration/1779370800171-AddLegalDocumentAndCompanyInfo.js(TypeORM schema migration, follows theAddSupportNodepattern):legal_documentandcompany_infotables.displayOrdercolumn tocountrywith default999.legal_documents_config.dart:69-122,settings_contact_page.dart:82-134).downreverses every step.Tests
legal-document.service.spec.ts— covers the four query branches: no-filter, type-only filter, type+language filter, empty result.company-info.service.spec.ts— happy path + 404 on unknown brand.country.service.spec.ts— verifies thegetAllCountrysort order including thedisplayOrder-then-nametiebreaker, and that the cached repository array is not mutated.Backwards compatibility
All additions are additive:
displayOrdercolumn has a sensible default; old clients ignore the newCountryDto.displayOrderfield.Local verification
npm run type-check— cleannpm run lint— cleannpm test— 946 / 946 passing (+8 new tests)Closes (audit, P2)
country_field.dart:65-79)legal_documents_config.dart:69-122)settings_contact_page.dart:82-134)Manual test plan (DEV)
GET /v1/legal-document?type=RegistrationAgreement&language=dereturns a single enabled document with the seededrealunit.de/agreement-de.pdfURL.GET /v1/legal-document?type=DfxTermsreturns the language-agnostic DFX terms entry.GET /v1/company-info/RealUnitreturns the RealUnit Schweiz AG seed.GET /v1/countryreturns CH/DE/IT/FR in that order at the top, then everything else alphabetically.Companion app PR
/v1/legal-document,/v1/company-info, and the newCountry.displayOrderfield; drops the hardcoded RealUnit company info,priorityCountries, and legacy WebDocumentConfig hardcodes. Closes V14, V17, V18 of the audit. Must merge after this PR is deployed so the app can rely on the new endpoints.