147 lines
6.0 KiB
PHP
147 lines
6.0 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_version' => ['nullable', 'integer'],
|
|
'custom' => ['nullable', 'array'],
|
|
'custom.*' => ['nullable'],
|
|
]);
|
|
|
|
// Prefer explicitly requested version if provided and active; otherwise use latest active
|
|
$baseQuery = DocumentTemplate::query()
|
|
->where('slug', $request->template_slug)
|
|
->where('core_entity', 'contract')
|
|
->where('active', true);
|
|
if ($request->filled('template_version')) {
|
|
$template = (clone $baseQuery)->where('version', (int) $request->integer('template_version'))->first();
|
|
if (! $template) {
|
|
$template = (clone $baseQuery)->orderByDesc('version')->firstOrFail();
|
|
}
|
|
} else {
|
|
$template = $baseQuery->orderByDesc('version')->firstOrFail();
|
|
}
|
|
|
|
// Load related data minimally
|
|
$contract->load(['clientCase.client.person', 'clientCase.person', 'clientCase.client']);
|
|
|
|
$renderer = app(\App\Services\Documents\DocxTemplateRenderer::class);
|
|
try {
|
|
// For custom tokens: pass overrides via request bag; service already reads request()->input('custom') if present.
|
|
$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) {
|
|
try {
|
|
logger()->error('ContractDocumentGenerationController generation failed', [
|
|
'template_id' => $template->id ?? null,
|
|
'template_slug' => $template->slug ?? null,
|
|
'template_version' => $template->version ?? null,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
} catch (\Throwable $logEx) {
|
|
}
|
|
|
|
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,
|
|
'stats' => $result['stats'] ?? null,
|
|
'template' => [
|
|
'id' => $template->id,
|
|
'slug' => $template->slug,
|
|
'version' => $template->version,
|
|
'file_path' => $template->file_path,
|
|
],
|
|
]);
|
|
}
|
|
}
|