feat(freight-api): seal EDR side of contracts with the one global stamp

This commit is contained in:
ghost2023
2026-08-12 06:26:46 +03:00
parent e52d666647
commit a603807e8e
21 changed files with 679 additions and 183 deletions

View File

@@ -49,14 +49,30 @@ export class StampSettingsService {
}
/**
* The stamp image for embedding into invoice PDFs. Never throws — invoice
* generation must succeed even if the stamp lookup fails; callers fall back
* to the programmatic seal when this returns null.
* The stamp image for embedding into generated documents, ALWAYS as a
* `data:` URL or null. Never throws — document generation must succeed even
* if the stamp lookup fails; callers fall back to their own seal on null.
*
* The data-URL-or-null guarantee is load-bearing, not cosmetic. Callers do
* two things with this value that a bare MinIO URL silently corrupts:
* ContractTransitionService base64-decodes it to snapshot the seal onto a
* signature row (a URL decodes to garbage bytes, not an error, permanently
* sealing an executed contract with a broken image), and the HTML render
* path inlines it into an <img> that headless Chromium cannot fetch. So
* where getView() may hand a raw URL to a browser that can load it, this
* degrades to null and lets the caller draw its text/vector seal instead.
*/
async getStampImageUrl(): Promise<string | null> {
try {
const setting = await this.get();
return await this.inlineImageUrl(setting.stampFile?.url);
const inlined = await this.inlineImageUrl(setting.stampFile?.url);
if (inlined && !inlined.startsWith("data:")) {
this.logger.warn(
`Company stamp could not be inlined for document rendering (falling back to the plain seal): ${inlined}`,
);
return null;
}
return inlined;
} catch (err) {
this.logger.warn(
`Could not load company stamp for PDF rendering: ${(err as Error).message}`,