- InvoiceService.ResolveInlineImagesAsync now fetches an invoice's
distinct whitelisted images concurrently (SemaphoreSlim-capped at 4)
instead of one at a time. It ran inline on every invoice render,
including repeated draft-preview renders, so N sequential mfr-portal
round-trips could otherwise add N x latency to a user-facing request.
- Add fuchs.invoice.inlineimages.fetches (counter, tagged by outcome:
success/http_error/bad_content_type/oversized/exception) and
fuchs.invoice.inlineimages.resolve.duration (histogram) to
FuchsTelemetry, matching the project's observability convention for
outbound-HTTP operations.
- MigraDocExtensions.HtmlConverter's pre-resolved-image parameter is
renamed inlineImages -> resolvedImages with generic docs: the
converter fetches nothing and knows nothing about invoices — which
images to resolve, and any whitelist/network policy, is entirely the
caller's concern (InvoiceService, ADR 0018 today; any future caller
tomorrow).
- Add tests asserting fetches actually overlap (not just faster) and
that the new counter/histogram are recorded.
- Update ADR 0018 to describe the concurrent fetch, telemetry, and the
generalized converter parameter.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- MigraDocExtensions: fix a bug where capping an oversized explicit
width+height image re-derived the other dimension from the image's
native pixel ratio instead of scaling both sides uniformly, silently
distorting a deliberately non-native-ratio (e.g. stretched) image.
- InvoiceService: derive the mfr-portal image whitelist prefix from the
existing Fds:MFR_host config setting (via MFRClientConfig.RestRoot)
instead of a second hardcoded literal, so it can't drift from the
actual configured host; parse <img> sources with HtmlAgilityPack
(already used by the PDF's own HTML converter) instead of a regex, so
a decoy attribute like data-src can't be picked up in place of the
real src; dedup fetch URLs with a HashSet instead of O(n^2) List
scanning; reuse FuchsPdf.MigraDocFilenameFromByteArray instead of
re-spelling the "base64:" prefix.
- Fix a stale log message and comment left over from the
GenerateInvoicePdf -> GenerateInvoicePdfAsync rename, and update
INVOICE_LIFECYCLE.md's references to the old sync method name.
- Add tests for the config-derived whitelist, a decoy-attribute src,
and the 20-image-per-invoice fetch cap boundary.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The invoice line editor already shows <img> pictures pulled from the mfr
portal, but the PDF pipeline's HTML-to-MigraDoc converter had no <img>
handler, so they never appeared on the actual invoice.
InvoiceService now fetches only https://portal.mobilefieldreport.com/mfr/*
image sources before rendering (size/type/count capped, failures logged and
skipped) and hands the resolved images down through ApplyInvoice into the
HTML converter, which renders them inline at their original HTML position
and size (px converted to cm, capped against layout overflow). See ADR 0018.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>