Fix 500 generation: include account entity in template defaults and merge global whitelist entities during resolution
This commit is contained in:
parent
18fb04fe65
commit
0c8d1e0b5d
170
app/Http/Controllers/Admin/DocumentTemplateController.php
Normal file
170
app/Http/Controllers/Admin/DocumentTemplateController.php
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreDocumentTemplateRequest;
|
||||
use App\Http\Requests\UpdateDocumentTemplateRequest;
|
||||
use App\Models\DocumentTemplate;
|
||||
use App\Services\Documents\TokenScanner;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class DocumentTemplateController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$this->ensurePermission();
|
||||
$templates = DocumentTemplate::query()->orderByDesc('updated_at')->get();
|
||||
|
||||
return Inertia::render('Admin/DocumentTemplates/Index', [
|
||||
'templates' => $templates,
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggleActive(DocumentTemplate $template)
|
||||
{
|
||||
$this->ensurePermission();
|
||||
$template->active = ! $template->active;
|
||||
$template->updated_by = Auth::id();
|
||||
$template->save();
|
||||
|
||||
return redirect()->back()->with('success', 'Status predloge posodobljen.');
|
||||
}
|
||||
|
||||
public function updateSettings(UpdateDocumentTemplateRequest $request, DocumentTemplate $template)
|
||||
{
|
||||
$this->ensurePermission();
|
||||
$template->fill($request->only(['output_filename_pattern', 'date_format']));
|
||||
if ($request->has('fail_on_unresolved')) {
|
||||
$template->fail_on_unresolved = (bool) $request->boolean('fail_on_unresolved');
|
||||
}
|
||||
// Build formatting options array from discrete fields if provided
|
||||
$fmt = $template->formatting_options ?? [];
|
||||
$dirty = false;
|
||||
foreach ([
|
||||
'number_decimals', 'decimal_separator', 'thousands_separator',
|
||||
'currency_symbol', 'currency_position',
|
||||
] as $key) {
|
||||
if ($request->filled($key)) {
|
||||
$fmt[$key] = $request->input($key);
|
||||
$dirty = true;
|
||||
} elseif ($request->has($key) && $request->input($key) === null) {
|
||||
unset($fmt[$key]);
|
||||
$dirty = true;
|
||||
}
|
||||
}
|
||||
if ($request->has('currency_space')) {
|
||||
$fmt['currency_space'] = (bool) $request->boolean('currency_space');
|
||||
$dirty = true;
|
||||
}
|
||||
if ($request->filled('default_date_format')) {
|
||||
$fmt['default_date_format'] = $request->input('default_date_format');
|
||||
$dirty = true;
|
||||
}
|
||||
if ($request->has('date_formats')) {
|
||||
$fmt['date_formats'] = array_filter((array) $request->input('date_formats'), fn ($v) => $v !== null && $v !== '');
|
||||
$dirty = true;
|
||||
}
|
||||
if ($dirty) {
|
||||
$template->formatting_options = $fmt;
|
||||
}
|
||||
$template->updated_by = Auth::id();
|
||||
$template->save();
|
||||
|
||||
return redirect()->back()->with('success', 'Nastavitve predloge shranjene.');
|
||||
}
|
||||
|
||||
public function store(StoreDocumentTemplateRequest $request)
|
||||
{
|
||||
$this->ensurePermission();
|
||||
$file = $request->file('file');
|
||||
|
||||
// Basic extension guard (defense in depth vs only MIME detection)
|
||||
if (strtolower($file->getClientOriginalExtension()) !== 'docx') {
|
||||
return redirect()->back()->withErrors(['file' => 'Datoteka mora biti DOCX.']);
|
||||
}
|
||||
|
||||
$slug = Str::slug($request->slug);
|
||||
|
||||
// Determine next version if slug exists
|
||||
$latest = DocumentTemplate::where('slug', $slug)->orderByDesc('version')->first();
|
||||
$nextVersion = $latest ? ($latest->version + 1) : 1;
|
||||
|
||||
$hash = hash_file('sha256', $file->getRealPath());
|
||||
$path = $file->store("document-templates/{$slug}/v{$nextVersion}", 'public');
|
||||
|
||||
// Scan tokens from uploaded DOCX (best effort)
|
||||
$tokens = [];
|
||||
try {
|
||||
/** @var TokenScanner $scanner */
|
||||
$scanner = app(TokenScanner::class);
|
||||
$zip = new \ZipArchive;
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'tmpl');
|
||||
copy($file->getRealPath(), $tmp);
|
||||
if ($zip->open($tmp) === true) {
|
||||
$xml = $zip->getFromName('word/document.xml');
|
||||
if ($xml !== false) {
|
||||
$tokens = $scanner->scan($xml);
|
||||
}
|
||||
$zip->close();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// swallow scanning errors
|
||||
}
|
||||
|
||||
// (Future) Could refine allowed columns automatically based on tokens
|
||||
$entities = ['contract', 'client_case', 'client', 'person', 'account'];
|
||||
$columns = [
|
||||
'contract' => ['reference', 'start_date', 'end_date', 'description'],
|
||||
'client_case' => ['client_ref'],
|
||||
'client' => [],
|
||||
'person' => ['full_name', 'first_name', 'last_name', 'nu'],
|
||||
// Add common account attributes; whitelist may further refine
|
||||
'account' => ['reference', 'initial_amount', 'balance_amount', 'promise_date'],
|
||||
];
|
||||
|
||||
$payload = [
|
||||
'name' => $request->name,
|
||||
'slug' => $slug,
|
||||
'custom_name' => $request->custom_name,
|
||||
'description' => $request->description,
|
||||
'core_entity' => 'contract',
|
||||
'entities' => $entities,
|
||||
'columns' => $columns,
|
||||
'version' => $nextVersion,
|
||||
'engine' => 'tokens',
|
||||
'file_path' => $path,
|
||||
'file_hash' => $hash,
|
||||
'file_size' => $file->getSize(),
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'active' => true,
|
||||
'created_by' => $latest ? $latest->created_by : Auth::id(), // preserve original author for lineage if re-upload
|
||||
'updated_by' => Auth::id(),
|
||||
'formatting_options' => [
|
||||
'number_decimals' => 2,
|
||||
'decimal_separator' => ',',
|
||||
'thousands_separator' => '.',
|
||||
'currency_symbol' => '€',
|
||||
'currency_position' => 'after',
|
||||
'currency_space' => true,
|
||||
],
|
||||
];
|
||||
if (Schema::hasColumn('document_templates', 'tokens')) {
|
||||
$payload['tokens'] = $tokens;
|
||||
}
|
||||
$template = DocumentTemplate::create($payload);
|
||||
|
||||
return redirect()->back()->with('success', 'Predloga uspešno shranjena. (v'.$template->version.')')->with('template_id', $template->id);
|
||||
}
|
||||
|
||||
private function ensurePermission(): void
|
||||
{
|
||||
if (Gate::denies('manage-document-templates') && Gate::denies('manage-settings')) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,12 @@ public function resolve(array $tokens, DocumentTemplate $template, Contract $con
|
|||
$configWhitelist = config('documents.whitelist', []);
|
||||
// Merge preserving DB additions/overrides
|
||||
$globalWhitelist = array_replace($configWhitelist, $settingsWhitelist);
|
||||
$templateEntities = $template->entities ?: array_keys($globalWhitelist);
|
||||
// Always treat globally whitelisted entities as available, even if legacy template does not list them
|
||||
if ($template->entities && is_array($template->entities)) {
|
||||
$templateEntities = array_values(array_unique(array_merge($template->entities, array_keys($globalWhitelist))));
|
||||
} else {
|
||||
$templateEntities = array_keys($globalWhitelist);
|
||||
}
|
||||
foreach ($tokens as $token) {
|
||||
[$entity,$attr] = explode('.', $token, 2);
|
||||
if ($entity === 'generation') {
|
||||
|
|
@ -80,6 +85,7 @@ private function entityAttribute(string $entity, string $attr, Contract $contrac
|
|||
return (string) $person->{$attr};
|
||||
case 'account':
|
||||
$account = optional($contract->account);
|
||||
|
||||
return (string) $account->{$attr};
|
||||
default:
|
||||
return '';
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user