Teren-app/app/Http/Controllers/ContractDocumentGenerationController.php

118 lines
4.6 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Events\DocumentGenerated;
use App\Models\Activity;
use App\Models\Contract;
use App\Models\Document;
use App\Models\DocumentTemplate;
use App\Services\Documents\TokenValueResolver;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class ContractDocumentGenerationController extends Controller
{
public function __invoke(Request $request, Contract $contract): Response
{
if (Gate::denies('read')) { // baseline read permission required to generate
abort(403);
}
$request->validate([
'template_slug' => ['required', 'string', 'exists:document_templates,slug'],
]);
$template = DocumentTemplate::where('slug', $request->template_slug)
->where('core_entity', 'contract')
->where('active', true)
->orderByDesc('version')
->firstOrFail();
// Load related data minimally
$contract->load(['clientCase.client.person', 'clientCase.person', 'clientCase.client']);
$renderer = app(\App\Services\Documents\DocxTemplateRenderer::class);
try {
$result = $renderer->render($template, $contract, Auth::user());
} catch (\App\Services\Documents\Exceptions\UnresolvedTokensException $e) {
return response()->json([
'status' => 'error',
'message' => 'Unresolved tokens detected.',
'tokens' => $e->unresolved ?? [],
], 422);
} catch (\Throwable $e) {
return response()->json([
'status' => 'error',
'message' => 'Generation failed.',
], 500);
}
$doc = new Document;
$doc->fill([
'uuid' => (string) Str::uuid(),
'name' => $result['fileName'],
'description' => 'Generated from template '.$template->slug.' v'.$template->version,
'user_id' => Auth::id(),
'disk' => 'public',
'path' => $result['relativePath'],
'file_name' => $result['fileName'],
'original_name' => $result['fileName'],
'extension' => 'docx',
'mime_type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'size' => $result['size'],
'checksum' => $result['checksum'],
'is_public' => true,
'template_id' => $template->id,
'template_version' => $template->version,
]);
$contract->documents()->save($doc);
// Dispatch domain event
event(new DocumentGenerated($doc));
// Optional: create an activity if template links to action/decision
if (($template->action_id || $template->decision_id || $template->activity_note_template) && $contract->client_case_id) {
try {
$note = null;
if ($template->activity_note_template) {
// Interpolate tokens in note using existing resolver logic (non-failing policy: keep)
/** @var TokenValueResolver $resolver */
$resolver = app(TokenValueResolver::class);
$rawNote = $template->activity_note_template;
$tokens = [];
if (preg_match_all('/\{([a-zA-Z0-9_\.]+)\}/', $rawNote, $m)) {
$tokens = array_unique($m[1]);
}
$values = [];
if ($tokens) {
$resolved = $resolver->resolve($tokens, $template, $contract, Auth::user(), 'keep');
foreach ($resolved['values'] as $k => $v) {
$values['{'.$k.'}'] = $v;
}
}
$note = strtr($rawNote, $values);
}
Activity::create(array_filter([
'note' => $note,
'action_id' => $template->action_id,
'decision_id' => $template->decision_id,
'contract_id' => $contract->id,
'client_case_id' => $contract->client_case_id,
], fn ($v) => ! is_null($v) && $v !== ''));
} catch (\Throwable $e) {
// swallow activity creation errors to not block document generation
}
}
return response()->json([
'status' => 'ok',
'document_uuid' => $doc->uuid,
'path' => $doc->path,
]);
}
}