Package and individual mail sender, new report, and other changes

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Simon Pocrnjič
2026-05-11 21:32:30 +02:00
parent b6bfa17980
commit e3bc5da7e3
49 changed files with 4754 additions and 249 deletions
+37 -4
View File
@@ -30,17 +30,41 @@ public function render(array $template, array $ctx): array
return preg_replace_callback('/{{\s*([a-zA-Z0-9_.]+)\s*}}/', function ($m) use ($map) {
$key = $m[1];
// body_text is handled separately by applyBodyText(); preserve as literal
if ($key === 'body_text') {
return $m[0];
}
return (string) data_get($map, $key, '');
}, $input);
};
$bodyText = isset($ctx['body_text']) ? (string) $ctx['body_text'] : '';
return [
'subject' => $replacer($template['subject']) ?? '',
'html' => $replacer($template['html'] ?? null) ?? null,
'text' => $replacer($template['text'] ?? null) ?? null,
'html' => $this->applyBodyText($replacer($template['html'] ?? null) ?? null, $bodyText, html: true),
'text' => $this->applyBodyText($replacer($template['text'] ?? null) ?? null, $bodyText, html: false),
];
}
/**
* Substitute the literal {{body_text}} placeholder with the user-supplied body text.
* In HTML context the text is HTML-escaped and newlines are converted to <br>.
* In plain-text context the raw value is used.
*/
public function applyBodyText(?string $content, string $bodyText, bool $html = true): ?string
{
if ($content === null) {
return null;
}
$replacement = $html
? nl2br(htmlspecialchars($bodyText, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'))
: $bodyText;
return preg_replace('/{{\s*body_text\s*}}/', $replacement, $content);
}
/**
* @param array{client?:Client, person?:Person, client_case?:ClientCase, contract?:Contract, activity?:Activity, extra?:array} $ctx
*/
@@ -145,8 +169,11 @@ protected function buildMap(array $ctx): array
'id' => data_get($co, 'id'),
'uuid' => data_get($co, 'uuid'),
'reference' => data_get($co, 'reference'),
// Format amounts in EU style for emails
'amount' => $formatMoneyEu(data_get($co, 'amount')),
// Account amounts — sourced from the related Account model
'account' => [
'balance_amount' => $formatMoneyEu(data_get($co, 'account.balance_amount')),
'initial_amount' => $formatMoneyEu(data_get($co, 'account.initial_amount')),
],
];
$meta = data_get($co, 'meta');
if (is_array($meta)) {
@@ -172,6 +199,12 @@ protected function buildMap(array $ctx): array
if (! empty($ctx['extra']) && is_array($ctx['extra'])) {
$out['extra'] = $ctx['extra'];
}
if (isset($ctx['mail_profile'])) {
$mp = $ctx['mail_profile'];
$out['profile'] = [
'signature' => is_array($mp->signature) ? $mp->signature : [],
];
}
return $out;
}