Other permissions changed

This commit is contained in:
Simon Pocrnjič 2025-10-31 12:26:58 +01:00
parent 07b1deda21
commit 0d9c8c8b30
7 changed files with 180 additions and 133 deletions

View File

@ -35,6 +35,7 @@ const props = defineProps({
downloadUrlBuilder: { type: Function, default: null }, downloadUrlBuilder: { type: Function, default: null },
// Optional: direct delete URL builder; if absent we emit 'delete' // Optional: direct delete URL builder; if absent we emit 'delete'
deleteUrlBuilder: { type: Function, default: null }, deleteUrlBuilder: { type: Function, default: null },
edit: { type: Boolean, default: false },
}); });
// Derive a human-friendly source for a document: Case or Contract reference // Derive a human-friendly source for a document: Case or Contract reference
const sourceLabel = (doc) => { const sourceLabel = (doc) => {
@ -322,6 +323,7 @@ function closeActions() {
type="button" type="button"
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2" class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
@click="emit('edit', doc)" @click="emit('edit', doc)"
v-if="edit"
> >
<FontAwesomeIcon :icon="faCircleInfo" class="h-4 w-4 text-gray-600" /> <FontAwesomeIcon :icon="faCircleInfo" class="h-4 w-4 text-gray-600" />
<span>Uredi</span> <span>Uredi</span>
@ -338,6 +340,7 @@ function closeActions() {
type="button" type="button"
class="w-full px-3 py-2 text-left text-sm text-red-600 hover:bg-red-50 flex items-center gap-2" class="w-full px-3 py-2 text-left text-sm text-red-600 hover:bg-red-50 flex items-center gap-2"
@click="askDelete(doc)" @click="askDelete(doc)"
v-if="edit"
> >
<FontAwesomeIcon :icon="faTrash" class="h-4 w-4" /> <FontAwesomeIcon :icon="faTrash" class="h-4 w-4" />
<span>Izbriši</span> <span>Izbriši</span>

View File

@ -12,6 +12,7 @@ const props = defineProps({
show: { type: Boolean, default: false }, show: { type: Boolean, default: false },
client_case: { type: Object, required: true }, client_case: { type: Object, required: true },
contract: { type: Object, default: null }, contract: { type: Object, default: null },
edit: { type: Boolean, default: true },
}); });
const emit = defineEmits(["close"]); const emit = defineEmits(["close"]);
@ -79,7 +80,7 @@ const deleteObject = (o) => {
<div class="text-xs uppercase text-gray-500">Ref.</div> <div class="text-xs uppercase text-gray-500">Ref.</div>
<div class="font-semibold text-gray-900">{{ o.reference || "-" }}</div> <div class="font-semibold text-gray-900">{{ o.reference || "-" }}</div>
</div> </div>
<div class="ml-3 flex items-center gap-2"> <div class="ml-3 flex items-center gap-2" v-if="edit">
<span <span
class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-700" class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-700"
>{{ o.type || "—" }}</span >{{ o.type || "—" }}</span

View File

@ -36,6 +36,7 @@ const props = defineProps({
all_segments: { type: Array, default: () => [] }, all_segments: { type: Array, default: () => [] },
templates: { type: Array, default: () => [] }, // active document templates (latest per slug) templates: { type: Array, default: () => [] }, // active document templates (latest per slug)
edit: { type: Boolean, default: () => false }, edit: { type: Boolean, default: () => false },
createDoc: { type: Boolean, default: () => false },
}); });
// Debug: log incoming contract balances (remove after fix) // Debug: log incoming contract balances (remove after fix)
@ -485,7 +486,7 @@ const closePaymentsDialog = () => {
> >
Opis</FwbTableHeadCell Opis</FwbTableHeadCell
> >
<FwbTableHeadCell class="w-px" v-if="edit" /> <FwbTableHeadCell class="w-px" />
</FwbTableHead> </FwbTableHead>
<FwbTableBody> <FwbTableBody>
<template v-for="(c, i) in contracts" :key="c.uuid || i"> <template v-for="(c, i) in contracts" :key="c.uuid || i">
@ -702,7 +703,7 @@ const closePaymentsDialog = () => {
</Dropdown> </Dropdown>
</div> </div>
</FwbTableCell> </FwbTableCell>
<FwbTableCell class="text-right whitespace-nowrap" v-if="edit"> <FwbTableCell class="text-right whitespace-nowrap">
<Dropdown align="right" width="56"> <Dropdown align="right" width="56">
<template #trigger> <template #trigger>
<button <button
@ -718,6 +719,7 @@ const closePaymentsDialog = () => {
</template> </template>
<template #content> <template #content>
<!-- Urejanje --> <!-- Urejanje -->
<template v-if="edit">
<div <div
class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400" class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400"
> >
@ -735,6 +737,7 @@ const closePaymentsDialog = () => {
/> />
<span>Uredi</span> <span>Uredi</span>
</button> </button>
</template>
<button <button
type="button" type="button"
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2" class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
@ -747,6 +750,7 @@ const closePaymentsDialog = () => {
<div class="my-1 border-t border-gray-100" /> <div class="my-1 border-t border-gray-100" />
<!-- Dokumenti --> <!-- Dokumenti -->
<template v-if="createDoc">
<div <div
class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400" class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400"
> >
@ -755,7 +759,9 @@ const closePaymentsDialog = () => {
<button <button
type="button" type="button"
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2" class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
:disabled="generating[c.uuid] || !templates || templates.length === 0" :disabled="
generating[c.uuid] || !templates || templates.length === 0
"
@click="openGenerateDialog(c)" @click="openGenerateDialog(c)"
> >
<FontAwesomeIcon <FontAwesomeIcon
@ -787,6 +793,7 @@ const closePaymentsDialog = () => {
{{ generationError[c.uuid] }} {{ generationError[c.uuid] }}
</div> </div>
<div class="my-1 border-t border-gray-100" /> <div class="my-1 border-t border-gray-100" />
</template>
<!-- Predmeti --> <!-- Predmeti -->
<div <div
class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400" class="px-3 pt-2 pb-1 text-[11px] uppercase tracking-wide text-gray-400"
@ -797,6 +804,7 @@ const closePaymentsDialog = () => {
type="button" type="button"
class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2" class="w-full px-3 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
@click="openObjectsList(c)" @click="openObjectsList(c)"
:edit="edit"
> >
<FontAwesomeIcon :icon="faCircleInfo" class="h-4 w-4 text-gray-600" /> <FontAwesomeIcon :icon="faCircleInfo" class="h-4 w-4 text-gray-600" />
<span>Seznam predmetov</span> <span>Seznam predmetov</span>
@ -835,7 +843,7 @@ const closePaymentsDialog = () => {
<FontAwesomeIcon :icon="faPlus" class="h-4 w-4 text-gray-600" /> <FontAwesomeIcon :icon="faPlus" class="h-4 w-4 text-gray-600" />
<span>Dodaj plačilo</span> <span>Dodaj plačilo</span>
</button> </button>
<template v-if="edit">
<div class="my-1 border-t border-gray-100" /> <div class="my-1 border-t border-gray-100" />
<!-- Arhiviranje / Ponovna aktivacija --> <!-- Arhiviranje / Ponovna aktivacija -->
<div <div
@ -843,6 +851,7 @@ const closePaymentsDialog = () => {
> >
{{ c.active ? "Arhiviranje" : "Ponovna aktivacija" }} {{ c.active ? "Arhiviranje" : "Ponovna aktivacija" }}
</div> </div>
<button <button
v-if="c.active" v-if="c.active"
type="button" type="button"
@ -861,7 +870,10 @@ const closePaymentsDialog = () => {
) )
" "
> >
<FontAwesomeIcon :icon="faBoxArchive" class="h-4 w-4 text-gray-600" /> <FontAwesomeIcon
:icon="faBoxArchive"
class="h-4 w-4 text-gray-600"
/>
<span>Arhiviraj</span> <span>Arhiviraj</span>
</button> </button>
<button <button
@ -882,9 +894,14 @@ const closePaymentsDialog = () => {
) )
" "
> >
<FontAwesomeIcon :icon="faBoxArchive" class="h-4 w-4 text-gray-600" /> <FontAwesomeIcon
:icon="faBoxArchive"
class="h-4 w-4 text-gray-600"
/>
<span>Ponovno aktiviraj</span> <span>Ponovno aktiviraj</span>
</button> </button>
</template>
<template v-if="edit">
<div class="my-1 border-t border-gray-100" /> <div class="my-1 border-t border-gray-100" />
<!-- Destruktivno --> <!-- Destruktivno -->
<button <button
@ -896,6 +913,7 @@ const closePaymentsDialog = () => {
<span>Izbriši</span> <span>Izbriši</span>
</button> </button>
</template> </template>
</template>
</Dropdown> </Dropdown>
</FwbTableCell> </FwbTableCell>
</FwbTableRow> </FwbTableRow>
@ -947,6 +965,7 @@ const closePaymentsDialog = () => {
@close="closeObjectsList" @close="closeObjectsList"
:client_case="client_case" :client_case="client_case"
:contract="selectedContract" :contract="selectedContract"
:edit="edit"
/> />
<PaymentDialog <PaymentDialog
@ -960,6 +979,7 @@ const closePaymentsDialog = () => {
:show="showPaymentsDialog" :show="showPaymentsDialog"
:contract="selectedContract" :contract="selectedContract"
@close="closePaymentsDialog" @close="closePaymentsDialog"
:edit="edit"
/> />
<!-- Generate document dialog --> <!-- Generate document dialog -->

View File

@ -9,6 +9,7 @@ import { router } from "@inertiajs/vue3";
const props = defineProps({ const props = defineProps({
show: { type: Boolean, default: false }, show: { type: Boolean, default: false },
contract: { type: Object, default: null }, contract: { type: Object, default: null },
edit: { type: Boolean, default: true },
}); });
const emit = defineEmits(["close"]); const emit = defineEmits(["close"]);
@ -114,7 +115,7 @@ watch(
<span v-if="p.reference" class="ml-2">Sklic: {{ p.reference }}</span> <span v-if="p.reference" class="ml-2">Sklic: {{ p.reference }}</span>
</div> </div>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2" v-if="edit">
<button <button
type="button" type="button"
class="inline-flex items-center gap-1 px-2 py-1 rounded text-red-700 hover:bg-red-50" class="inline-flex items-center gap-1 px-2 py-1 rounded text-red-700 hover:bg-red-50"

View File

@ -7,6 +7,7 @@ import axios from "axios";
const props = defineProps({ const props = defineProps({
show: { type: Boolean, default: false }, show: { type: Boolean, default: false },
contract: { type: Object, default: null }, contract: { type: Object, default: null },
edit: { type: Boolean, default: true },
}); });
const emit = defineEmits(["close"]); const emit = defineEmits(["close"]);
@ -99,20 +100,27 @@ watch(
<div> <div>
<div class="text-sm text-gray-800"> <div class="text-sm text-gray-800">
{{ {{
Intl.NumberFormat('de-DE', { style: 'currency', currency: p.currency || 'EUR' }).format(p.amount ?? 0) Intl.NumberFormat("de-DE", {
style: "currency",
currency: p.currency || "EUR",
}).format(p.amount ?? 0)
}} }}
</div> </div>
<div class="text-xs text-gray-500"> <div class="text-xs text-gray-500">
<span>{{ formatDate(p.paid_at) }}</span> <span>{{ formatDate(p.paid_at) }}</span>
<span v-if="p.reference" class="ml-2">Sklic: {{ p.reference }}</span> <span v-if="p.reference" class="ml-2">Sklic: {{ p.reference }}</span>
<span v-if="p.balance_before !== undefined" class="ml-2"> <span v-if="p.balance_before !== undefined" class="ml-2">
Stanje pred: {{ Stanje pred:
Intl.NumberFormat('de-DE', { style: 'currency', currency: p.currency || 'EUR' }).format(p.balance_before ?? 0) {{
Intl.NumberFormat("de-DE", {
style: "currency",
currency: p.currency || "EUR",
}).format(p.balance_before ?? 0)
}} }}
</span> </span>
</div> </div>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2" v-if="edit">
<button <button
type="button" type="button"
class="inline-flex items-center gap-1 px-2 py-1 rounded text-red-700 hover:bg-red-50" class="inline-flex items-center gap-1 px-2 py-1 rounded text-red-700 hover:bg-red-50"
@ -129,10 +137,18 @@ watch(
</template> </template>
<template #footer> <template #footer>
<div class="flex justify-end gap-2 w-full"> <div class="flex justify-end gap-2 w-full">
<button type="button" class="px-3 py-2 rounded bg-gray-200 hover:bg-gray-300" @click="loadPayments"> <button
type="button"
class="px-3 py-2 rounded bg-gray-200 hover:bg-gray-300"
@click="loadPayments"
>
Osveži Osveži
</button> </button>
<button type="button" class="px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700" @click="close"> <button
type="button"
class="px-3 py-2 rounded bg-indigo-600 text-white hover:bg-indigo-700"
@click="close"
>
Zapri Zapri
</button> </button>
</div> </div>

View File

@ -324,6 +324,7 @@ const submitAttachSegment = () => {
:segments="segments" :segments="segments"
:templates="contract_doc_templates" :templates="contract_doc_templates"
:edit="hasPerm('contract-edit')" :edit="hasPerm('contract-edit')"
:create-doc="hasPerm('create-docs')"
@edit="openDrawerEditContract" @edit="openDrawerEditContract"
@delete="requestDeleteContract" @delete="requestDeleteContract"
@add-activity="openDrawerAddActivity" @add-activity="openDrawerAddActivity"
@ -371,6 +372,7 @@ const submitAttachSegment = () => {
</div> </div>
<DocumentsTable <DocumentsTable
:documents="documents" :documents="documents"
:edit="hasPerm('doc-edit')"
@view="openViewer" @view="openViewer"
@edit="openDocEdit" @edit="openDocEdit"
:download-url-builder=" :download-url-builder="
@ -408,6 +410,7 @@ const submitAttachSegment = () => {
:contracts="contracts" :contracts="contracts"
@close="closeDocEdit" @close="closeDocEdit"
@saved="onDocSaved" @saved="onDocSaved"
v-if="hasPerm('doc-edit')"
/> />
<DocumentViewerDialog <DocumentViewerDialog
:show="viewer.open" :show="viewer.open"

View File

@ -166,7 +166,7 @@
}); });
// Contract document generation (JSON) - protected by auth+verified; permission enforced inside controller service // Contract document generation (JSON) - protected by auth+verified; permission enforced inside controller service
Route::post('contracts/{contract:uuid}/generate-document', \App\Http\Controllers\ContractDocumentGenerationController::class)->name('contracts.generate-document'); Route::post('contracts/{contract:uuid}/generate-document', \App\Http\Controllers\ContractDocumentGenerationController::class)->name('contracts.generate-document')->middleware("permission:create-docs");
// Phone page // Phone page
Route::get('phone', [PhoneViewController::class, 'index'])->name('phone.index'); Route::get('phone', [PhoneViewController::class, 'index'])->name('phone.index');
@ -337,12 +337,15 @@
Route::post('client-cases/{client_case:uuid}/segments', [ClientCaseContoller::class, 'attachSegment'])->name('clientCase.segments.attach'); Route::post('client-cases/{client_case:uuid}/segments', [ClientCaseContoller::class, 'attachSegment'])->name('clientCase.segments.attach');
// client-case / documents // client-case / documents
Route::post('client-cases/{client_case:uuid}/documents', [ClientCaseContoller::class, 'storeDocument'])->name('clientCase.document.store'); Route::post('client-cases/{client_case:uuid}/documents', [ClientCaseContoller::class, 'storeDocument'])->name('clientCase.document.store');
Route::get('client-cases/{client_case:uuid}/documents/{document:uuid}/view', [ClientCaseContoller::class, 'viewDocument'])->name('clientCase.document.view');
Route::get('client-cases/{client_case:uuid}/documents/{document:uuid}/download', [ClientCaseContoller::class, 'downloadDocument'])->name('clientCase.document.download');
Route::middleware("permission:doc-edit")->group( function() {
Route::patch('client-cases/{client_case:uuid}/documents/{document:uuid}', [ClientCaseContoller::class, 'updateDocument']) Route::patch('client-cases/{client_case:uuid}/documents/{document:uuid}', [ClientCaseContoller::class, 'updateDocument'])
->withoutScopedBindings() ->withoutScopedBindings()
->name('clientCase.document.update'); ->name('clientCase.document.update');
Route::get('client-cases/{client_case:uuid}/documents/{document:uuid}/view', [ClientCaseContoller::class, 'viewDocument'])->name('clientCase.document.view');
Route::get('client-cases/{client_case:uuid}/documents/{document:uuid}/download', [ClientCaseContoller::class, 'downloadDocument'])->name('clientCase.document.download');
Route::delete('client-cases/{client_case:uuid}/documents/{document:uuid}', [ClientCaseContoller::class, 'deleteDocument'])->name('clientCase.document.delete'); Route::delete('client-cases/{client_case:uuid}/documents/{document:uuid}', [ClientCaseContoller::class, 'deleteDocument'])->name('clientCase.document.delete');
});
// client-case / person phone - send SMS // client-case / person phone - send SMS
Route::post('client-cases/{client_case:uuid}/phone/{phone_id}/sms', [ClientCaseContoller::class, 'sendSmsToPhone'])->name('clientCase.phone.sms'); Route::post('client-cases/{client_case:uuid}/phone/{phone_id}/sms', [ClientCaseContoller::class, 'sendSmsToPhone'])->name('clientCase.phone.sms');
// client-case / contracts list for SMS dialog // client-case / contracts list for SMS dialog