Skip to content

fix(infakt): implement real PDF download via RegulatoryDocumentReader - #1323

Merged
norbert-kulus-blockydevs merged 1 commit into
1282-infakt-fe-pluginfrom
1321-infakt-pdf-download
Jul 2, 2026
Merged

fix(infakt): implement real PDF download via RegulatoryDocumentReader#1323
norbert-kulus-blockydevs merged 1 commit into
1282-infakt-fe-pluginfrom
1321-infakt-pdf-download

Conversation

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator

Summary

  • InfaktInvoicingAdapter read a pdf_url field that does not exist on Infakt's invoice resource (verified live against the sandbox), so InvoiceRecord.pdfUrl was always null and the "download PDF" affordance never worked for Infakt connections.
  • The adapter now implements RegulatoryDocumentReader.getRegulatoryDocument for kind: 'rendered', calling the real GET /invoices/{uuid}/pdf.json?document_type=original&invoice_type={kind} endpoint (confirmed live: returns a valid PDF binary directly).
  • The dead pdf_url / print_url fields are removed from the Infakt wire type; InvoiceRecord.pdfUrl stays on core (Subiekt's FE still reads it) but Infakt no longer fakes it — it's null now, with the real path routed through the existing GET /invoices/:invoiceId/document?kind=rendered controller route (unchanged, already supports this).
  • FE: new useInvoiceRenderedDocumentDownload hook + a working "Download PDF" button in the Infakt invoice detail section.

Base branch is 1282-infakt-fe-plugin (PR #1300, still open) because the touched FE component (infakt-invoice-detail-section.tsx) only exists on that branch — will retarget to main once it merges, matching PR #1307's precedent.

Test plan

  • pnpm --filter @openlinker/integrations-infakt build/lint/test — 115/115 passing, new getRegulatoryDocument + getBinary coverage (happy path, no-content-type fallback, unsupported kind, oversized-body cap)
  • pnpm --filter @openlinker/web type-check clean
  • New FE test: clicking "Download PDF" calls apiClient.invoicing.downloadDocument(id, 'rendered')
  • pnpm check:invariants clean (no cross-context/service-interface violations)
  • Live sandbox verification of the real PDF endpoint before implementation (see issue [BUG] Integration — Infakt adapter's pdfUrl is always null; real PDF download endpoint unused #1321)

Closes #1321

InfaktInvoicingAdapter read a pdf_url field that does not exist on Infakt's
invoice resource (verified live against the sandbox), so InvoiceRecord.pdfUrl
was always null and the "download PDF" affordance never worked. Infakt does
expose a real PDF at GET /invoices/{uuid}/pdf.json?document_type=original,
so the adapter now implements RegulatoryDocumentReader for kind 'rendered'
against that endpoint, and the FE Infakt invoice detail section gets a
working Download PDF button wired to the existing /document?kind=rendered
route.

Closes #1321

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

Copy link
Copy Markdown
Collaborator Author

Summary

Small, well-scoped fix: implements RegulatoryDocumentReader on InfaktInvoicingAdapter against the real pdf.json endpoint, removes the dead pdf_url/print_url fields, and wires a "Download PDF" button on the FE. The pattern (capped streaming binary read, UnsupportedRegulatoryDocumentKindError for unsupported kinds, hook shape) faithfully mirrors the existing KSeF RegulatoryDocumentReader implementation, and the port contract (kind?: RegulatoryDocumentKind = 'confirmation') is honored correctly. The main concern is a missing null-guard on record.providerInvoiceId in the new adapter method — inconsistent with this same file's own established pattern two methods above.

Issues

[IMPORTANT]libs/integrations/infakt/src/infrastructure/adapters/infakt-invoicing.adapter.ts (line ~510)

getRegulatoryDocument builds the request path as `invoices/${record.providerInvoiceId}/pdf.json` without checking for null, even though InvoiceRecord.providerInvoiceId is typed string | null. This adapter already has an established guard for exactly this in getClearanceStatus (line 357: if (!record.providerInvoiceId) { return { regulatoryStatus: 'not-applicable' }; }), a few methods above. Without the same guard here, a null id silently builds the literal path invoices/null/pdf.json and hits the Infakt API with a nonsensical URL instead of failing predictably (e.g. via a clear domain exception). The port docstring says "callers gate on the record being cleared before invoking," which reduces the odds of hitting this in practice, but the existing in-file precedent shows the adapter itself doesn't currently rely on callers to enforce that — it guards defensively. No test exercises a providerInvoiceId: null record either. Add the same early guard (or throw a clear exception) before building the URL.

[SUGGESTION]apps/web/src/features/invoicing/hooks/use-invoice-rendered-document-download.ts

triggerBlobDownload (lines ~45-54) and extensionForBlob/EXTENSION_BY_MIME are copy-pasted verbatim (down to the comment about deferred revokeObjectURL) from use-ksef-upo-download.ts. Per engineering-standards.md's "prefer reusing existing abstractions" principle, this is a good candidate to extract into a shared helper (e.g. features/invoicing/lib/blob-download.ts) that both hooks import, rather than maintaining two copies of the same anchor-click/object-URL lifecycle logic. Not blocking — the PR description explains why the hooks aren't merged (different API routes), but that reasoning doesn't apply to the private download-trigger helper, which is identical in both files.

[SUGGESTION]libs/integrations/infakt/src/infrastructure/http/infakt-http-client.ts (getBinary)

The streaming byte-cap only checks the running total as chunks arrive. KSeF's readBinaryBodyCapped (which this is documented to mirror) additionally pre-checks the advertised Content-Length header before starting to stream, for an earlier fail-fast. Not a correctness or security gap here (the streaming cap alone still bounds memory), just a minor inconsistency with the pattern it says it mirrors — worth adding for parity if convenient, otherwise fine to leave as a smaller v1.

Verdict

🔄 Approve with changes — the providerInvoiceId null-guard (IMPORTANT) should be fixed before merge to keep behavior consistent with this adapter's own established defensive pattern; the two SUGGESTIONs are optional follow-ups.

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Code Review — PR #1323

Scope reviewed: all 12 changed files (Infakt adapter + HTTP client, core RegulatoryDocumentReader wiring, FE hook + Infakt detail-section button, tests, implementation plan doc).

Findings

No BLOCKING, IMPORTANT, or SUGGESTION findings.

Verification performed

  • Confirmed RegulatoryDocumentReader / RegulatoryDocumentKind / UnsupportedRegulatoryDocumentKindError are pre-existing, unmodified core contracts — no new port/capability surface introduced.
  • Confirmed the controller route (GET /invoices/:invoiceId/document?kind=rendered) and FE apiClient.invoicing.downloadDocument(id, 'rendered') contract were already in place and unchanged — this PR only fills in the previously-missing real implementation.
  • Confirmed toInfaktInvoiceType mapping and providerInvoiceId usage are correct against the existing adapter conventions.
  • Confirmed the getBinary streaming-cap implementation correctly bounds memory (same pattern as KsefHttpClient.readBinaryBodyCapped).
  • Confirmed the FE gating (invoice.regulatoryStatus === 'accepted') is consistent with the backend's record.status === 'issued' && record.regulatoryStatus === 'accepted' guard, mirroring the existing KSeF UPO-download gating precedent.
  • Ran and verified green:
    • pnpm --filter @openlinker/integrations-infakt test — 115/115 passing
    • pnpm --filter @openlinker/integrations-infakt lint — clean, no diff from --fix
    • pnpm --filter @openlinker/web type-check — clean
    • FE test infakt-invoice-detail-section.test.tsx — 8/8 passing

Positive observations

  • Correctly kept InvoiceRecord.pdfUrl on core (Subiekt's FE still reads it) while explicitly nulling it out at all 3 Infakt call sites with an explanatory comment pointing at the real path — avoids a needless core-wide breaking change while being explicit about the dead field.
  • getBinary's streaming byte cap mirrors the established KSeF pattern (defense against a mendacious/oversized body) rather than inventing a new approach.
  • Test coverage includes the happy path, missing-content-type fallback, unsupported-kind rejection, and the oversized-body cap — good edge-case coverage on both the adapter and the HTTP client.

Verdict: No issues found. This PR looks ready to merge (note: approval was not submitted by this session since it was flagged as a self-approval — this is the same branch/worktree as the PR author).

@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.

Tech Lead Review — Approve

Clean, well-scoped fix. The adapter now implements the RegulatoryDocumentReader sub-capability for kind: 'rendered', routing through the existing neutral GET /invoices/:invoiceId/document?kind=rendered controller instead of the phantom pdf_url field. Verified end to end:

  • Capability + guard: getRegulatoryDocument(record, kind) matches the core contract, kind defaults to confirmation (consistent with the interface), and unsupported kinds throw UnsupportedRegulatoryDocumentKindError → the controller maps that to 409. The controller's availability gate (status === 'issued' && regulatoryStatus === 'accepted') is mirrored exactly by the FE button's accepted gate, so no dead-end 409s from the UI.
  • ADR-026 neutrality: Infakt wire vocab (document_type: 'original', invoice_type: 'vat') stays in the adapter; core only ever sees the neutral kind='rendered'.
  • No Subiekt regression: dead-field removal is confined to the InfaktInvoice wire type. InvoiceRecord.pdfUrl stays on core and Subiekt still populates it (subiekt-invoicing.adapter.ts:166); Infakt just returns null.
  • Binary safety: getBinary streams with a 10 MB accumulation cap mirroring the KSeF client — an oversized/mendacious body can't drive an unbounded read.
  • FE: imperative one-shot hook (no raw fetch, goes through apiClient.invoicing.downloadDocument), object-URL revoke deferred past the click tick. Tests cover the click → downloadDocument(id, 'rendered') path.
  • Test coverage is solid on both halves (adapter happy / no-content-type / unsupported-confirmation / unsupported-source; http-client happy / non-2xx / oversized-cap; FE click).

Suggestions (non-blocking)

  • triggerBlobDownload (object-URL + <a download> + deferred revoke) is now the fifth copy of this primitive across features/invoicing/hooks and features/shipments/lib/label-download.ts. Consider extracting a shared downloadBlob(blob, filename) util. The hook itself is rightly kept separate (documented reason: neutral /document route vs. UPO's dedicated route).
  • Download filename uses the internal ol-invoice-{id}; a provider invoice number would read nicer for operators. Minor.

Merge order: this is stacked on 1282-infakt-fe-plugin (#1300) — it targets that branch, not main, and must not merge before #1300 lands.

Verdict: Approve.

piotrswierzy pushed a commit that referenced this pull request Jul 2, 2026
… correction (#1300)

* feat(infakt): register Infakt plugin in API/worker + wire webhook ingestion

Registers InfaktIntegrationModule in apps/api and apps/worker so the host
can resolve the Infakt 'Invoicing' capability, and wires Infakt's KSeF-relay
webhooks into OL's ADR-021/ADR-015 ingestion pipeline:

- InfaktInboundWebhookDecoderAdapter (provider-keyed): HMAC-SHA256 verify,
  subscription-verification handshake echo, envelope extraction
- InfaktWebhookEventTranslatorAdapter (adapterKey-keyed): maps
  send_to_ksef_success/error to the new `invoicing` inbound domain
- InboundWebhookDecoderPort grows an optional detectHandshake step (ADR-021)
  so a provider's subscription-verification ping can echo a body before
  signature verification — WebhookService/WebhookController thread the
  return value through
- InboundRoutingPolicyService routes `invoicing` events to the existing
  invoicing.regulatoryStatus.reconcile job (webhook as trigger, not source
  of truth — the scheduled reconcile still drains the full frontier)

Closes #1281

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

* fix(infakt): address PR #1293 tech review suggestions

Shorten the wrap-around import path in both webhook adapters
(infrastructure/adapters -> infrastructure/webhooks was going through
infrastructure twice), and replace the dummy-secret
`new InfaktWebhookTranslator({ secret: '' }, logger)` construction with
a named `InfaktWebhookTranslator.forParsing(logger)` factory that makes
the secret-independent-parsing-only intent explicit in the type itself.

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

* fix(infakt): correct field names, KSeF submission, and handshake status found in manual QA

Manual E2E testing against the real Infakt sandbox surfaced 8 issues
blocking real invoice issuance/correction and webhook verification:

- upsertCustomer sent `name`/`post_code`; Infakt's v3 API wants
  `company_name`/`postal_code` (rejected/ignored otherwise)
- issueInvoice hardcoded payment_method: 'transfer', which 422s without
  a configured bank account; defaulted to 'cash' (proven live in the
  June 30 POC)
- issueInvoice/issueCorrection sent client_uuid; Infakt's invoices.json
  only accepts the numeric client_id
- issueCorrection never sent a client at all, so every correction 422'd
- empty InvoiceLine.taxRate (core's documented contract) cascaded into
  a rejection on every single invoice line; added a Polish-standard-VAT
  (23%) fallback
- InfaktInvoicingAdapter.sendToKsef() existed but was never called from
  issueInvoice/issueCorrection (only a standalone POC script called it
  directly) - every Infakt invoice sat in `draft` forever. Now called
  inline, matching how KSeF submits inline and Subiekt transmits
  natively at issuance.
- InfaktInboundWebhookDecoderAdapter.extractEnvelope always routed
  regardless of event name instead of using the `ignore` outcome for
  events OL doesn't act on
- The webhook handshake echo returned 202; Infakt's own verifier only
  accepts 200

Confirmed end-to-end against the real sandbox: issue -> auto KSeF
submission -> real Infakt webhook (real HMAC) -> reconcile job ->
regulatoryStatus=cleared with a real KSeF number, for both the
original invoice and its correction.

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

* fix(infakt): disambiguate verification handshake from signed events

Address review feedback on #1293: gate getVerificationEcho on the
absence of the event envelope so a signed webhook that happens to
carry a verification_code field in its resource is never
mis-short-circuited into the handshake path. Also cap the echoed
verification_code length, since the handshake echo is returned
pre-signature-verification and shouldn't reflect an unbounded
attacker-controlled value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): resolve rebase conflict duplicating taxRateNumeric

Rebasing onto main (which already carries #1292's grossToNet/taxRateNumeric
module-level refactor) alongside this branch's own empty-taxRate fix produced
a duplicate: an unused private taxRateNumeric method plus the real,
still-unpatched module-level function actually called by grossToNet. Fold
the empty-string fallback into the module-level function that's on the real
call path and drop the dead duplicate; extend the two issueCorrection tests
exercising send_to_ksef.json to seed the response now that issueCorrection
always submits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): address PR #1293 tech-review findings on KSeF submission

- Document the retry-safety assumption behind the two-step issue+submit
  flow at both sendToKsef call sites: a retry re-POSTs invoices.json with
  the same external_id, relying on Infakt returning/reusing the same
  invoice uuid rather than creating a duplicate draft, so the follow-up
  sendToKsef becomes a safe re-attempt on the same document.
- Add error-path tests for issueInvoice and issueCorrection covering
  sendToKsef failing after a successful invoice creation, asserting the
  InfaktApiError/failureMode propagates.
- payment_method being hardcoded to 'cash' is tracked separately as #1303
  (no neutral paymentMethod field exists on IssueInvoiceCommand yet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): register ConnectionTesterPort for infakt.accounting.v1

Found during a live E2E walkthrough of the FE plugin (PR #1300): "Test
connection" fails with "Connection testing is not supported for adapter
infakt.accounting.v1" because infakt-plugin.ts never registered a
ConnectionTesterPort. Every other adapter with a Test connection affordance
(Erli, WooCommerce, PrestaShop, InPost, Allegro, Subiekt) has one; Infakt was
the one gap.

Add InfaktConnectionTesterAdapter mirroring SubiektConnectionTesterAdapter's
pattern (Infakt's factory only exposes createInvoicingAdapter, not a bare
HTTP-client construction seam, so credentials are resolved and the client
built directly rather than via a factory method): probes GET clients.json
(cheap, side-effect-free, requires a valid key), maps InfaktApiError to the
neutral ConnectionTestResult, and never throws. Register it in
infakt-plugin.ts alongside the existing validator/retry-classifier/webhook
registrations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): send invoice amounts as plain-integer groszy, not decimal strings

Critical fiscal bug found during live E2E against the real inFakt sandbox:
issued invoices landed on inFakt's dashboard (and were submitted to KSeF)
at ~1/100th of the real order total (e.g. a PLN 349.00 order → PLN 3.48 on
the resulting invoice). Confirmed against both the raw sandbox response
(net_price/tax_price/gross_price returned as plain integers, e.g. 283 for
PLN 2.83) and the official Infakt API schema (unit_net_price/net_price/
gross_price are `integer`, documented "w groszach") that inFakt's wire
format is a plain integer count of groszy (1 PLN = 100 groszy) everywhere
- not the "amount currency" decimal-string format (e.g. "283.74 PLN") the
adapter was previously sending, which the earlier #1292 review had
incorrectly confirmed against the schema.

- InfaktInvoice.gross_price/net_price/tax_price and
  InfaktInvoiceService.unit_net_price/net_price/tax_price/gross_price are
  now typed `number` (plain integer groszy), not `string`.
- Added toGroszy/fromGroszy helpers replacing the removed parseInfaktAmount;
  issueInvoice and issueCorrection now round PLN amounts to integer groszy
  when building the request payload, and convert the original invoice's
  groszy amounts back to PLN decimals before doing gross-to-net arithmetic.
- Updated all affected unit tests (fixtures + assertions) to the integer
  groszy format and to assert exact groszy values rather than decimal
  strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): polish remaining review suggestions on PR #1293

Addresses the three outstanding SUGGESTIONs from the full-PR re-review
(2026-07-01T22:11:12Z), none of which were blocking:

- infakt-inbound-webhook-decoder.adapter.ts: drop the redundant
  parenthesization around parsed.resource['invoice_uuid'] flagged as a
  merge-artifact.
- webhook.controller.ts: add a one-line comment pointing at the
  res.status(HttpStatus.OK) call, since @res is the only place this
  controller reaches for the raw Response object.
- infakt-invoicing.adapter.ts: clarify sendToKsef's public-visibility
  rationale — it already has a real external caller
  (scripts/poc-sandbox-test.ts), not just a hypothetical future one.

Verified: pnpm --filter @openlinker/integrations-infakt test (108
passing), pnpm --filter @openlinker/api test -- webhook (608 passing),
type-check clean on both packages, targeted eslint clean on all three
files.

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

* fix(infakt): map KSeF success status to accepted, not cleared

Found during a live E2E walkthrough (letting the real scheduled
invoicing.regulatoryStatus.reconcile job pick up clearance, rather than
manually poking the DB): toRegulatoryStatus mapped Infakt's terminal
ksef_data.status: 'success' to the neutral 'cleared' instead of 'accepted'.

Per the core RegulatoryStatus contract, 'cleared' is reserved for
split-clearance regimes that no current provider emits; the FE's status
card only branches on submitted/accepted/rejected, so an invoice stuck at
'cleared' rendered as a permanently in-progress "CLEARING" badge with no
clearance-reference chip, even though the invoice had genuinely cleared on
the government side. KSeF's own adapter already maps its terminal 200
status to 'accepted' for the identical reason - Infakt now mirrors that.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(plans): implementation plan for inFakt FE plugin + invoice-section redesign

Covers issue #1282 (guided setup, credentials rotation, invoice detail
section, KOR correction flow) plus the OrderInvoicePanel regulatory-section
host-chrome redesign validated in the approved mockup. Corrects the issue's
original setup-flow description against verified codebase precedent (Erli's
guided-route pattern, not the generic inline create form).

Closes #1282

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

* docs(plans): add E2E Playwright evidence phase to inFakt FE plan

Adds Phase 4: two Playwright walkthrough scripts (connection setup,
invoice issuance through real KSeF clearance + a correction) mirroring
the existing Subiekt/Erli screenshot-evidence convention, plus posting
the resulting docs/assets/infakt/*.png screenshots inline in a PR
comment as end-to-end proof.

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

* feat(web): inFakt FE plugin — connection setup + invoice detail + KOR correction

Implements the frontend half of the inFakt accounting integration
(epic #1279): guided connection setup (name + API key + optional
sandbox baseUrl, mirroring the Erli pattern), credentials rotation,
post-create baseUrl editing, and the invoice-surfacing slots
(regulatory status + KSeF number, KOR correction flow) driven by the
existing generic InvoicingPort/CorrectionIssuer backend contract
(PR #1292/#1293).

Also introduces a shared `.reg-card` severity-stripe treatment for the
invoiceDetailSection provider slot (additive alongside each section's
existing root class, so KSeF/Subiekt keep their current DOM shape and
tests) — inFakt ships with it from day one, KSeF/Subiekt adopt it as a
drive-by improvement.

Closes #1282

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

* test(web): add inFakt E2E screenshot-evidence scripts

Playwright walkthroughs (apps/web/e2e/infakt-connection.mjs,
infakt-invoice.mjs) mirroring the existing Subiekt/Erli proof-capture
convention. Not part of pnpm test / CI — manual-run evidence scripts
against a live stack + the real inFakt sandbox, producing
docs/assets/infakt/*.png for the PR comment and the future operator
setup guide.

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

* docs(infakt): add first 4 manual inFakt-dashboard walkthrough screenshots

Part of the E2E evidence trail for PR #1300 / issue #1282: inFakt
sandbox dashboard login, API key generation page, and the webhook
creation flow (list + new-webhook form). Captured manually against
the real inFakt sandbox dashboard, no secrets visible in frame.

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

* docs(infakt): add real inFakt E2E screenshots (connection, issuance, KOR, list)

Captured against the real inFakt sandbox on a temporary paired stack
(this branch's web build + the 1281-infakt-plugin-registration-webhook
backend branch), confirming the full connect -> issue -> KSeF-clearance
-> correction flow works end to end through OpenLinker:

- 00-05: guided connection setup, Test connection (passing, after the
  ConnectionTesterPort fix landed on PR #1293), connections list
- 12: invoice accepted with real KSeF clearance number
  (8201194127-20260701-A5797F400000-ED)
- 14-16: KOR correction flow, also cleared by the real sandbox
- 17: invoices list showing the original + correction, both accepted
- if1-if4: manual inFakt-dashboard screenshots (API key, webhooks)

Known gaps (tracked, not blocking):
- 13-invoice-detail-page was captured via the /invoices/:invoiceId page,
  but that GET route doesn't exist yet on the 1281 backend branch's base
  main snapshot — will be trivial to recapture once #1292/#1293 land on
  current main.
- The not-issued / submitted (pending) order-detail states aren't
  captured cleanly yet — the seeded test order is now terminal
  (accepted) for this connection; a second seeded order would be needed
  for a clean before/during capture.

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

* docs(infakt): drop broken invoice-detail-page screenshot (route missing on this backend branch, not blocking)

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

* docs(infakt): re-capture E2E screenshots with the money-format fix verified

Re-ran the full sandbox walkthrough on a fresh order (PLN 189.00) after
the groszy/decimal-string fix landed on PR #1293 (651cac2). Confirmed
against inFakt's raw API response that amounts now round-trip exactly
(gross_price: 18900 groszy = PLN 189.00) and the KOR correction combines
original + corrected lines correctly (189.00 + 99.99 = 288.99 PLN).

Also adds the invoice-detail-page screenshot (13) that 404'd on a stale
branch snapshot last time — the route works fine now.

Replaces the earlier PLN 349.00 test order's screenshots, which
(correctly, at the time) showed the ~100x-low amounts that led to the
money-format bug report on PR #1293.

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

* docs(infakt): move E2E screenshots to libs/integrations/infakt/docs/assets/

Aligns with the convention established in PR #1284 (KSeF/Subiekt
tutorials) — screenshot assets live inside the integration package's
own docs/assets/, not a root-level docs/assets/<provider>/ directory.
Updates the two e2e scripts' output path accordingly.

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

* docs(infakt): add clean not-issued/submitted screenshots + fresh Part 6 confirmation

- 08-orders-list.png: clean, filtered (sourceConnectionId + createdFrom)
  view of just the seeded test orders — the unfiltered list is too
  cluttered with other sessions' dev-DB fixtures to be tutorial-usable.
- 10/11: genuinely clean not-issued -> submitted transition, captured
  without a page reload wiping the connection-picker selection (fixed
  infakt-invoice.mjs to shoot the submitted state before any reload).
- if5-infakt-invoice-confirmed.png: fresh manual inFakt-dashboard
  screenshot confirming the money-format fix (9/07/2026 = PLN 189.00,
  10/07/2026 correction = PLN 288.99), replacing the pre-fix evidence.
- 13/17 updated: also documents the KSeF cleared-vs-accepted mapping bug
  found this run (flagged on PR #1293) — two rows show "KSeF: CLEARING"
  (the real reconcile job's output, unpatched) alongside two "KSeF:
  ACCEPTED" rows from earlier runs where I'd manually corrected the DB
  while chasing the money bug. Will re-capture once the accepted-mapping
  fix lands.

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

* fix(web): address inFakt FE plugin tech-review findings

Ports the KSeF correction flow's line-delta validation guard into the
inFakt correction flow so a filled line number without a quantity or
price change is rejected client-side (matching the backend's
HasCorrectionDeltaConstraint) instead of silently 400ing. Extracts the
`.reg-card` tone mapping duplicated across KSeF, Subiekt, and inFakt
detail sections into a single `regCardToneFor` helper on the invoicing
feature barrel. Also applies the review's non-blocking suggestions:
narrows the helper's return type, makes InvoiceCorrectionFlowProps.connection
optional (no implementer reads it), and gives the `.textarea` class and
`.reg-card` note paragraphs real CSS rules instead of relying on bare
element selectors / inline styles.

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

* docs(infakt): re-capture screenshots with the cleared->accepted fix verified

Fourth fresh sandbox order (PLN 259.00), confirming both backend fixes
together: gross_price round-trips exactly (25900 groszy = PLN 259.00),
and the invoice now shows "KSeF: ACCEPTED" with a real clearance
reference chip instead of getting stuck on "KSeF: CLEARING" forever.
Correction verified too (259.00 + 99.99 = 358.99 PLN exact, gross_price
35899 groszy).

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

* feat(web): Infakt default-payment-method picker (wizard + edit disclosure)

Adds a "Default payment method" field to the inFakt connection wizard
(defaults to Transfer, the common case for online-shop invoicing) and
the equivalent field to the edit-connection screen, tucked behind a
new InlineDisclosure primitive (shared/ui) so it reads as an inline
fact ("Payment method for invoice: Cash") rather than a permanently-
open control. Wires apps/web/src/features/connections/components/
infakt-setup-form.tsx, infakt-setup.schema.ts, edit-connection.schema.ts,
EditConnectionForm.tsx and plugins/infakt/components/infakt-structured-
section.tsx to the config.defaultPaymentMethod field introduced by #1303.

Related to #1303.

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

* docs(infakt): re-verify screenshots against current code post tech-review (order PLN 899.00)

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

* docs(infakt): re-capture connection wizard screenshots with payment-method field

Rebuilt the web bundle after pulling the default-payment-method picker
feature (#1303) and re-ran the connection walkthrough — the wizard,
created/test/list states now show the new "Default payment method"
field. Live-verified against the real sandbox (Test connection passes).

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

* fix(web): address inFakt FE plugin PR #1300 tech-review findings

Default the inFakt setup wizard's payment method to cash - transfer 422s
on inFakt unless a bank account is configured, contradicting the help
copy and the adapter's own fallback. Also syncs the stale lazy-route
count comment (49 total, 10 plugin routes) and drops two unused
.reg-card CSS classes plus a raw-px rule in favor of the rem convention.

Signed-off-by: Norbert Kulus <norbert.kulus@silksh.pl>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): implement real PDF download via RegulatoryDocumentReader (#1323)

fix(infakt): implement real PDF download via RegulatoryDocumentReader (#1323)

InfaktInvoicingAdapter read a pdf_url field that does not exist on Infakt's
invoice resource (verified live against the sandbox), so InvoiceRecord.pdfUrl
was always null and the "download PDF" affordance never worked. Infakt does
expose a real PDF at GET /invoices/{uuid}/pdf.json?document_type=original,
so the adapter now implements RegulatoryDocumentReader for kind 'rendered'
against that endpoint, and the FE Infakt invoice detail section gets a
working Download PDF button wired to the existing /document?kind=rendered
route.

Closes #1321

---------

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Signed-off-by: Norbert Kulus <norbert.kulus@silksh.pl>
Co-authored-by: Claude Sonnet 5 <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.

2 participants