From ec6456cf2338c2bc656b1a9805676a020c95e6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Pocrnji=C4=8D?= Date: Sun, 12 Oct 2025 19:07:41 +0200 Subject: [PATCH] updated document --- .../ContractDocumentGenerationController.php | 72 ++++-- app/Services/Documents/TokenValueResolver.php | 4 - .../Pages/Admin/DocumentTemplates/Index.vue | 2 +- .../js/Pages/Cases/Partials/ContractTable.vue | 208 +++++++++++++----- 4 files changed, 205 insertions(+), 81 deletions(-) diff --git a/app/Http/Controllers/ContractDocumentGenerationController.php b/app/Http/Controllers/ContractDocumentGenerationController.php index 04b8633..89b8f65 100644 --- a/app/Http/Controllers/ContractDocumentGenerationController.php +++ b/app/Http/Controllers/ContractDocumentGenerationController.php @@ -13,11 +13,15 @@ use Illuminate\Support\Facades\Gate; use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\Response; +use Illuminate\Http\RedirectResponse; class ContractDocumentGenerationController extends Controller { - public function __invoke(Request $request, Contract $contract): Response + public function __invoke(Request $request, Contract $contract): Response|RedirectResponse { + // Inertia requests include the X-Inertia header and should receive redirects or Inertia responses, not JSON + $isInertia = (bool) $request->header('X-Inertia'); + $wantsJson = ! $isInertia && ($request->expectsJson() || $request->wantsJson()); if (Gate::denies('read')) { // baseline read permission required to generate abort(403); } @@ -50,11 +54,18 @@ public function __invoke(Request $request, Contract $contract): Response // 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); + if ($wantsJson) { + return response()->json([ + 'status' => 'error', + 'message' => 'Unresolved tokens detected.', + 'tokens' => $e->unresolved ?? [], + ], 500); + } + + // Return back with validation-like errors so Inertia can surface them via onError + return back()->withErrors([ + 'document' => 'Unresolved tokens detected.', + ])->with('unresolved_tokens', $e->unresolved ?? []); } catch (\Throwable $e) { try { logger()->error('ContractDocumentGenerationController generation failed', [ @@ -66,10 +77,16 @@ public function __invoke(Request $request, Contract $contract): Response } catch (\Throwable $logEx) { } - return response()->json([ - 'status' => 'error', - 'message' => 'Generation failed.', - ], 500); + if ($wantsJson) { + return response()->json([ + 'status' => 'error', + 'message' => 'Generation failed.', + ], 500); + } + + return back()->withErrors([ + 'document' => 'Generation failed.', + ]); } $doc = new Document; @@ -130,16 +147,31 @@ public function __invoke(Request $request, Contract $contract): Response } } - 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, + if ($wantsJson) { + 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, + ], + ]); + } + + // Flash some lightweight info if needed by the UI; Inertia will GET the page after redirect + return back()->with([ + 'doc_generated' => [ + 'uuid' => $doc->uuid, + 'path' => $doc->path, + 'template' => [ + 'slug' => $template->slug, + 'version' => $template->version, + ], + 'stats' => $result['stats'] ?? null, ], ]); } diff --git a/app/Services/Documents/TokenValueResolver.php b/app/Services/Documents/TokenValueResolver.php index 9c60af7..25250b6 100644 --- a/app/Services/Documents/TokenValueResolver.php +++ b/app/Services/Documents/TokenValueResolver.php @@ -136,10 +136,6 @@ public function resolve( } } } - // If still not allowed, permit tokens explicitly scanned/stored on the template - if (! $isAllowed && $templateTokens) { - $isAllowed = in_array($token, $templateTokens, true); - } if (! $isAllowed) { if ($policy === 'fail') { throw new \RuntimeException("Nedovoljen stolpec token: $token"); diff --git a/resources/js/Pages/Admin/DocumentTemplates/Index.vue b/resources/js/Pages/Admin/DocumentTemplates/Index.vue index ea5d729..32978a0 100644 --- a/resources/js/Pages/Admin/DocumentTemplates/Index.vue +++ b/resources/js/Pages/Admin/DocumentTemplates/Index.vue @@ -109,7 +109,7 @@ const groups = computed(() => { emit("add-activity", c); // CaseObject dialog state import { ref, computed } from "vue"; import { router, useForm } from "@inertiajs/vue3"; -import axios from "axios"; +import DialogModal from "@/Components/DialogModal.vue"; // Document generation state/dialog const generating = ref({}); // contract_uuid => boolean const generatedDocs = ref({}); // contract_uuid => { uuid, path } @@ -173,22 +173,37 @@ const personAddressSource = ref("case_person"); // for person.person_address.* const clientAddress = computed(() => { const addr = props.client?.person?.addresses?.[0] || null; return addr - ? { address: addr.address || "", post_code: addr.post_code || "", city: addr.city || "" } + ? { + address: addr.address || "", + post_code: addr.post_code || "", + city: addr.city || "", + } : { address: "", post_code: "", city: "" }; }); const casePersonAddress = computed(() => { const addr = props.client_case?.person?.addresses?.[0] || null; return addr - ? { address: addr.address || "", post_code: addr.post_code || "", city: addr.city || "" } + ? { + address: addr.address || "", + post_code: addr.post_code || "", + city: addr.city || "", + } : { address: "", post_code: "", city: "" }; }); -const customTokenList = computed(() => (templateTokens.value || []).filter((t) => t.startsWith("custom."))); +const customTokenList = computed(() => + (templateTokens.value || []).filter((t) => t.startsWith("custom.")) +); function openGenerateDialog(c) { generateFor.value = c; // Prefer a template that actually has tokens; fallback to the first available - const first = (props.templates || []).find(t => Array.isArray(t?.tokens) && t.tokens.length > 0) || (props.templates || [])[0] || null; + const first = + (props.templates || []).find( + (t) => Array.isArray(t?.tokens) && t.tokens.length > 0 + ) || + (props.templates || [])[0] || + null; selectedTemplateSlug.value = first?.slug || null; templateTokens.value = Array.isArray(first?.tokens) ? first.tokens : []; templateCustomDefaults.value = (first?.meta && first.meta.custom_defaults) || {}; @@ -225,8 +240,14 @@ async function submitGenerate() { generating.value[c.uuid] = true; generationError.value[c.uuid] = null; try { - const clientAddr = clientAddressSource.value === "case_person" ? casePersonAddress.value : clientAddress.value; - const personAddr = personAddressSource.value === "case_person" ? casePersonAddress.value : clientAddress.value; + const clientAddr = + clientAddressSource.value === "case_person" + ? casePersonAddress.value + : clientAddress.value; + const personAddr = + personAddressSource.value === "case_person" + ? casePersonAddress.value + : clientAddress.value; const token_overrides = { "client.person.person_address.address": clientAddr.address, "client.person.person_address.post_code": clientAddr.post_code, @@ -240,33 +261,27 @@ async function submitGenerate() { template_version: tpl.version, custom: { ...customInputs.value }, token_overrides, - unresolved_policy: 'fail', + unresolved_policy: "fail", }; - const { data } = await axios.post( + await router.post( route("contracts.generate-document", { contract: c.uuid }), - payload - ); - if (data.status === "ok") { - generatedDocs.value[c.uuid] = { uuid: data.document_uuid, path: data.path }; - // if no tokens were found/replaced, surface a gentle warning inline - const stats = data.stats || null; - // Show warning only when zero tokens were found in the template (most common real issue) - if (stats && stats.tokensFound === 0) { - generationError.value[c.uuid] = "Opozorilo: V predlogi niso bili najdeni tokeni."; - } else { - generationError.value[c.uuid] = null; + payload, + { + preserveScroll: true, + onSuccess: () => { + // Close dialog and refresh documents list + generationError.value[c.uuid] = null; + showGenerateDialog.value = false; + router.reload({ only: ["documents"] }); + }, + onError: () => { + // Typically 422 validation-like errors + generationError.value[c.uuid] = "Manjkajoči tokeni v predlogi."; + }, } - showGenerateDialog.value = false; - router.reload({ only: ["documents"] }); - } else { - generationError.value[c.uuid] = data.message || "Napaka pri generiranju."; - } + ); } catch (e) { - if (e?.response?.status === 422) { - generationError.value[c.uuid] = "Manjkajoči tokeni v predlogi."; - } else { - generationError.value[c.uuid] = "Neuspešno generiranje."; - } + generationError.value[c.uuid] = "Neuspešno generiranje."; } finally { generating.value[c.uuid] = false; } @@ -739,7 +754,7 @@ const closePaymentsDialog = () => { { /> -
-
-
Generiraj dokument
+ + +