Skip to content

feat(subiekt): payment method / bank account / cash register per invoice (#1324) - #1335

Merged
norbert-kulus-blockydevs merged 1 commit into
mainfrom
1324-subiekt-bank-account-plan
Jul 6, 2026
Merged

feat(subiekt): payment method / bank account / cash register per invoice (#1324)#1335
norbert-kulus-blockydevs merged 1 commit into
mainfrom
1324-subiekt-bank-account-plan

Conversation

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator

Summary

Adds payment method / bank account / cash register (Stanowisko Kasowe) per invoice for the Subiekt nexo invoicing integration, mirroring the inFakt pattern (#1303/#1308) and reusing the existing core capabilities BankAccountsReader / BankAccountDefaultSetter (no new core ports).

  • Bridge client (libs/integrations/subiekt/src/bridge/**): listBankAccounts / setDefaultBankAccount / listCashRegisters.
  • Connection config (SubiektConnectionConfig + DTO + factory): defaultPaymentMethod, bankAccountId, defaultStanowiskoKasoweId.
  • Adapter (subiekt-invoicing.adapter.ts): implements BankAccountsReader / BankAccountDefaultSetter; owner-aware listBankAccountsWithOwner + listCashRegisters; stamps payment / bank-account / cash-register fields onto issueInvoice.
  • API (apps/api/src/integrations/http/subiekt.controller.ts): owner-aware GET .../bank-accounts + GET .../cash-registers.
  • FE (apps/web/src/plugins/subiekt/components/subiekt-structured-section.tsx + features/connections hooks/types): payment-method select, owner-grouped bank-account picker with a multi-payer warning, and a Stanowisko Kasowe picker with a Centrala help line. The Oddział axis is intentionally cut - it is bound read-only to the bridge's Sfera session and cannot be overridden per invoice (decision 8b).

⚠️ Draft - depends on #1310 (must merge first)

This branch is stacked on the inFakt prerequisite chain. #1300 and #1309 are already merged to main; #1310 (bank-account picker core capabilities) is still open and #1324 reuses exactly those capabilities.

Do not merge until #1310 is merged to main. The current diff is inflated because #1310 (and the surrounding inFakt/KSeF prereq work) are not yet on main, so their files show up here. Once #1310 merges, this branch will be rebased onto main and the diff will collapse to the Subiekt-only delta listed above. Please review only the libs/integrations/subiekt/**, apps/api/src/integrations/http/subiekt.controller.ts, and apps/web/src/plugins/subiekt/** + features/connections Subiekt additions here; the inFakt / KSeF churn belongs to the prereq PRs.

Verification

  • Per-package: subiekt 216 tests, api subiekt controller 6/6, web suite green; web type-check clean after integrating feat(infakt): bank-account picker with live inFakt default sync (#1303 follow-up) #1310.
  • Live E2E against a real Nexo_Demo_1 Sfera install, both directly through the bridge and full-stack through OpenLinker (Presta-sourced order -> OL -> Subiekt adapter -> bridge -> Sfera): 8 invoices across all payment configs (cash / transfer + bank account / two distinct Stanowiska Kasowe), each confirmed by SQL read-back on the real document. Config-driven, no caching; explicit payment selection correctly requires a faktura (a paragon is rejected 422).

Operator docs (setup guide + tutorial) for this feature are in PR #1284.

Closes #1324

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

E2E verification - confirmed working (post-merge)

I re-ran a full end-to-end test on the merged code (this branch's merge of #1310 + main, commit 296eafae) and confirm the feature works: OpenLinker issues Subiekt invoices for PrestaShop-sourced orders across payment variants, and each selection lands correctly on the real Subiekt/Sfera document.

Post-merge checks

Live run against a real Nexo_Demo_1 Sfera install - 3 new invoices issued through OpenLinker (POST /v1/invoices -> Subiekt adapter -> bridge -> Sfera) for Presta-sourced orders, one per payment config, then read back straight from the Dokumenty table:

Variant Invoice Sfera read-back (form / cash / deferred / cash-register)
cash FS 197/CENTRALA/2026 1 Gotowka / 349.00 / 0 / 100065
transfer + bank account 100004 FS 198/CENTRALA/2026 2 Przelew / 0 / 49.00 / 100065
cash + cash-register 100066 FS 199/CENTRALA/2026 1 Gotowka / 492.00 / 0 / 100066

Behavior is identical to the pre-merge run: transfer flips FormaPlatnosciId to 2 and moves the amount to the deferred bucket; an explicit cash-register (Stanowisko Kasowe) 100066 reads back distinct from the session default 100065. Config-driven, no caching. Explicit payment selection correctly requires a faktura (a paragon is rejected 422).

Full report with screenshots (invoices list showing FS 197-199 + FS 199 detail issued via the Subiekt connection): https://claude.ai/code/artifact/afbfaca3-bef5-4067-9639-d34a1d95f754

Dependency note still stands: this PR must land after #1310 is merged to main (the current diff is inflated by the not-yet-merged prerequisites and will collapse to the Subiekt-only delta once #1310 merges and this branch is rebased).

@piotrswierzy piotrswierzy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/pr-review — systematic review

The Subiekt-scoped delta itself is high quality (details below), but there is one branch-state problem that goes beyond the documented "stacked-diff inflation" and must be repaired before this PR can even be meaningfully rebased.

🔴 BLOCKING

The branch reverts work already merged to main — a rebase will NOT fix this automatically.
Verified independently: the merge-base of 296eafae with origin/main is main's current tip (678041c0) — meaning main was merged into this branch — yet the branch tree is missing merged work:

  • apps/api/src/migrations/1818000000003-add-invoice-issued-line-snapshot.ts (#1297) is deleted, and git grep issuedLineSnapshot over libs/core/src/invoicing on the branch finds nothing.
  • The #1330 buildEditConnectionSchema / ConnectionConfigContribution plugin-slot seam (plus its tests) is rolled back.
  • The #1317 KSeF FA(3) Płatność code and scripts/check-ksef-forma-platnosci-drift.mjs are rolled back.

This looks like a bad conflict resolution when main was merged in. Because the branch tip's file contents themselves lack the merged work, merging as-is silently reverts three shipped features — and the PR's "diff will collapse once #1310 merges and this rebases" claim won't hold. Fix: re-merge/rebase keeping main's side for all inFakt/KSeF/core/schema-seam files, then re-verify with git diff origin/main...HEAD showing zero deletions of merged work before undrafting.

🟡 IMPORTANT (in-scope Subiekt delta)

  1. apps/web/src/features/connections/components/edit-connection.schema.ts (~829-836, 1077-1101) — the three Subiekt fields are wired the pre-#1330 way (host-inlined schema clauses + StructuredConfigPatch keys). Current main's sanctioned mechanism is the plugin ConnectionConfigContribution slot — which this branch currently deletes. After the merge repair, rework the fields as a plugins/subiekt contribution instead of growing the host schema.
  2. subiekt-structured-section.tsx:63,139,156-157 — unset payment state is misrepresented as "Cash". The adapter deliberately sends nothing when defaultPaymentMethod is absent (and per your own E2E, explicit selection has real consequences — paragon → 422), but the summary label shows "Cash" and the Select has no empty option, so once an operator picks a value there's no way back to the unset/legacy state. Add an explicit Not set (Subiekt default) <option value=""> (mirroring the cash-register select) and derive the summary from the real tri-state.
  3. use-set-default-bank-account-mutation.ts:33-34 + subiekt-structured-section.tsx:97 — after flipping the provider default, onSuccess invalidates only connectionsQueryKeys.bankAccounts(id), but the Subiekt section reads subiektBankAccounts(id) — the owner-aware list's isDefault flags go stale ("(default in Subiekt)" sticks to the old account; the !account.isDefault skip-check operates on stale data). Invalidate both keys.
  4. subiekt-structured-section.tsx:97void setDefaultBankAccount.mutateAsync({...}) produces an unhandled promise rejection on failure (mutateAsync re-throws even though onError toasts). Use .mutate() per the hook's own documented contract.
  5. use-set-default-bank-account-mutation.ts:40-41 — the error toast is hardcoded to inFakt ("Could not update the inFakt default account…") but this PR makes Subiekt a second call site — a Subiekt bridge failure would show an inFakt error. Parameterize or neutralize the copy.
  6. subiekt-adapter.factory.ts:60-62 — the comment "this call intentionally does not type-check (the adapter still has a 3-arg constructor)" is false in the final tree — the 4-arg constructor ships in this same PR. Delete the stale mid-stack narration.

🟢 SUGGESTIONS

  • subiekt-invoicing.adapter.ts:515-533paymentFields() silently returns {} for transfer without bankAccountId; add a logger.warn so the misconfiguration is observable (nothing prevents saving that half-configured state).
  • subiekt-invoicing.adapter.ts:456Number(accountId) unvalidated → /api/bank-accounts/NaN/default on a non-numeric id; guard and throw the config domain error.
  • subiekt-structured-section.tsx:45-48, 230PAYMENT_METHOD_LABELS bypasses t() and the cash-register help default is Polish amid English defaults; unify.
  • subiekt-structured-section.tsx:74-81 — group accounts by ownerPodmiotId, not ownerName (two Podmioty sharing a display name would merge into one optgroup).

✅ Positive observations

  • Capability reuse is exactly as chartered — the adapter implements the existing core BankAccountsReader/BankAccountDefaultSetter with guard-narrowing; no new core ports snuck in. Subiekt-only concepts (Podmiot owner tag, Stanowiska Kasowe) stay plugin-local; the generic route 501s on unsupported adapters; the Subiekt controller narrows with a type-only adapter import — clean CORE↔integration boundary.
  • Fiscal-safe additive request building — byte-identical legacy request for unconfigured connections, with an exemplary test matrix + live E2E with SQL read-back.
  • Conventions all honored: as const unions, types in *.types.ts, shared Logger, colocated specs everywhere, @Roles('admin'), no any, no credential leakage.

Verdict: ❌ Request changes — formally moot while draft-blocked on #1310, but the merged-work reversion must be repaired first; the in-scope IMPORTANTs should land with the rebase.

norbert-kulus-blockydevs added a commit that referenced this pull request Jul 3, 2026
- strip unmerged #1335 content (Subiekt Part 2b + payment/cash-register
  sections + shots 28-29 + WSL-guide proxy-endpoint step) - rides with
  PR #1335 instead
- fix fabricated credentials/config examples against the real DTOs:
  KSeF (authType+secret, env+nested seller), DPD (login+password,
  payerFid string + required senderAddress), WooCommerce (siteUrl,
  inventory/orders blocks), Allegro (authorization-code flow,
  environment enum + optional keys), InPost (organizationId string,
  senderAddress, -pl sandbox host), Erli (real validator keys,
  ADR-025-correct taxonomy preference, decoder-accurate webhook body),
  AI (OL_AI_DEFAULT_MODEL / OL_AI_OPENAI_MODEL, gpt-4o-mini)
- Subiekt tutorial: manual document-type selection (FE does not
  NIP-preselect; Part 7 no longer instructs relying on auto-Receipt);
  README scopes the NIP rule to the auto-issue path
- sync KSeF/Subiekt capability tables with the adapters' implements
  lists; drop the stale Source layout trees
- align Subiekt /health response shape with the real bridge response
- revert the unrelated Erli S7 int-spec (0c1d07b) - moves to its own PR

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Repairs the BLOCKING merge-state defect and all IMPORTANT/SUGGESTION
findings from the /pr-review on #1335.

BLOCKING — the branch had merged an older main and, via a bad conflict
resolution, silently reverted three shipped features (#1297 issued-line
snapshot, #1317 KSeF FA(3) Płatność, #1330 connection-config plugin slot,
plus release-please plumbing). Reset the branch to origin/main and
re-applied only the Subiekt-scoped delta on top, so the diff is now the
Subiekt-only change and no merged work is reverted.

IMPORTANT
1. Payment/bank/cash-register config now rides the #1330
   ConnectionConfigContribution plugin slot (new
   plugins/subiekt/subiekt-connection-config.ts) instead of growing the
   host edit-connection.schema.ts.
2. Payment method is a real tri-state: an explicit "Not set (Subiekt
   default)" option, and the summary derives from the actual state (no
   false "Cash" for unset).
3. use-set-default-bank-account-mutation now invalidates the owner-aware
   subiektBankAccounts key too, so isDefault flags don't go stale.
4. The section fires the default-sync via .mutate() (not void mutateAsync)
   to avoid an unhandled promise rejection on failure.
5. The set-default error toast copy is now provider-neutral (was inFakt).
6. Removed the stale "does not type-check (3-arg constructor)" comment in
   subiekt-adapter.factory.ts — the 4-arg constructor ships here.

SUGGESTIONS
- paymentFields() warns when transfer is configured without a bankAccountId.
- setDefaultBankAccount guards the Number(accountId) coercion, throwing the
  config domain error on a non-numeric id.
- Payment labels routed through t(); the cash-register help default is English.
- Bank accounts group by the stable ownerPodmiotId, not the display name.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@norbert-kulus-blockydevs
norbert-kulus-blockydevs force-pushed the 1324-subiekt-bank-account-plan branch from 296eafa to 1d51d76 Compare July 6, 2026 07:04
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review. All findings are addressed. #1310 has since merged to main, so the draft blocker is cleared too. Pushed as a force-update (the branch was rebased — see BLOCKING below).

🔴 BLOCKING — merged-work reversion — FIXED

You were right: the branch had merged an older main and a bad conflict resolution silently reverted shipped work (#1297 issued-line snapshot, #1317 KSeF FA(3) Płatność + check-ksef-forma-platnosci-drift.mjs, #1330 buildEditConnectionSchema/ConnectionConfigContribution, plus the release-please plumbing).

Rather than re-merging and re-resolving, I reset the branch to current origin/main and re-applied only the Subiekt-scoped delta on top. The diff is now Subiekt-only (git diff origin/main..HEAD = 35 files, 0 deletions of merged work). Verified the previously-reverted work is present again: 1818000000003-add-invoice-issued-line-snapshot.ts, issuedLineSnapshot in core, buildEditConnectionSchema/ConnectionConfigContribution, and check-ksef-forma-platnosci-drift.mjs (which now runs green in check:invariants).

🟡 IMPORTANT

  1. Plugin config slot, not host schema — the three fields now live in a new plugins/subiekt/subiekt-connection-config.ts ConnectionConfigContribution (mirrors KSeF), wired via platform.connectionConfig + a declare module merge into PluginEditConnectionFields. The host edit-connection.schema.ts is untouched. (The pre-existing subiektBridgeUrl/subiektTriggerModel/subiektCapabilities stay host-inlined — their web: move plugin-specific connection-config assembly behind a plugin slot #1330 migration is out of [FEATURE] Integration+Frontend — Subiekt bank-account/payment-method + Oddział/Stanowisko Kasowe per invoice #1324 scope, same as inFakt/InPost today.)
  2. Payment tri-state — added an explicit Not set (Subiekt default) <option value="">; the summary now derives from the real tri-state ('' | cash | transfer), so an unset method no longer shows a misleading "Cash". readConfigToForm hydrates an absent key to '', not 'cash'.
  3. Stale isDefault flagsuse-set-default-bank-account-mutation now invalidates both bankAccounts(id) and subiektBankAccounts(id) on success.
  4. Unhandled rejection — the section fires the default-sync via .mutate() (not void mutateAsync), per the hook's documented contract.
  5. inFakt-hardcoded toast — the set-default error toast copy is now provider-neutral ("Could not update the default account… the invoicing provider still shows the previous default"); updated the two inFakt tests that asserted the old string.
  6. Stale factory comment — removed the "this call intentionally does not type-check (3-arg constructor)" narration in subiekt-adapter.factory.ts; the 4-arg constructor ships here (its 4th param defaults to {}, keeping the 3-arg test call sites valid).

🟢 SUGGESTIONS (all applied)

  • paymentFields() now logger.warns when transfer is configured without a bankAccountId (observable half-config).
  • setDefaultBankAccount guards the Number(accountId) coercion and throws SubiektConfigException on a non-numeric id instead of POSTing /bank-accounts/NaN/default.
  • Payment-method labels routed through t(); the cash-register help default is now English.
  • Bank accounts group by the stable numeric ownerPodmiotId (display label still prefers ownerName), so two Podmioty sharing a name no longer merge into one optgroup.

Verification

  • pnpm type-check (web, api, subiekt) — clean.
  • pnpm lint + check:invariants — green (cross-context imports, service-interfaces, migration-timestamps, KSeF forma-płatności drift all pass).
  • Tests: subiekt 218 passed; web 2017 passed (incl. new tri-state, group-by-id, .mutate(), NaN-guard, transfer-without-account-warn cases); api subiekt.controller 6 passed.

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Consolidated review (tech-review + pr-review)

Two independent passes on the Subiekt-only delta (libs/integrations/subiekt/**, apps/api/src/integrations/http/subiekt.controller.ts + DTOs, apps/web/src/plugins/subiekt/** + the Subiekt bits of features/connections). The inFakt/KSeF churn was excluded (it belongs to the prereq PRs; #1310 is now on main and the head is rebased, so the GitHub diff already collapses to the clean Subiekt delta).

Verdict: Request changes - one confirmed blocking correctness regression + the test gap that let it through. Everything else is merge-ready polish.

The design is genuinely strong: capability reuse verified (zero changes under libs/core/, adapter implements the pre-existing BankAccountsReader / BankAccountDefaultSetter verbatim - no new ports), hexagonal boundaries clean (owner-aware view stays Subiekt-local, neutral listBankAccounts drops ownerPodmiotId/ownerName), admin-gated read-only endpoints with SSRF/IMDS guards preserved, no token leakage, and thorough tests at every layer. The "drop on missing sibling" config-sync pattern is handled correctly, and the Oddział cut is documented consistently as session-bound.


BLOCKING

Payment fields are stamped on paragons, which the bridge rejects - libs/integrations/subiekt/src/infrastructure/adapters/subiekt-invoicing.adapter.ts:180-181 (helper at :525-545)

issueInvoice spreads ...this.paymentFields() unconditionally, with no gate on the resolved bridgeDocumentType. But deriveNeutralDocumentType maps a buyer without a pl-nip tax id to receipt -> PA (paragon), and the bridge's own contract states paymentMethod is "not supported for documentType: "PA"" (subiekt-bridge.types.ts:121-122).

Failure scenario: a seller doing mixed B2B/B2C sets defaultPaymentMethod (e.g. transfer + bankAccountId) so their fakturas carry bank details. Every subsequent B2C order (no NIP) derives to a paragon, the adapter still attaches paymentMethod, and the bridge rejects it (400/422) -> SubiektInvoiceRejectedError, a terminal failure. B2C paragon issuance, which worked before this PR, is now broken for any connection that configures payment. This is not theoretical - the PR's own verification note ("a paragon is rejected 422") is exactly this reject; the adapter just doesn't prevent a no-NIP order from reaching it. Applies to cash too, since the constraint is on the field's presence on PA at all.

Fix: gate the payment fields on the faktura path only - pass bridgeDocumentType into paymentFields() and return {} when it is 'PA'. Keep cashRegisterFields() unconditional (the bridge documents no PA restriction on stanowiskoKasoweId - it is in fact the paragon-relevant routing field).

IMPORTANT

Test gap that hid the blocker - libs/integrations/subiekt/src/infrastructure/adapters/__tests__/subiekt-invoicing.adapter.spec.ts

The payment/cash-register stamping suite only exercises the faktura (FV) path - every asserted request derives to FV. There is no test for the paragon (PA) interaction, which is exactly the broken branch. Add: "issueInvoice does not stamp paymentMethod on a receipt/PA document even when defaultPaymentMethod is configured" (it will fail until the fix above lands - that is the point).

SUGGESTIONS / NITs

  1. Side-effectful external write from a config picker (flag, not a defect) - apps/web/src/plugins/subiekt/components/subiekt-structured-section.tsx: onBankAccountChange fires setDefaultBankAccount.mutate(...), which flips Subiekt's global default bank account as a side effect of editing an OL connection. Intentional and mirrors the inFakt feat(infakt): bank-account picker with live inFakt default sync (#1303 follow-up) #1310 precedent, failures surface via toast - flagging only so it is a conscious sign-off that editing OL config mutates external ERP state.

  2. Cash-register query fetches unconditionally - subiekt-structured-section.tsx: useSubiektCashRegistersQuery runs on every Subiekt connection edit, even when the payment disclosure is collapsed (the bank-accounts query is correctly gated on isTransfer). Harmless (retries off, degrades gracefully) but a wasted bridge round-trip; consider gating on disclosure-open for symmetry.

  3. Mapping duplication - subiekt-invoicing.adapter.ts: listBankAccounts and listBankAccountsWithOwner map the same bridge response nearly identically. The neutral one could delegate to the owner-aware variant and strip the two owner fields, keeping the null-coercion rules in one place. Same idea for the positive-integer id guard duplicated between setDefaultBankAccount and the factory's parsePositiveIntField.

  4. View-id type/doc wording - subiekt-invoicing-views.types.ts: SubiektBankAccountView.id is string (a deliberate mirror of neutral InvoicingBankAccount.id: string) while SubiektCashRegisterView.id is number, yet the file header says ids are "surfaced as the bridge-native NUMBERs ... NOT stringified". Code is consistent end-to-end; just narrow the header wording so it doesn't read as contradicting the bank-account case.

  5. Endpoint templating consistency - SUBIEKT_BRIDGE_ENDPOINTS.setDefaultBankAccount(id) interpolates ${id} without encodeURIComponent (the status endpoint uses it). Safe here (id is a validated positive integer) - purely a consistency observation.

  6. Multi-payer picker is warning-only - on a multi-płatnik install an account from any Podmiot can be picked and persisted; because the issuing płatnik is session-bound (unknowable to OL) a mismatched account can be mis-billed/rejected. Matches the documented [FEATURE] Integration+Frontend — Subiekt bank-account/payment-method + Oddział/Stanowisko Kasowe per invoice #1324 limitation and is surfaced honestly, so acceptable here; a follow-up could at least record the chosen account's ownerPodmiotId for a future session-aware bridge to validate.


Once the PA suppression lands with the covering test, this is an approve.

@norbert-kulus-blockydevs
norbert-kulus-blockydevs marked this pull request as ready for review July 6, 2026 07:32
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

CI green + un-drafted

The draft blocker (#1310) is merged to main, so I marked this PR ready for review. The full pipeline now runs (the code jobs were previously skipped only because of the draft == false gate) and is green on run 28775341654:

  • Build - pass
  • Lint (incl. check:invariants: cross-context imports, service-interfaces, migration-timestamps, KSeF forma-płatności drift) - pass
  • Type Check - pass
  • Test (unit) - pass
  • Integration Tests - pass (16m59s)
  • PHP Unit Tests - pass

Branch is rebased on current main (git merge-base HEAD origin/main == origin/main tip); git diff origin/main..HEAD is the Subiekt-only delta with zero deletions of merged work. All review findings (BLOCKING + 6 IMPORTANT + 4 SUGGESTIONS) were addressed in the previous comment's commit. Ready for another look.

@piotrswierzy piotrswierzy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/pr-review — delta re-review (296eafae1d51d76c)

The BLOCKING reversion from my earlier Request-changes is fully resolved, and every prior in-scope IMPORTANT is addressed. Approving.

✅ GATE — merged-work reversion → RESOLVED

  • Merge-base = a9fd03ab = current origin/main tip; branch is properly rebased onto main containing #1310 (b279e171 confirmed as an ancestor).
  • git diff origin/main...1d51d76c --diff-filter=D returns empty — zero file deletions. The diffstat "deletions" are line-level edits within Subiekt-scoped/shared files.
  • All three previously-reverted works are present: #1297 snapshot migration (1818000000003-add-invoice-issued-line-snapshot.ts), #1330 seam (buildEditConnectionSchema/ConnectionConfigContribution), #1317 KSeF Płatność.
  • Three-dot diff is now Subiekt-scoped only, plus the legitimate neutral features/connections bank-account hooks/api/types and one controller registration.

#1310 capability reuse → RESOLVED

BankAccountsReader/BankAccountDefaultSetter come from merged #1310 in libs/corenot re-declared under libs/integrations/subiekt. The adapter cleanly implements them alongside InvoicingPort, RegulatoryStatusReader, CorrectionIssuer. No new core ports.

✅ Prior IMPORTANTs — all addressed (code even cites the review-item numbers)

  1. Unset payment "Cash" → explicit <option value="">Not set (Subiekt default)</option> + tri-state summary.
  2. Stale invalidation → onSuccess invalidates both bankAccounts(id) and subiektBankAccounts(id).
  3. void mutateAsync → now .mutate({...}); hook owns the single error seam.
  4. inFakt-hardcoded toast → now provider-neutral.
  5. False mid-stack factory comment → gone; new validateAndParseConfig/parsePositiveIntField are fiscal-safe additive (absent field → send nothing, no 'cash' default).
  6. FE fields now go through the #1330 ConnectionConfigContribution slot (plugins/subiekt/subiekt-connection-config.ts); host edit-connection.schema.ts diff is empty — no inlined Subiekt clauses.

✅ Positives

Guard-narrowing controller (getCapabilityAdapter<InvoicingPort>isSubiektInvoicingAdapter → 400 on non-Subiekt), @Roles('admin'), fiscal-safe additive request building, and a thorough spec set (controller/factory/adapter/structured-section/validator/bridge-client + the #1310 capability specs).

🟢 Minor (non-blocking)

isSubiektInvoicingAdapter is an inline module-level duck-type guard in subiekt.controller.ts, and the API layer imports the concrete SubiektInvoicingAdapter type. Acceptable for a platform-specific controller; could optionally become an exported guard from the integration package, but no correctness issue.

Verdict: ✅ Approve. One housekeeping note: the PR body still carries the stale "⚠️ Draft — depends on #1310 (must merge first)" text — please refresh it, since the draft flag is false and #1310 has merged.

@norbert-kulus-blockydevs
norbert-kulus-blockydevs merged commit c89afbf into main Jul 6, 2026
14 checks passed
piotrswierzy pushed a commit that referenced this pull request Jul 6, 2026
… Subiekt nexo (#1284)

* docs(integrations): add README + operator tutorials for all 8 adapter packages

Add per-package README.md to every integration package that was missing one:
ai, allegro, dpd-polska, erli, inpost, ksef, subiekt, woocommerce.
Each README covers adapter key + capabilities, credentials/config shape,
and links to further operator docs.

Add full A-to-Z operator tutorials for KSeF and Subiekt nexo with per-step
screenshot placeholders (ksef/tutorial.md, subiekt/tutorial.md). The Subiekt
tutorial covers bridge setup via PowerShell/WSL (without-exe-packaging branch),
wizard, B2B faktura, B2C paragon, and idempotency. The KSeF tutorial covers
token generation on the MF portal, connection wizard, B2B order, issuance, and
UPO download.

Create libs/integrations/ksef/assets/ and libs/integrations/subiekt/assets/
directories for future screenshot captures.

Update root README Integrations table to link the tutorial.md files for KSeF
and Subiekt nexo.

Closes #1265
Closes #1266

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NQB4zBWSrneR71TRKkx1t
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): add real Playwright screenshots + rewrite KSeF and Subiekt tutorials

Replaces all placeholder screenshot references in KSeF and Subiekt nexo
tutorials with actual Playwright captures taken against a live OpenLinker
instance (preview build with all in-flight FE PRs merged).

KSeF assets (14 PNGs):
  01-02: connections list + platform picker
  03-09: wizard fields (name, env, NIP, address, auth-type, secret)
  10-12: connection created, list, detail page
  13-14: invoices list with regulatory badges, invoice detail

Subiekt assets (18 PNGs):
  06-15: connection wizard (empty -> filled -> created -> test -> list -> detail)
  20-26: invoice flow (orders list, order detail, connection picker,
          ready-to-issue, issue clicked, issued state, invoices list)

Tutorial rewrites:
  - ksef/tutorial.md: reordered around actual wizard fields; Part 1 (KSeF portal)
    marked manual; Parts 2-5 use real screenshot refs
  - subiekt/tutorial.md: full rewrite to match connection-picker flow (multiple
    Invoicing connections), real bridge startup instructions, TLS note, idempotency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add real Subiekt nexo FS document screenshot to tutorial

Part 5 (verify in Subiekt nexo) was the only remaining manual-step
placeholder without a real capture. Replaces it with an actual screenshot
of an issued FS document open in Subiekt nexo desktop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): add real KSeF 2.0 test-portal screenshots for token generation

Part 1 (get a KSeF authorisation token) was the last manual-step
placeholder. Captures the full flow on the live ap-test.ksef.mf.gov.pl
portal: test-auth login, NIP + test certificate, dashboard, token list,
generate-token form, and the revealed token (value redacted before commit
since it's a live, usable test-environment credential).

Also corrects the test-portal URL: ksef-test.mf.gov.pl (KSeF 1.0) was
decommissioned 2025-09-01; the current test environment is
ap-test.ksef.mf.gov.pl (KSeF 2.0).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): point to the openlinker-subiekt-bridge repo

The bridge is being moved to its own repo under the openlinker-project
org (not yet published). Update README + tutorial repo links and clone
paths accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): move all integration docs + assets under libs/integrations/<pkg>/docs/

Establishes one consistent convention across all 8 integration packages:
docs (setup guides, runbooks, tutorials) and their image assets live at
libs/integrations/<pkg>/docs/ and libs/integrations/<pkg>/docs/assets/,
co-located with the code they document instead of split across a separate
docs/integrations/<pkg>/ tree.

Moves (via git mv, history preserved):
- docs/integrations/<pkg>/*.md -> libs/integrations/<pkg>/docs/*.md
  (allegro, dpd-polska, erli, inpost, ksef, prestashop, subiekt, woocommerce)
- docs/integrations/woocommerce/screenshots/ -> .../woocommerce/docs/assets/
- docs/assets/erli/ -> libs/integrations/erli/docs/assets/
- docs/assets/subiekt/ -> libs/integrations/subiekt/docs/assets/
  (merged with the tutorial screenshots already at .../subiekt/assets/,
  no filename collisions)
- libs/integrations/{ksef,subiekt}/tutorial.md -> .../docs/tutorial.md
- libs/integrations/{ksef,subiekt}/assets/ -> .../docs/assets/

Rewrites every relative link and image reference in the moved files
(directory depth changed by one level), updates the 8 package READMEs'
documentation sections, the root README's integration table, the
new-integration issue template, ADR-025's doc links, and the e2e
Playwright capture scripts' output paths. Verified all 251 local links
in the changed docs resolve.

Historical docs/plans/*.md archive entries are intentionally left
untouched — they're frozen snapshots per docs/plans/README.md's own
convention, not live documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): drop old personal repo link, switch bridge instructions to plain PowerShell

- Remove remaining references to the old norbert-kulus-blockydevs/openlinker-subiekt
  repo and its without-exe-packaging branch — the bridge now lives directly on
  openlinker-project/openlinker-subiekt-bridge.
- Rewrite "running the bridge" / smoke-test steps as plain Windows PowerShell
  (Invoke-RestMethod) instead of WSL-flavored bash+curl — the bridge always
  runs on Windows; WSL was an artifact of how screenshots were captured for
  this PR, not an operator requirement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): declutter, crop, and fix tutorial screenshots

Re-captures the KSeF and Subiekt tutorial screenshots against the fully
merged dev branch (all 9 in-flight FE PRs) with three fixes:

1. Wizard field-by-field captures consolidated into one "all fields
   filled" screenshot per wizard (KSeF: 6 shots -> 1; Subiekt: 3 shots -> 1).

2. Noisy/irrelevant data cropped instead of shown in full:
   - Connections list: crop to header + Add-connection button, hiding
     unrelated existing connections.
   - Invoices list: filter by the tutorial's own connection + issued
     status before capturing, instead of showing the full unfiltered
     list (which mixed in dozens of unrelated test rows).
   - Order detail / invoice-issued state: crop above the Sync
     status/Activity/Order Snapshot sections, which aren't part of the
     tutorial narrative.
   - Connection detail page: crop above the "Capabilities" panel, which
     has an unrelated pre-existing bug (renders literal `’` and
     "adapter not recognized" for working connections) surfaced by one
     of the merged branches -- out of scope for this docs PR.

3. Fixed two broken captures:
   - ksef/docs/assets/14-ol-invoice-detail-ksef.png was an "Invoice not
     found" error -- GET /invoices/:invoiceId didn't exist on main yet
     (ships in open PR #1231). Temporarily merged that branch locally to
     capture the real working page, then reverted the merge.
   - subiekt order-detail flow (21-25) used a test order with a
     dangling customerId (no matching customer_projections row) that
     rendered "Couldn't load customer details. Retry". Backed a real
     order (ol_order_ksef_e2e_test_001) with a customer_projections row
     matching its embedded billing address, then drove the actual
     Issue-invoice flow through the live Subiekt bridge end-to-end.

Also drops one redundant intermediate screenshot
(24-ol-invoice-issuing-or-issued.png, identical to 25 once the bridge
issue was fixed) and removes now-orphaned per-field screenshot files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): redact KSeF portal cert/token data, fix platform-picker screenshots

- p3/p4 (KSeF portal — NIP + test cert / sign test request): the test
  certificate's SHA256 fingerprint and ID were shown in plaintext. Redact
  both (they're live, usable values on the shared public test environment,
  same category as the token value already redacted in p8).
- p6 (KSeF portal token list): was showing the full historical token list
  (9+ unrelated tokens: "kolejny", "henha", "teest", ...) instead of just
  the tutorial's own token. Generate a fresh, clearly-named token
  immediately before capturing so it sorts to the top, crop to header +
  that one row.
- 02-ol-platform-picker.png / 07-ol-platform-picker.png: these were
  byte-identical between the KSeF and Subiekt tutorials, but their alt
  text claimed each showed its own platform's card specifically ("KSeF
  card" / "Subiekt nexo card") — neither image actually highlighted
  anything. Hover the relevant card before capturing so each tutorial's
  screenshot genuinely shows what its alt text says.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): add missing Part 3/4 screenshots, drop unimplemented KOR section

Part 3 (get a B2B order in), Part 4 (issue the invoice), and the Subiekt
cross-reference in "Next steps" had no corresponding OpenLinker screenshots
- three (correction, orders-list, order-detail) were simply never captured.
Fills the gap with a real order (ol_order_e2e_tutorial_001, B2B, company
buyer) driven through orders-list -> order-detail (not issued) ->
connection picker -> ready-to-issue -> issued+accepted, calling the real
KSeF test API.

Also:
- Corrected an inaccurate claim: the tutorial said OpenLinker "pre-selects
  Invoice (faktura VAT) when a NIP is present" — checked
  order-invoice-panel.tsx and the document-type select always defaults to
  'invoice' with no NIP-aware logic. Removed the false claim.
- Removed the entire "Part 6 — Correction invoices (KOR)" section. The
  Issue-correction dialog is real, working FE (KSeF has an
  invoiceCorrectionFlow slot), but actually submitting fails with "Provider
  does not support correction issuance" — KsefInvoicingAdapter doesn't
  implement the CorrectionIssuer capability. Checked all 11 open PRs; none
  add it either, so this isn't a merge-order gap, it's an unbuilt backend
  capability. Replaced with a one-line "coming soon" note in Next steps.
- Removed the "Pair with Subiekt nexo" bullet from Next steps per review —
  the KSeF tutorial should carry zero Subiekt references.
- Renamed/consolidated the local (gitignored) capture scripts used to
  produce these screenshots for future reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): restore Part 6 (KOR corrections) tutorial with real screenshots

CorrectionIssuer shipped on KsefInvoicingAdapter (#1289), so the correction
flow that was previously removed from the tutorial (backend didn't support
it yet) now works end to end. Captured 4 real screenshots against a live
KSeF sandbox correction: the Issue correction button, the filled-in
correction dialog, the post-submit confirmation, and the invoices list
showing the original + correction rows side by side.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* chore(docs): address tech-review findings - remove orphaned assets and stray .gitkeep files

Delete 34 Subiekt screenshots never referenced by tutorial.md or
README.md (an earlier numbering scheme plus a cut PrestaShop-order
walkthrough section), and two redundant .gitkeep files - one in a
stray top-level libs/integrations/subiekt/assets/ directory
confusable with the real docs/assets/, and one in
libs/integrations/ksef/docs/assets/ now that it holds 22 real
screenshots.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* test(erli): cover #1146 cancellation stock-restore hook (S7)

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(woocommerce): recapture master-shop screenshots in light mode

The 9 WooCommerce master-shop-setup-guide screenshots were dark-mode
(captured before this branch existed, pre-dating the docs relocation).
Recaptured all of them against a live dev stack in light mode, with the
same red-box/arrow callouts drawn programmatically instead of by hand.

Adds two reusable Playwright e2e scripts:
- apps/web/e2e/annotate.mjs: canvas-overlay helper to draw red
  rectangle/ellipse annotations (with optional arrow) onto a page before
  screenshotting, so future tutorial captures don't need a manual
  image-editor pass.
- apps/web/e2e/woocommerce-walkthrough.mjs: drives the WooCommerce
  connection wizard end-to-end and captures all 9 assets referenced from
  master-shop-setup-guide.md, forcing light theme via
  localStorage['openlinker.theme'] regardless of host prefers-color-scheme.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): fix Piotr's review findings on PR #1284

- ksef/README.md: fix wrong KSeF API base URLs (was api-test.ksef.mf.gov.pl/api/v2
  and ksef.mf.gov.pl/api/v2; authoritative ksef-hosts.ts uses a bare /v2 path with
  prod host api.ksef.mf.gov.pl). Also documents the demo tier that was missing.
- ksef/docs/setup-guide.md: refresh from the stale C2-stub narrative (issuance
  throws, RegulatoryTransmitter unimplemented, no seller-profile config) to the
  shipped reality — full FA(3)+KOR issuance, RegulatoryTransmitter and
  CorrectionIssuer implemented, seller block present on KsefConnectionConfig.
- subiekt/docs/tutorial.md: the appsettings.json sample had a fictional
  "HeaderName": "X-Api-Key" field — the real bridge (AuthOptions) only has
  Enabled/ApiKey and hardcodes Authorization: Bearer. Removed the field and
  documented that the client's extra x-bridge-token header is sent but ignored
  by the bridge.
- subiekt/docs/runbook.md: same x-bridge-token clarification.
- woocommerce/README.md: capability table was missing CategoryProvisioner
  (manifest declares 6 capabilities, README listed 5).
- allegro/README.md: ADR-024 link pointed at the adrs/ directory instead of the
  file; repointed to the actual architecture-overview.md#824 section plus the
  correct ADR-024 file (which covers the related OfferManager/ProductPublisher
  split, not #824 itself).

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add Windows+WSL2 dev quick-setup guide (Presta + OpenLinker + bridge)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): expand PrestaShop run steps + link canonical Getting Started

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): use {{USER_NAME}} placeholder instead of a real Windows username

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add optional 'launch bridge from WSL' command box

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): document payment-method / bank-account / cash-register per-invoice feature (#1324)

Adds setup-guide Part B2 subsection + tutorial Part 2b with live screenshots; FV-only requirement, multi-payer warning, fixed-Centrala note, advanced config keys.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): fix broken image links in setup-guide (remap to existing assets, drop unbacked shots)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): payment config (FA(3) Platnosc) tutorial + KOR snapshot semantics

Live-E2E-verified on the KSeF test environment (2026-07-03):
- new tutorial Part 2a - per-connection payment config (method, term,
  bank account, SWIFT, skonto) with fresh screenshots
- setup-guide: config.payment field reference + issuance-time line
  snapshot semantics in the Corrections section (#1297)
- README: Platnosc feature bullet
- re-captured 12-ol-ksef-detail.png with the post-#1320 capability panel

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): address Piotr review round 3 on PR #1284

- strip unmerged #1335 content (Subiekt Part 2b + payment/cash-register
  sections + shots 28-29 + WSL-guide proxy-endpoint step) - rides with
  PR #1335 instead
- fix fabricated credentials/config examples against the real DTOs:
  KSeF (authType+secret, env+nested seller), DPD (login+password,
  payerFid string + required senderAddress), WooCommerce (siteUrl,
  inventory/orders blocks), Allegro (authorization-code flow,
  environment enum + optional keys), InPost (organizationId string,
  senderAddress, -pl sandbox host), Erli (real validator keys,
  ADR-025-correct taxonomy preference, decoder-accurate webhook body),
  AI (OL_AI_DEFAULT_MODEL / OL_AI_OPENAI_MODEL, gpt-4o-mini)
- Subiekt tutorial: manual document-type selection (FE does not
  NIP-preselect; Part 7 no longer instructs relying on auto-Receipt);
  README scopes the NIP rule to the auto-issue path
- sync KSeF/Subiekt capability tables with the adapters' implements
  lists; drop the stale Source layout trees
- align Subiekt /health response shape with the real bridge response
- revert the unrelated Erli S7 int-spec (0c1d07b) - moves to its own PR

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): post-review consolidation fixes on PR #1284

- merge origin/main so the branch carries the shipped FA(3) Platnosc
  feature (#1317, b40902e) that the payment docs describe - the docs
  no longer reference an unmerged feature
- repoint 4 inbound links broken by the docs/integrations -> package
  docs/ move (architecture-overview, getting-started, user-guide 02/04)
- Erli README: capability row now matches ErliOfferManagerAdapter's
  implements list (drop OfferLister/OfferQuantityBatchUpdater, add
  OfferStatusReader/OfferStockRestorer/TaxonomyBorrower)
- align the Part 2a heading dash with sibling headings
- woocommerce-walkthrough.mjs: admin password from OL_ADMIN_PASSWORD
  env instead of a hardcoded shared-instance value

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(plans): repoint dangling links after integrations docs move

The docs/integrations -> libs/integrations/<pkg>/docs/ relocation on
PR #1284 left two frozen implementation-plan docs pointing at the old
paths. Repoint them to the current locations.

Addresses the residual N1 finding from the PR #1284 delta re-review.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): reconcile README/setup-guide shapes with DTOs; e2e cleanup

Address the consolidated /pr-review + /tech-review findings on PR #1284.

IMPORTANT:
- dpd-polska setup-guide: document payerFid as the required numeric id and
  masterFid as optional, matching DpdConnectionConfigDto.
- ksef setup-guide credentials table: the operator submits { authType, secret }
  (raw token); secretRef is platform-assigned, kept in prose only.
- woocommerce README: OrderProcessorManager supports only OrderFulfillmentUpdater
  (drop FulfillmentStatusReader / DestinationOptionsReader over-claim).
- prestashop README: add ProductPublisher + CategoryProvisioner to prose and
  capability table (manifest declares 6).

Minor:
- woocommerce setup-guide: note HTTPS is required (config validator rejects http).
- ksef README: clarify RegulatoryStatusReader is covered via RegulatoryTransmitter.
- apps/web/e2e: parameterize admin credentials via OL_ADMIN_USERNAME/PASSWORD,
  mark author-local defaults, drop dead VARIANT const, fix stale localStorage log
  message, add apps/web/e2e/README.md documenting these as manual capture scripts.
- erli setup-guide: wire in 00-dashboard/01-connections-list; drop redundant
  orphaned 12-order-detail.png.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

---------

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
Repairs the BLOCKING merge-state defect and all IMPORTANT/SUGGESTION
findings from the /pr-review on #1335.

BLOCKING — the branch had merged an older main and, via a bad conflict
resolution, silently reverted three shipped features (#1297 issued-line
snapshot, #1317 KSeF FA(3) Płatność, #1330 connection-config plugin slot,
plus release-please plumbing). Reset the branch to origin/main and
re-applied only the Subiekt-scoped delta on top, so the diff is now the
Subiekt-only change and no merged work is reverted.

IMPORTANT
1. Payment/bank/cash-register config now rides the #1330
   ConnectionConfigContribution plugin slot (new
   plugins/subiekt/subiekt-connection-config.ts) instead of growing the
   host edit-connection.schema.ts.
2. Payment method is a real tri-state: an explicit "Not set (Subiekt
   default)" option, and the summary derives from the actual state (no
   false "Cash" for unset).
3. use-set-default-bank-account-mutation now invalidates the owner-aware
   subiektBankAccounts key too, so isDefault flags don't go stale.
4. The section fires the default-sync via .mutate() (not void mutateAsync)
   to avoid an unhandled promise rejection on failure.
5. The set-default error toast copy is now provider-neutral (was inFakt).
6. Removed the stale "does not type-check (3-arg constructor)" comment in
   subiekt-adapter.factory.ts — the 4-arg constructor ships here.

SUGGESTIONS
- paymentFields() warns when transfer is configured without a bankAccountId.
- setDefaultBankAccount guards the Number(accountId) coercion, throwing the
  config domain error on a non-numeric id.
- Payment labels routed through t(); the cash-register help default is English.
- Bank accounts group by the stable ownerPodmiotId, not the display name.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
… Subiekt nexo (#1284)

* docs(integrations): add README + operator tutorials for all 8 adapter packages

Add per-package README.md to every integration package that was missing one:
ai, allegro, dpd-polska, erli, inpost, ksef, subiekt, woocommerce.
Each README covers adapter key + capabilities, credentials/config shape,
and links to further operator docs.

Add full A-to-Z operator tutorials for KSeF and Subiekt nexo with per-step
screenshot placeholders (ksef/tutorial.md, subiekt/tutorial.md). The Subiekt
tutorial covers bridge setup via PowerShell/WSL (without-exe-packaging branch),
wizard, B2B faktura, B2C paragon, and idempotency. The KSeF tutorial covers
token generation on the MF portal, connection wizard, B2B order, issuance, and
UPO download.

Create libs/integrations/ksef/assets/ and libs/integrations/subiekt/assets/
directories for future screenshot captures.

Update root README Integrations table to link the tutorial.md files for KSeF
and Subiekt nexo.

Closes #1265
Closes #1266

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NQB4zBWSrneR71TRKkx1t
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): add real Playwright screenshots + rewrite KSeF and Subiekt tutorials

Replaces all placeholder screenshot references in KSeF and Subiekt nexo
tutorials with actual Playwright captures taken against a live OpenLinker
instance (preview build with all in-flight FE PRs merged).

KSeF assets (14 PNGs):
  01-02: connections list + platform picker
  03-09: wizard fields (name, env, NIP, address, auth-type, secret)
  10-12: connection created, list, detail page
  13-14: invoices list with regulatory badges, invoice detail

Subiekt assets (18 PNGs):
  06-15: connection wizard (empty -> filled -> created -> test -> list -> detail)
  20-26: invoice flow (orders list, order detail, connection picker,
          ready-to-issue, issue clicked, issued state, invoices list)

Tutorial rewrites:
  - ksef/tutorial.md: reordered around actual wizard fields; Part 1 (KSeF portal)
    marked manual; Parts 2-5 use real screenshot refs
  - subiekt/tutorial.md: full rewrite to match connection-picker flow (multiple
    Invoicing connections), real bridge startup instructions, TLS note, idempotency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add real Subiekt nexo FS document screenshot to tutorial

Part 5 (verify in Subiekt nexo) was the only remaining manual-step
placeholder without a real capture. Replaces it with an actual screenshot
of an issued FS document open in Subiekt nexo desktop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): add real KSeF 2.0 test-portal screenshots for token generation

Part 1 (get a KSeF authorisation token) was the last manual-step
placeholder. Captures the full flow on the live ap-test.ksef.mf.gov.pl
portal: test-auth login, NIP + test certificate, dashboard, token list,
generate-token form, and the revealed token (value redacted before commit
since it's a live, usable test-environment credential).

Also corrects the test-portal URL: ksef-test.mf.gov.pl (KSeF 1.0) was
decommissioned 2025-09-01; the current test environment is
ap-test.ksef.mf.gov.pl (KSeF 2.0).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): point to the openlinker-subiekt-bridge repo

The bridge is being moved to its own repo under the openlinker-project
org (not yet published). Update README + tutorial repo links and clone
paths accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): move all integration docs + assets under libs/integrations/<pkg>/docs/

Establishes one consistent convention across all 8 integration packages:
docs (setup guides, runbooks, tutorials) and their image assets live at
libs/integrations/<pkg>/docs/ and libs/integrations/<pkg>/docs/assets/,
co-located with the code they document instead of split across a separate
docs/integrations/<pkg>/ tree.

Moves (via git mv, history preserved):
- docs/integrations/<pkg>/*.md -> libs/integrations/<pkg>/docs/*.md
  (allegro, dpd-polska, erli, inpost, ksef, prestashop, subiekt, woocommerce)
- docs/integrations/woocommerce/screenshots/ -> .../woocommerce/docs/assets/
- docs/assets/erli/ -> libs/integrations/erli/docs/assets/
- docs/assets/subiekt/ -> libs/integrations/subiekt/docs/assets/
  (merged with the tutorial screenshots already at .../subiekt/assets/,
  no filename collisions)
- libs/integrations/{ksef,subiekt}/tutorial.md -> .../docs/tutorial.md
- libs/integrations/{ksef,subiekt}/assets/ -> .../docs/assets/

Rewrites every relative link and image reference in the moved files
(directory depth changed by one level), updates the 8 package READMEs'
documentation sections, the root README's integration table, the
new-integration issue template, ADR-025's doc links, and the e2e
Playwright capture scripts' output paths. Verified all 251 local links
in the changed docs resolve.

Historical docs/plans/*.md archive entries are intentionally left
untouched — they're frozen snapshots per docs/plans/README.md's own
convention, not live documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): drop old personal repo link, switch bridge instructions to plain PowerShell

- Remove remaining references to the old norbert-kulus-blockydevs/openlinker-subiekt
  repo and its without-exe-packaging branch — the bridge now lives directly on
  openlinker-project/openlinker-subiekt-bridge.
- Rewrite "running the bridge" / smoke-test steps as plain Windows PowerShell
  (Invoke-RestMethod) instead of WSL-flavored bash+curl — the bridge always
  runs on Windows; WSL was an artifact of how screenshots were captured for
  this PR, not an operator requirement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): declutter, crop, and fix tutorial screenshots

Re-captures the KSeF and Subiekt tutorial screenshots against the fully
merged dev branch (all 9 in-flight FE PRs) with three fixes:

1. Wizard field-by-field captures consolidated into one "all fields
   filled" screenshot per wizard (KSeF: 6 shots -> 1; Subiekt: 3 shots -> 1).

2. Noisy/irrelevant data cropped instead of shown in full:
   - Connections list: crop to header + Add-connection button, hiding
     unrelated existing connections.
   - Invoices list: filter by the tutorial's own connection + issued
     status before capturing, instead of showing the full unfiltered
     list (which mixed in dozens of unrelated test rows).
   - Order detail / invoice-issued state: crop above the Sync
     status/Activity/Order Snapshot sections, which aren't part of the
     tutorial narrative.
   - Connection detail page: crop above the "Capabilities" panel, which
     has an unrelated pre-existing bug (renders literal `’` and
     "adapter not recognized" for working connections) surfaced by one
     of the merged branches -- out of scope for this docs PR.

3. Fixed two broken captures:
   - ksef/docs/assets/14-ol-invoice-detail-ksef.png was an "Invoice not
     found" error -- GET /invoices/:invoiceId didn't exist on main yet
     (ships in open PR #1231). Temporarily merged that branch locally to
     capture the real working page, then reverted the merge.
   - subiekt order-detail flow (21-25) used a test order with a
     dangling customerId (no matching customer_projections row) that
     rendered "Couldn't load customer details. Retry". Backed a real
     order (ol_order_ksef_e2e_test_001) with a customer_projections row
     matching its embedded billing address, then drove the actual
     Issue-invoice flow through the live Subiekt bridge end-to-end.

Also drops one redundant intermediate screenshot
(24-ol-invoice-issuing-or-issued.png, identical to 25 once the bridge
issue was fixed) and removes now-orphaned per-field screenshot files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): redact KSeF portal cert/token data, fix platform-picker screenshots

- p3/p4 (KSeF portal — NIP + test cert / sign test request): the test
  certificate's SHA256 fingerprint and ID were shown in plaintext. Redact
  both (they're live, usable values on the shared public test environment,
  same category as the token value already redacted in p8).
- p6 (KSeF portal token list): was showing the full historical token list
  (9+ unrelated tokens: "kolejny", "henha", "teest", ...) instead of just
  the tutorial's own token. Generate a fresh, clearly-named token
  immediately before capturing so it sorts to the top, crop to header +
  that one row.
- 02-ol-platform-picker.png / 07-ol-platform-picker.png: these were
  byte-identical between the KSeF and Subiekt tutorials, but their alt
  text claimed each showed its own platform's card specifically ("KSeF
  card" / "Subiekt nexo card") — neither image actually highlighted
  anything. Hover the relevant card before capturing so each tutorial's
  screenshot genuinely shows what its alt text says.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): add missing Part 3/4 screenshots, drop unimplemented KOR section

Part 3 (get a B2B order in), Part 4 (issue the invoice), and the Subiekt
cross-reference in "Next steps" had no corresponding OpenLinker screenshots
- three (correction, orders-list, order-detail) were simply never captured.
Fills the gap with a real order (ol_order_e2e_tutorial_001, B2B, company
buyer) driven through orders-list -> order-detail (not issued) ->
connection picker -> ready-to-issue -> issued+accepted, calling the real
KSeF test API.

Also:
- Corrected an inaccurate claim: the tutorial said OpenLinker "pre-selects
  Invoice (faktura VAT) when a NIP is present" — checked
  order-invoice-panel.tsx and the document-type select always defaults to
  'invoice' with no NIP-aware logic. Removed the false claim.
- Removed the entire "Part 6 — Correction invoices (KOR)" section. The
  Issue-correction dialog is real, working FE (KSeF has an
  invoiceCorrectionFlow slot), but actually submitting fails with "Provider
  does not support correction issuance" — KsefInvoicingAdapter doesn't
  implement the CorrectionIssuer capability. Checked all 11 open PRs; none
  add it either, so this isn't a merge-order gap, it's an unbuilt backend
  capability. Replaced with a one-line "coming soon" note in Next steps.
- Removed the "Pair with Subiekt nexo" bullet from Next steps per review —
  the KSeF tutorial should carry zero Subiekt references.
- Renamed/consolidated the local (gitignored) capture scripts used to
  produce these screenshots for future reuse.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014S9kgctitB75BfHEDejJ6B
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): restore Part 6 (KOR corrections) tutorial with real screenshots

CorrectionIssuer shipped on KsefInvoicingAdapter (#1289), so the correction
flow that was previously removed from the tutorial (backend didn't support
it yet) now works end to end. Captured 4 real screenshots against a live
KSeF sandbox correction: the Issue correction button, the filled-in
correction dialog, the post-submit confirmation, and the invoices list
showing the original + correction rows side by side.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* chore(docs): address tech-review findings - remove orphaned assets and stray .gitkeep files

Delete 34 Subiekt screenshots never referenced by tutorial.md or
README.md (an earlier numbering scheme plus a cut PrestaShop-order
walkthrough section), and two redundant .gitkeep files - one in a
stray top-level libs/integrations/subiekt/assets/ directory
confusable with the real docs/assets/, and one in
libs/integrations/ksef/docs/assets/ now that it holds 22 real
screenshots.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* test(erli): cover #1146 cancellation stock-restore hook (S7)

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(woocommerce): recapture master-shop screenshots in light mode

The 9 WooCommerce master-shop-setup-guide screenshots were dark-mode
(captured before this branch existed, pre-dating the docs relocation).
Recaptured all of them against a live dev stack in light mode, with the
same red-box/arrow callouts drawn programmatically instead of by hand.

Adds two reusable Playwright e2e scripts:
- apps/web/e2e/annotate.mjs: canvas-overlay helper to draw red
  rectangle/ellipse annotations (with optional arrow) onto a page before
  screenshotting, so future tutorial captures don't need a manual
  image-editor pass.
- apps/web/e2e/woocommerce-walkthrough.mjs: drives the WooCommerce
  connection wizard end-to-end and captures all 9 assets referenced from
  master-shop-setup-guide.md, forcing light theme via
  localStorage['openlinker.theme'] regardless of host prefers-color-scheme.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): fix Piotr's review findings on PR #1284

- ksef/README.md: fix wrong KSeF API base URLs (was api-test.ksef.mf.gov.pl/api/v2
  and ksef.mf.gov.pl/api/v2; authoritative ksef-hosts.ts uses a bare /v2 path with
  prod host api.ksef.mf.gov.pl). Also documents the demo tier that was missing.
- ksef/docs/setup-guide.md: refresh from the stale C2-stub narrative (issuance
  throws, RegulatoryTransmitter unimplemented, no seller-profile config) to the
  shipped reality — full FA(3)+KOR issuance, RegulatoryTransmitter and
  CorrectionIssuer implemented, seller block present on KsefConnectionConfig.
- subiekt/docs/tutorial.md: the appsettings.json sample had a fictional
  "HeaderName": "X-Api-Key" field — the real bridge (AuthOptions) only has
  Enabled/ApiKey and hardcodes Authorization: Bearer. Removed the field and
  documented that the client's extra x-bridge-token header is sent but ignored
  by the bridge.
- subiekt/docs/runbook.md: same x-bridge-token clarification.
- woocommerce/README.md: capability table was missing CategoryProvisioner
  (manifest declares 6 capabilities, README listed 5).
- allegro/README.md: ADR-024 link pointed at the adrs/ directory instead of the
  file; repointed to the actual architecture-overview.md#824 section plus the
  correct ADR-024 file (which covers the related OfferManager/ProductPublisher
  split, not #824 itself).

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add Windows+WSL2 dev quick-setup guide (Presta + OpenLinker + bridge)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): expand PrestaShop run steps + link canonical Getting Started

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): use {{USER_NAME}} placeholder instead of a real Windows username

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): add optional 'launch bridge from WSL' command box

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): document payment-method / bank-account / cash-register per-invoice feature (#1324)

Adds setup-guide Part B2 subsection + tutorial Part 2b with live screenshots; FV-only requirement, multi-payer warning, fixed-Centrala note, advanced config keys.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(subiekt): fix broken image links in setup-guide (remap to existing assets, drop unbacked shots)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): payment config (FA(3) Platnosc) tutorial + KOR snapshot semantics

Live-E2E-verified on the KSeF test environment (2026-07-03):
- new tutorial Part 2a - per-connection payment config (method, term,
  bank account, SWIFT, skonto) with fresh screenshots
- setup-guide: config.payment field reference + issuance-time line
  snapshot semantics in the Corrections section (#1297)
- README: Platnosc feature bullet
- re-captured 12-ol-ksef-detail.png with the post-#1320 capability panel

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): address Piotr review round 3 on PR #1284

- strip unmerged #1335 content (Subiekt Part 2b + payment/cash-register
  sections + shots 28-29 + WSL-guide proxy-endpoint step) - rides with
  PR #1335 instead
- fix fabricated credentials/config examples against the real DTOs:
  KSeF (authType+secret, env+nested seller), DPD (login+password,
  payerFid string + required senderAddress), WooCommerce (siteUrl,
  inventory/orders blocks), Allegro (authorization-code flow,
  environment enum + optional keys), InPost (organizationId string,
  senderAddress, -pl sandbox host), Erli (real validator keys,
  ADR-025-correct taxonomy preference, decoder-accurate webhook body),
  AI (OL_AI_DEFAULT_MODEL / OL_AI_OPENAI_MODEL, gpt-4o-mini)
- Subiekt tutorial: manual document-type selection (FE does not
  NIP-preselect; Part 7 no longer instructs relying on auto-Receipt);
  README scopes the NIP rule to the auto-issue path
- sync KSeF/Subiekt capability tables with the adapters' implements
  lists; drop the stale Source layout trees
- align Subiekt /health response shape with the real bridge response
- revert the unrelated Erli S7 int-spec (0c1d07b) - moves to its own PR

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): post-review consolidation fixes on PR #1284

- merge origin/main so the branch carries the shipped FA(3) Platnosc
  feature (#1317, b40902e) that the payment docs describe - the docs
  no longer reference an unmerged feature
- repoint 4 inbound links broken by the docs/integrations -> package
  docs/ move (architecture-overview, getting-started, user-guide 02/04)
- Erli README: capability row now matches ErliOfferManagerAdapter's
  implements list (drop OfferLister/OfferQuantityBatchUpdater, add
  OfferStatusReader/OfferStockRestorer/TaxonomyBorrower)
- align the Part 2a heading dash with sibling headings
- woocommerce-walkthrough.mjs: admin password from OL_ADMIN_PASSWORD
  env instead of a hardcoded shared-instance value

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(plans): repoint dangling links after integrations docs move

The docs/integrations -> libs/integrations/<pkg>/docs/ relocation on
PR #1284 left two frozen implementation-plan docs pointing at the old
paths. Repoint them to the current locations.

Addresses the residual N1 finding from the PR #1284 delta re-review.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(integrations): reconcile README/setup-guide shapes with DTOs; e2e cleanup

Address the consolidated /pr-review + /tech-review findings on PR #1284.

IMPORTANT:
- dpd-polska setup-guide: document payerFid as the required numeric id and
  masterFid as optional, matching DpdConnectionConfigDto.
- ksef setup-guide credentials table: the operator submits { authType, secret }
  (raw token); secretRef is platform-assigned, kept in prose only.
- woocommerce README: OrderProcessorManager supports only OrderFulfillmentUpdater
  (drop FulfillmentStatusReader / DestinationOptionsReader over-claim).
- prestashop README: add ProductPublisher + CategoryProvisioner to prose and
  capability table (manifest declares 6).

Minor:
- woocommerce setup-guide: note HTTPS is required (config validator rejects http).
- ksef README: clarify RegulatoryStatusReader is covered via RegulatoryTransmitter.
- apps/web/e2e: parameterize admin credentials via OL_ADMIN_USERNAME/PASSWORD,
  mark author-local defaults, drop dead VARIANT const, fix stale localStorage log
  message, add apps/web/e2e/README.md documenting these as manual capture scripts.
- erli setup-guide: wire in 00-dashboard/01-connections-list; drop redundant
  orphaned 12-order-detail.png.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

---------

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Integration+Frontend — Subiekt bank-account/payment-method + Oddział/Stanowisko Kasowe per invoice

2 participants