Package and individual mail sender, new report, and other changes
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -921,6 +921,18 @@ public function show(ClientCase $clientCase)
|
||||
->select(['id', 'name', 'content', 'allow_custom_body'])
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
'email_templates' => \App\Models\EmailTemplate::query()
|
||||
->select(['id', 'name', 'subject_template', 'text_template', 'action_id', 'decision_id'])
|
||||
->where('active', true)
|
||||
->where('client', false)
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
'mail_profiles' => \App\Models\MailProfile::query()
|
||||
->select(['id', 'name'])
|
||||
->where('active', true)
|
||||
->orderBy('priority')
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
'auto_mail_decisions' => \App\Models\Decision::query()->where('auto_mail', true)->orderBy('name')->get(['id', 'name']),
|
||||
]);
|
||||
}
|
||||
@@ -1575,6 +1587,161 @@ public function previewSms(ClientCase $clientCase, Request $request, SmsService
|
||||
* Extracts 'value' from objects with {title, value, type} structure.
|
||||
* Also creates direct access aliases for nested fields (skipping numeric keys).
|
||||
*/
|
||||
/**
|
||||
* Render an email template preview with context from the client case.
|
||||
*/
|
||||
public function previewEmailForEmail(ClientCase $clientCase, Request $request, int $email_id): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'template_id' => ['required', 'integer', 'exists:email_templates,id'],
|
||||
'contract_uuid' => ['sometimes', 'nullable', 'uuid'],
|
||||
'body_text' => ['sometimes', 'nullable', 'string', 'max:10000'],
|
||||
]);
|
||||
|
||||
$email = \App\Models\Email::query()
|
||||
->where('id', $email_id)
|
||||
->where('person_id', $clientCase->person_id)
|
||||
->firstOrFail();
|
||||
|
||||
$template = \App\Models\EmailTemplate::findOrFail((int) $validated['template_id']);
|
||||
|
||||
$contract = null;
|
||||
if (! empty($validated['contract_uuid'])) {
|
||||
$contract = $clientCase->contracts()
|
||||
->where('uuid', $validated['contract_uuid'])
|
||||
->first();
|
||||
}
|
||||
|
||||
$ctx = $this->buildCaseEmailContext($clientCase, $contract);
|
||||
$ctx['body_text'] = (string) ($validated['body_text'] ?? '');
|
||||
|
||||
$renderer = app(\App\Services\EmailTemplateRenderer::class);
|
||||
$rendered = $renderer->render([
|
||||
'subject' => (string) $template->subject_template,
|
||||
'html' => (string) $template->html_template,
|
||||
'text' => (string) $template->text_template,
|
||||
], $ctx);
|
||||
|
||||
return response()->json([
|
||||
'subject' => $rendered['subject'] ?? '',
|
||||
'html' => (string) ($rendered['html'] ?? ''),
|
||||
'has_body_text' => (bool) preg_match('/{{\s*body_text\s*}}/', (string) $template->html_template),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a (possibly templated) email to a person email address belonging to this case.
|
||||
*/
|
||||
public function sendEmailToEmail(ClientCase $clientCase, Request $request, int $email_id)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'subject' => ['required', 'string', 'max:255'],
|
||||
'html_body' => ['nullable', 'string'],
|
||||
'body_text' => ['nullable', 'string', 'max:10000'],
|
||||
'template_id' => ['sometimes', 'nullable', 'integer', 'exists:email_templates,id'],
|
||||
'mail_profile_id' => ['sometimes', 'nullable', 'integer', 'exists:mail_profiles,id'],
|
||||
'contract_uuid' => ['sometimes', 'nullable', 'uuid'],
|
||||
]);
|
||||
|
||||
// Ensure the email belongs to the person of this case
|
||||
$email = \App\Models\Email::query()
|
||||
->where('id', $email_id)
|
||||
->where('person_id', $clientCase->person_id)
|
||||
->firstOrFail();
|
||||
|
||||
$to = (string) $email->value;
|
||||
|
||||
/** @var \App\Models\MailProfile|null $mailProfile */
|
||||
$mailProfile = ! empty($validated['mail_profile_id'])
|
||||
? \App\Models\MailProfile::query()->where('id', $validated['mail_profile_id'])->where('active', true)->first()
|
||||
: \App\Models\MailProfile::query()->where('active', true)->orderBy('priority')->orderBy('id')->first();
|
||||
|
||||
if (! $mailProfile) {
|
||||
return back()->with('error', 'Ni aktivnega e-poštnega profila.');
|
||||
}
|
||||
|
||||
$contract = null;
|
||||
if (! empty($validated['contract_uuid'])) {
|
||||
$contract = $clientCase->contracts()->where('uuid', $validated['contract_uuid'])->first();
|
||||
}
|
||||
|
||||
$htmlBody = (string) ($validated['html_body'] ?? '');
|
||||
$bodyText = (string) ($validated['body_text'] ?? '');
|
||||
|
||||
// Apply {{body_text}} substitution if the html body contains the placeholder
|
||||
if ($bodyText !== '' && preg_match('/{{\s*body_text\s*}}/', $htmlBody)) {
|
||||
$renderer = app(\App\Services\EmailTemplateRenderer::class);
|
||||
$htmlBody = $renderer->applyBodyText($htmlBody, $bodyText, html: true) ?? $htmlBody;
|
||||
}
|
||||
|
||||
$subject = (string) $validated['subject'];
|
||||
|
||||
$log = new \App\Models\EmailLog;
|
||||
$log->fill([
|
||||
'uuid' => (string) \Illuminate\Support\Str::uuid(),
|
||||
'template_id' => $validated['template_id'] ?? null,
|
||||
'mail_profile_id' => $mailProfile->id,
|
||||
'to_email' => $to,
|
||||
'to_recipients' => [$to],
|
||||
'subject' => $subject,
|
||||
'body_html_hash' => $htmlBody !== '' ? hash('sha256', $htmlBody) : null,
|
||||
'body_text_preview' => null,
|
||||
'embed_mode' => 'base64',
|
||||
'status' => \App\Models\EmailLogStatus::Queued,
|
||||
'queued_at' => now(),
|
||||
'client_id' => $clientCase->client_id,
|
||||
'client_case_id' => $clientCase->id,
|
||||
'contract_id' => $contract?->id,
|
||||
'ip' => $request->ip(),
|
||||
]);
|
||||
$log->save();
|
||||
|
||||
$log->body()->create([
|
||||
'body_html' => $htmlBody,
|
||||
'body_text' => $bodyText,
|
||||
'inline_css' => false,
|
||||
]);
|
||||
|
||||
dispatch(new \App\Jobs\SendEmailTemplateJob($log->id));
|
||||
|
||||
// Create activity if template has action/decision
|
||||
if (! empty($validated['template_id'])) {
|
||||
$template = \App\Models\EmailTemplate::find((int) $validated['template_id']);
|
||||
if ($template && ($template->action_id || $template->decision_id)) {
|
||||
$activity = $clientCase->activities()->create(array_filter([
|
||||
'contract_id' => $contract?->id,
|
||||
'action_id' => $template->action_id,
|
||||
'decision_id' => $template->decision_id,
|
||||
'note' => 'Poslano: '.$to.($bodyText !== '' ? ' | Vsebina: '.mb_strimwidth($bodyText, 0, 500, '…') : ''),
|
||||
'user_id' => optional($request->user())->id,
|
||||
], fn ($v) => ! is_null($v)));
|
||||
$activity->emailLogs()->attach($log->id);
|
||||
}
|
||||
}
|
||||
|
||||
return back()->with('success', "E-pošta poslana na {$to}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a template rendering context from the given client case and optional contract.
|
||||
*/
|
||||
private function buildCaseEmailContext(ClientCase $clientCase, ?\App\Models\Contract $contract = null): array
|
||||
{
|
||||
$clientCase->loadMissing('client.person');
|
||||
$ctx = [
|
||||
'client_case' => $clientCase,
|
||||
'client' => $clientCase->client,
|
||||
'person' => optional($clientCase->client)->person,
|
||||
'mail_profile' => \App\Models\MailProfile::query()->where('active', true)->orderBy('priority')->orderBy('id')->first(),
|
||||
];
|
||||
if ($contract) {
|
||||
$contract->loadMissing(['clientCase.client.person', 'account.type']);
|
||||
$ctx['contract'] = $contract;
|
||||
}
|
||||
|
||||
return $ctx;
|
||||
}
|
||||
|
||||
private function flattenMeta(array $meta, string $prefix = ''): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
Reference in New Issue
Block a user