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); } } }