Dateimodal überarbeiten und Dateitypen pflegen
This commit is contained in:
6
backend/db/migrations/0040_filetag_system_types.sql
Normal file
6
backend/db/migrations/0040_filetag_system_types.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE "filetags" ADD COLUMN "isSystemUsed" boolean DEFAULT false NOT NULL;
|
||||
|
||||
UPDATE "filetags"
|
||||
SET "isSystemUsed" = true
|
||||
WHERE COALESCE("createddocumenttype", '') <> ''
|
||||
OR COALESCE("incomingDocumentType", '') <> '';
|
||||
@@ -281,6 +281,13 @@
|
||||
"when": 1779840000000,
|
||||
"tag": "0039_events_repeat_interval",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 40,
|
||||
"version": "7",
|
||||
"when": 1779141600000,
|
||||
"tag": "0040_filetag_system_types",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ export const filetags = pgTable("filetags", {
|
||||
createdDocumentType: text("createddocumenttype").default(""),
|
||||
incomingDocumentType: text("incomingDocumentType"),
|
||||
|
||||
isSystemUsed: boolean("isSystemUsed").notNull().default(false),
|
||||
|
||||
archived: boolean("archived").notNull().default(false),
|
||||
})
|
||||
|
||||
|
||||
@@ -116,12 +116,12 @@ async function ensureTenantFileDefaults(server: FastifyInstance, tenantId: numbe
|
||||
const timestamp = new Date()
|
||||
|
||||
const tagDefaults = [
|
||||
{ name: "Rechnungen", color: "#16a34a", createdDocumentType: "invoices" },
|
||||
{ name: "Angebote", color: "#2563eb", createdDocumentType: "quotes" },
|
||||
{ name: "Auftragsbestätigungen", color: "#7c3aed", createdDocumentType: "confirmationOrders" },
|
||||
{ name: "Lieferscheine", color: "#ea580c", createdDocumentType: "deliveryNotes" },
|
||||
{ name: "Eingangsrechnungen", color: "#dc2626", incomingDocumentType: "invoices" },
|
||||
{ name: "Mahnungen", color: "#b91c1c", incomingDocumentType: "reminders" },
|
||||
{ name: "Rechnungen", color: "#16a34a", createdDocumentType: "invoices", isSystemUsed: true },
|
||||
{ name: "Angebote", color: "#2563eb", createdDocumentType: "quotes", isSystemUsed: true },
|
||||
{ name: "Auftragsbestätigungen", color: "#7c3aed", createdDocumentType: "confirmationOrders", isSystemUsed: true },
|
||||
{ name: "Lieferscheine", color: "#ea580c", createdDocumentType: "deliveryNotes", isSystemUsed: true },
|
||||
{ name: "Eingangsrechnungen", color: "#dc2626", incomingDocumentType: "invoices", isSystemUsed: true },
|
||||
{ name: "Mahnungen", color: "#b91c1c", incomingDocumentType: "reminders", isSystemUsed: true },
|
||||
]
|
||||
|
||||
for (const tag of tagDefaults) {
|
||||
|
||||
@@ -45,36 +45,42 @@ export default async function adminRoutes(server: FastifyInstance) {
|
||||
name: "Rechnungen",
|
||||
color: "#16a34a",
|
||||
createdDocumentType: "invoices",
|
||||
isSystemUsed: true,
|
||||
},
|
||||
{
|
||||
tenant: tenantId,
|
||||
name: "Angebote",
|
||||
color: "#2563eb",
|
||||
createdDocumentType: "quotes",
|
||||
isSystemUsed: true,
|
||||
},
|
||||
{
|
||||
tenant: tenantId,
|
||||
name: "Auftragsbestätigungen",
|
||||
color: "#7c3aed",
|
||||
createdDocumentType: "confirmationOrders",
|
||||
isSystemUsed: true,
|
||||
},
|
||||
{
|
||||
tenant: tenantId,
|
||||
name: "Lieferscheine",
|
||||
color: "#ea580c",
|
||||
createdDocumentType: "deliveryNotes",
|
||||
isSystemUsed: true,
|
||||
},
|
||||
{
|
||||
tenant: tenantId,
|
||||
name: "Eingangsrechnungen",
|
||||
color: "#dc2626",
|
||||
incomingDocumentType: "invoices",
|
||||
isSystemUsed: true,
|
||||
},
|
||||
{
|
||||
tenant: tenantId,
|
||||
name: "Mahnungen",
|
||||
color: "#b91c1c",
|
||||
incomingDocumentType: "reminders",
|
||||
isSystemUsed: true,
|
||||
},
|
||||
])
|
||||
.returning({
|
||||
|
||||
@@ -968,6 +968,14 @@ export default async function resourceRoutes(server: FastifyInstance) {
|
||||
//@ts-ignore
|
||||
delete data.updatedBy; delete data.updatedAt;
|
||||
|
||||
if (resource === "filetags") {
|
||||
delete data.isSystemUsed
|
||||
|
||||
if (oldRecord.isSystemUsed && data.archived === true) {
|
||||
return reply.code(400).send({ error: "System-Dateitypen können nicht archiviert werden" })
|
||||
}
|
||||
}
|
||||
|
||||
if (portalCustomerId) {
|
||||
data = {
|
||||
...sanitizePortalCustomerUpdate(data),
|
||||
|
||||
@@ -105,7 +105,22 @@ export const resourceConfig = {
|
||||
numberRangeHolder: "vendorNumber",
|
||||
},
|
||||
files: {
|
||||
table: files
|
||||
table: files,
|
||||
mtoLoad: [
|
||||
"project",
|
||||
"customer",
|
||||
"contract",
|
||||
"vendor",
|
||||
"incominginvoice",
|
||||
"plant",
|
||||
"createddocument",
|
||||
"vehicle",
|
||||
"product",
|
||||
"check",
|
||||
"inventoryitem",
|
||||
"authProfile",
|
||||
"type",
|
||||
],
|
||||
},
|
||||
folders: {
|
||||
table: folders
|
||||
@@ -113,6 +128,9 @@ export const resourceConfig = {
|
||||
filetags: {
|
||||
table: filetags
|
||||
},
|
||||
type: {
|
||||
table: filetags
|
||||
},
|
||||
inventoryitems: {
|
||||
table: inventoryitems,
|
||||
numberRangeHolder: "articleNumber",
|
||||
@@ -201,6 +219,11 @@ export const resourceConfig = {
|
||||
tenantKey: "tenant_id",
|
||||
searchColumns: ["first_name", "last_name", "full_name", "email", "employee_number"],
|
||||
},
|
||||
authProfile: {
|
||||
table: authProfiles,
|
||||
tenantKey: "tenant_id",
|
||||
searchColumns: ["first_name", "last_name", "full_name", "email", "employee_number"],
|
||||
},
|
||||
letterheads: {
|
||||
table: letterheads,
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script setup>
|
||||
|
||||
const toast = useToast()
|
||||
const dataStore = useDataStore()
|
||||
const modal = useModal()
|
||||
|
||||
const props = defineProps({
|
||||
documentData: {
|
||||
type: Object,
|
||||
@@ -15,353 +14,264 @@ const props = defineProps({
|
||||
returnEmit: {
|
||||
type: Boolean
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
const emit = defineEmits(["updateNeeded"])
|
||||
|
||||
const folders = ref([])
|
||||
|
||||
const filetypes = ref([])
|
||||
const documentboxes = ref([])
|
||||
const getFiletypeId = () => props.documentData.type && typeof props.documentData.type === "object"
|
||||
? props.documentData.type.id
|
||||
: props.documentData.type || null
|
||||
const selectedFiletype = ref(getFiletypeId())
|
||||
|
||||
const resourceOptions = ref([
|
||||
{label: "Projekt", value: "project", entity: "projects", optionAttr: "name", route: (item) => `/standardEntity/projects/show/${item.id}`},
|
||||
{label: "Kunde", value: "customer", entity: "customers", optionAttr: "name", route: (item) => `/standardEntity/customers/show/${item.id}`},
|
||||
{label: "Lieferant", value: "vendor", entity: "vendors", optionAttr: "name", route: (item) => `/standardEntity/vendors/show/${item.id}`},
|
||||
{label: "Fahrzeug", value: "vehicle", entity: "vehicles", optionAttr: "licensePlate", route: (item) => `/standardEntity/vehicles/show/${item.id}`},
|
||||
{label: "Objekt", value: "plant", entity: "plants", optionAttr: "name", route: (item) => `/standardEntity/plants/show/${item.id}`},
|
||||
{label: "Vertrag", value: "contract", entity: "contracts", optionAttr: "name", route: (item) => `/standardEntity/contracts/show/${item.id}`},
|
||||
{label: "Produkt", value: "product", entity: "products", optionAttr: "name", route: (item) => `/standardEntity/products/show/${item.id}`},
|
||||
{label: "Ausgangsbeleg", value: "createddocument", entity: "createddocuments", optionAttr: "documentNumber", route: (item) => `/createDocument/show/${item.id}`},
|
||||
{label: "Eingangsrechnung", value: "incominginvoice", entity: "incominginvoices", optionAttr: "reference", route: (item) => `/incomingInvoices/show/${item.id}`},
|
||||
{label: "Inventarartikel", value: "inventoryitem", entity: "inventoryitems", optionAttr: "name", route: (item) => `/standardEntity/inventoryitems/show/${item.id}`},
|
||||
{label: "Überprüfung", value: "check", entity: "checks", optionAttr: "name", route: (item) => `/standardEntity/checks/show/${item.id}`},
|
||||
{label: "Mitarbeiter", value: "authProfile", entity: "profiles", optionAttr: "fullName", route: (item) => `/staff/profiles/${item.id}`}
|
||||
])
|
||||
|
||||
const resourceToAssign = ref("project")
|
||||
const itemOptions = ref([])
|
||||
const idToAssign = ref(null)
|
||||
|
||||
const selectedResource = computed(() => resourceOptions.value.find((option) => option.value === resourceToAssign.value))
|
||||
|
||||
const setup = async () => {
|
||||
const data = await useEntities("folders").select()
|
||||
|
||||
data.forEach(folder => {
|
||||
let name = folder.name
|
||||
|
||||
const addParent = (item) => {
|
||||
name = `${item.name} > ${name}`
|
||||
|
||||
if(item.parent){
|
||||
addParent(data.find(i => i.id === item.parent))
|
||||
} else {
|
||||
folders.value.push({
|
||||
id: folder.id,
|
||||
name: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if(folder.parent) {
|
||||
addParent(data.find(i => i.id === folder.parent))
|
||||
} else {
|
||||
folders.value.push({
|
||||
id: folder.id,
|
||||
name: folder.name,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
filetypes.value = await useEntities("filetags").select()
|
||||
//documentboxes.value = await useEntities("documentboxes").select()
|
||||
selectedFiletype.value = getFiletypeId()
|
||||
await getItemsBySelectedResource()
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
const updateDocument = async () => {
|
||||
const {url, ...objData} = props.documentData
|
||||
delete objData.url
|
||||
delete objData.filetags
|
||||
|
||||
/*console.log(objData)
|
||||
|
||||
if(objData.project) objData.project = objData.project.id
|
||||
if(objData.customer) objData.customer = objData.customer.id
|
||||
if(objData.contract) objData.contract = objData.contract.id
|
||||
if(objData.vendor) objData.vendor = objData.vendor.id
|
||||
if(objData.plant) objData.plant = objData.plant.id
|
||||
if(objData.createddocument) objData.createddocument = objData.createddocument.id
|
||||
if(objData.vehicle) objData.vehicle = objData.vehicle.id
|
||||
if(objData.product) objData.product = objData.product.id
|
||||
if(objData.profile) objData.profile = objData.profile.id
|
||||
if(objData.check) objData.check = objData.check.id
|
||||
if(objData.inventoryitem) objData.inventoryitem = objData.inventoryitem.id
|
||||
if(objData.incominginvoice) objData.incominginvoice = objData.incominginvoice.id*/
|
||||
|
||||
console.log(objData)
|
||||
|
||||
const {data,error} = await useEntities("files").update(objData.id, objData)
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
|
||||
} else {
|
||||
console.log(data)
|
||||
const updateDocument = async (payload, closeAfterUpdate = false) => {
|
||||
try {
|
||||
await useEntities("files").update(props.documentData.id, payload, true)
|
||||
Object.assign(props.documentData, payload)
|
||||
toast.add({title: "Datei aktualisiert"})
|
||||
modal.close()
|
||||
emit("updateNeeded")
|
||||
//openShowModal.value = false
|
||||
if (closeAfterUpdate) modal.close()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
toast.add({title: "Datei konnte nicht aktualisiert werden", color: "error"})
|
||||
}
|
||||
}
|
||||
|
||||
const archiveDocument = async () => {
|
||||
props.documentData.archived = true
|
||||
await updateDocument()
|
||||
|
||||
modal.close()
|
||||
emit("update")
|
||||
await updateDocument({archived: true}, true)
|
||||
}
|
||||
|
||||
const resourceOptions = ref([
|
||||
{label: 'Projekt', value: 'project', optionAttr: "name"},
|
||||
{label: 'Kunde', value: 'customer', optionAttr: "name"},
|
||||
{label: 'Lieferant', value: 'vendor', optionAttr: "name"},
|
||||
{label: 'Fahrzeug', value: 'vehicle', optionAttr: "licensePlate"},
|
||||
{label: 'Objekt', value: 'plant', optionAttr: "name"},
|
||||
{label: 'Vertrag', value: 'contract', optionAttr: "name"},
|
||||
{label: 'Produkt', value: 'product', optionAttr: "name"}
|
||||
])
|
||||
const resourceToAssign = ref("project")
|
||||
const itemOptions = ref([])
|
||||
const idToAssign = ref(null)
|
||||
const getItemsBySelectedResource = async () => {
|
||||
if(resourceToAssign.value === "project") {
|
||||
itemOptions.value = await useEntities("projects").select()
|
||||
} else if(resourceToAssign.value === "customer") {
|
||||
itemOptions.value = await useEntities("customers").select()
|
||||
} else if(resourceToAssign.value === "vendor") {
|
||||
itemOptions.value = await useEntities("vendors").select()
|
||||
} else if(resourceToAssign.value === "vehicle") {
|
||||
itemOptions.value = await useEntities("vehicles").select()
|
||||
} else if(resourceToAssign.value === "product") {
|
||||
itemOptions.value = await useEntities("products").select()
|
||||
} else if(resourceToAssign.value === "plant") {
|
||||
itemOptions.value = await useEntities("plants").select()
|
||||
} else if(resourceToAssign.value === "contract") {
|
||||
itemOptions.value = await useEntities("contracts").select()
|
||||
} else {
|
||||
itemOptions.value = []
|
||||
}
|
||||
idToAssign.value = null
|
||||
itemOptions.value = selectedResource.value?.entity
|
||||
? await useEntities(selectedResource.value.entity).select()
|
||||
: []
|
||||
}
|
||||
getItemsBySelectedResource()
|
||||
|
||||
const updateDocumentAssignment = async () => {
|
||||
props.documentData[resourceToAssign.value] = idToAssign.value
|
||||
await updateDocument()
|
||||
if (!selectedResource.value || !idToAssign.value) return
|
||||
|
||||
await updateDocument({[selectedResource.value.value]: idToAssign.value})
|
||||
props.documentData[selectedResource.value.value] = itemOptions.value.find((item) => item.id === idToAssign.value) || idToAssign.value
|
||||
idToAssign.value = null
|
||||
}
|
||||
|
||||
const folderToMoveTo = ref(null)
|
||||
const moveFile = async () => {
|
||||
|
||||
const res = await useEntities("files").update(props.documentData.id, {folder: folderToMoveTo.value})
|
||||
|
||||
modal.close()
|
||||
const removeAssignment = async (assignment) => {
|
||||
await updateDocument({[assignment.value]: null})
|
||||
props.documentData[assignment.value] = null
|
||||
}
|
||||
|
||||
const getAssignmentItem = (assignment) => {
|
||||
const value = props.documentData[assignment.value]
|
||||
return value && typeof value === "object" ? value : null
|
||||
}
|
||||
|
||||
const getAssignmentLabel = (assignment) => {
|
||||
const value = props.documentData[assignment.value]
|
||||
if (!value) return ""
|
||||
if (typeof value === "object") return value[assignment.optionAttr] || value.name || value.id
|
||||
return value
|
||||
}
|
||||
|
||||
const currentAssignments = computed(() =>
|
||||
resourceOptions.value
|
||||
.filter((assignment) => Boolean(props.documentData[assignment.value]))
|
||||
.map((assignment) => ({
|
||||
...assignment,
|
||||
item: getAssignmentItem(assignment),
|
||||
display: getAssignmentLabel(assignment)
|
||||
}))
|
||||
)
|
||||
|
||||
const displayedFileTags = computed(() => {
|
||||
if (Array.isArray(props.documentData.filetags) && props.documentData.filetags.length) {
|
||||
return props.documentData.filetags
|
||||
}
|
||||
|
||||
if (props.documentData.type && typeof props.documentData.type === "object") {
|
||||
return [props.documentData.type]
|
||||
}
|
||||
|
||||
const selected = filetypes.value.find((filetype) => filetype.id === props.documentData.type)
|
||||
return selected ? [selected] : []
|
||||
})
|
||||
|
||||
const updateFiletype = async () => {
|
||||
await updateDocument({type: selectedFiletype.value || null})
|
||||
props.documentData.type = selectedFiletype.value || null
|
||||
}
|
||||
|
||||
setup()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal fullscreen >
|
||||
<UModal fullscreen>
|
||||
<template #content>
|
||||
<UCard :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }" class="h-full">
|
||||
<template #header>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge
|
||||
v-for="tag in props.documentData.filetags"
|
||||
>
|
||||
{{tag.name}}
|
||||
</UBadge>
|
||||
<template #header>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge
|
||||
v-for="tag in displayedFileTags"
|
||||
:key="tag.id"
|
||||
>
|
||||
{{tag.name}}
|
||||
</UBadge>
|
||||
</div>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="modal.close()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<div class="w-1/3">
|
||||
<PDFViewer
|
||||
v-if="props.documentData.id && props.documentData.path.toLowerCase().includes('pdf')"
|
||||
:file-id="props.documentData.id" />
|
||||
|
||||
<img
|
||||
v-else
|
||||
class="w-full"
|
||||
:src="props.documentData.url"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-2/3 p-5">
|
||||
<UButtonGroup>
|
||||
<ArchiveButton
|
||||
color="error"
|
||||
variant="outline"
|
||||
type="files"
|
||||
@confirmed="archiveDocument"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
:to="props.documentData.url"
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-top-right-on-square"
|
||||
target="_blank"
|
||||
>
|
||||
Öffnen
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
|
||||
<USeparator class="my-3" label="Zuweisungen"/>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="assignment in currentAssignments"
|
||||
:key="assignment.value"
|
||||
class="flex items-center justify-between gap-3 rounded-md border border-gray-200 p-2 dark:border-gray-800"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs text-gray-500">{{ assignment.label }}</div>
|
||||
<nuxt-link
|
||||
v-if="assignment.item"
|
||||
:to="assignment.route(assignment.item)"
|
||||
class="block truncate font-medium text-primary"
|
||||
>
|
||||
{{ assignment.display }}
|
||||
</nuxt-link>
|
||||
<span v-else class="font-medium">{{ assignment.display }}</span>
|
||||
</div>
|
||||
<UButton
|
||||
icon="i-heroicons-x-mark"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
@click="removeAssignment(assignment)"
|
||||
/>
|
||||
</div>
|
||||
<UAlert
|
||||
v-if="currentAssignments.length === 0"
|
||||
icon="i-heroicons-link"
|
||||
title="Noch keine Zuweisungen vorhanden"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<USeparator class="my-3" label="Datei zuweisen"/>
|
||||
|
||||
<UFormField label="Bereich auswählen">
|
||||
<USelectMenu
|
||||
v-model="resourceToAssign"
|
||||
:items="resourceOptions"
|
||||
value-key="value"
|
||||
label-key="label"
|
||||
@update:model-value="getItemsBySelectedResource"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<UFormField class="mt-3" label="Eintrag auswählen">
|
||||
<USelectMenu
|
||||
v-model="idToAssign"
|
||||
:items="itemOptions"
|
||||
:label-key="selectedResource ? selectedResource.optionAttr : 'name'"
|
||||
value-key="id"
|
||||
:search-input="{ placeholder: 'Eintrag suchen...' }"
|
||||
:filter-fields="[selectedResource ? selectedResource.optionAttr : 'name']"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<div class="mt-2 flex justify-end">
|
||||
<UButton
|
||||
icon="i-heroicons-link"
|
||||
:disabled="!idToAssign"
|
||||
@click="updateDocumentAssignment"
|
||||
>
|
||||
Zuweisen
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<USeparator class="my-5" label="Dateityp"/>
|
||||
|
||||
<InputGroup class="w-full">
|
||||
<USelectMenu
|
||||
v-model="selectedFiletype"
|
||||
class="flex-auto"
|
||||
value-key="id"
|
||||
label-key="name"
|
||||
:items="filetypes"
|
||||
@update:model-value="updateFiletype"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="modal.close()" />
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<div class="flex flex-row">
|
||||
<div :class="false ? ['w-full'] : ['w-1/3']">
|
||||
<PDFViewer
|
||||
v-if="props.documentData.id && props.documentData.path.toLowerCase().includes('pdf')"
|
||||
:file-id="props.documentData.id" />
|
||||
|
||||
<img
|
||||
class=" w-full"
|
||||
:src="props.documentData.url"
|
||||
alt=""
|
||||
v-else
|
||||
/>
|
||||
</div>
|
||||
<div class="w-2/3 p-5" v-if="!false">
|
||||
<UButtonGroup>
|
||||
<ArchiveButton
|
||||
color="error"
|
||||
variant="outline"
|
||||
type="files"
|
||||
@confirmed="archiveDocument"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
:to="props.documentData.url"
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-top-right-on-square"
|
||||
target="_blank"
|
||||
>
|
||||
Öffnen
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
|
||||
<USeparator label="Zuweisungen"/>
|
||||
<table class="w-full">
|
||||
<tr v-if="props.documentData.project">
|
||||
<td>Projekt</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/projects/show/${props.documentData.project.id}`">{{props.documentData.project.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.customer">
|
||||
<td>Kunde</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/customers/show/${props.documentData.customer.id}`">{{props.documentData.customer.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.vendor">
|
||||
<td>Lieferant</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/vendors/show/${props.documentData.vendor.id}`">{{props.documentData.vendor.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.createddocument">
|
||||
<td>Ausgangsbeleg</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/createDocument/show/${props.documentData.createddocument.id}`">{{props.documentData.createddocument.documentNumber}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.plant">
|
||||
<td>Objekt</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/plants/show/${props.documentData.plant.id}`">{{props.documentData.plant.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.contract">
|
||||
<td>Vertrag</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/contracts/show/${props.documentData.contract.id}`">{{props.documentData.contract.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.vehicle">
|
||||
<td>Fahrzeug</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/vehicles/show/${props.documentData.vehicle.id}`">{{props.documentData.vehicle.licensePlate}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.product">
|
||||
<td>Artikel</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/products/show/${props.documentData.product.id}`">{{props.documentData.product.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.inventoryitem">
|
||||
<td>Inventarartikel</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/inventoryitem/show/${props.documentData.inventoryitem.id}`">{{props.documentData.inventoryitem.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.check">
|
||||
<td>Überprüfung</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/checks/show/${props.documentData.check.id}`">{{props.documentData.check.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.profile">
|
||||
<td>Mitarbeiter</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/profiles/show/${props.documentData.profile.id}`">{{props.documentData.profile.fullName}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.incominginvoice">
|
||||
<td>Eingangsrechnung</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/incomingInvoices/show/${props.documentData.incominginvoice.id}`">{{props.documentData.incominginvoice.reference}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<USeparator class="my-3" label="Datei zuweisen"/>
|
||||
|
||||
<UFormField
|
||||
label="Resource auswählen"
|
||||
>
|
||||
<USelectMenu
|
||||
:items="resourceOptions"
|
||||
v-model="resourceToAssign"
|
||||
value-key="value"
|
||||
label-key="label"
|
||||
@change="getItemsBySelectedResource"
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormField>
|
||||
<UFormField
|
||||
label="Eintrag auswählen:"
|
||||
>
|
||||
<USelectMenu
|
||||
:items="itemOptions"
|
||||
v-model="idToAssign"
|
||||
:label-key="resourceOptions.find(i => i.value === resourceToAssign)? resourceOptions.find(i => i.value === resourceToAssign).optionAttr : 'name'"
|
||||
value-key="id"
|
||||
@change="updateDocumentAssignment"
|
||||
></USelectMenu>
|
||||
</UFormField>
|
||||
|
||||
|
||||
|
||||
<USeparator class="my-5" label="Datei verschieben"/>
|
||||
|
||||
<InputGroup class="w-full">
|
||||
<USelectMenu
|
||||
class="flex-auto"
|
||||
v-model="folderToMoveTo"
|
||||
value-key="id"
|
||||
label-key="name"
|
||||
:items="folders"
|
||||
/>
|
||||
<UButton
|
||||
@click="moveFile"
|
||||
variant="outline"
|
||||
:disabled="!folderToMoveTo"
|
||||
>Verschieben</UButton>
|
||||
</InputGroup>
|
||||
|
||||
<USeparator class="my-5" label="Dateityp"/>
|
||||
|
||||
<InputGroup class="w-full">
|
||||
<USelectMenu
|
||||
class="flex-auto"
|
||||
v-model="props.documentData.type"
|
||||
value-key="id"
|
||||
label-key="name"
|
||||
:items="filetypes"
|
||||
@change="updateDocument"
|
||||
/>
|
||||
</InputGroup>
|
||||
<USeparator class="my-5" label="Dokumentenbox" />
|
||||
|
||||
<InputGroup class="w-full">
|
||||
<USelectMenu
|
||||
class="flex-auto"
|
||||
v-model="props.documentData.documentbox"
|
||||
value-key="id"
|
||||
label-key="key"
|
||||
:items="documentboxes"
|
||||
@change="updateDocument"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.bigPreview {
|
||||
width: 100%;
|
||||
aspect-ratio: 1/ 1.414;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -394,6 +394,12 @@ const getPostSaveRoute = () => {
|
||||
return null
|
||||
}
|
||||
|
||||
const canArchiveItem = computed(() => {
|
||||
if (!item.value?.id) return false
|
||||
if (dataType.canArchiveFunction) return dataType.canArchiveFunction(item.value)
|
||||
return true
|
||||
})
|
||||
|
||||
const createItem = async () => {
|
||||
let ret = null
|
||||
|
||||
@@ -461,7 +467,7 @@ const updateItem = async () => {
|
||||
<template #right>
|
||||
<ArchiveButton
|
||||
color="error"
|
||||
v-if="platform !== 'mobile'"
|
||||
v-if="platform !== 'mobile' && canArchiveItem"
|
||||
variant="outline"
|
||||
:type="type"
|
||||
@confirmed="useEntities(type).archive(item.id)"
|
||||
|
||||
@@ -289,6 +289,11 @@ const links = computed(() => {
|
||||
to: "/standardEntity/contracttypes",
|
||||
icon: "i-heroicons-document-duplicate",
|
||||
} : null,
|
||||
featureEnabled("files") ? {
|
||||
label: "Dateitypen",
|
||||
to: "/standardEntity/filetags",
|
||||
icon: "i-heroicons-tag",
|
||||
} : null,
|
||||
has("vehicles") && featureEnabled("vehicles") ? {
|
||||
label: "Fahrzeuge",
|
||||
to: "/standardEntity/vehicles",
|
||||
|
||||
@@ -48,9 +48,13 @@ export const useFiles = () => {
|
||||
}
|
||||
})
|
||||
|
||||
console.log(res)
|
||||
const fileDataById = new Map(data.map((file) => [file.id, file]))
|
||||
|
||||
return res.files
|
||||
return (res.files || []).map((file) => ({
|
||||
...file,
|
||||
...(fileDataById.get(file.id) || {}),
|
||||
url: file.url
|
||||
}))
|
||||
}
|
||||
|
||||
const selectSomeDocuments = async (documentIds, sortColumn = null, folder = null) => {
|
||||
@@ -73,6 +77,7 @@ export const useFiles = () => {
|
||||
const selectDocument = async (id) => {
|
||||
let documentIds = [id]
|
||||
if(documentIds.length === 0) return []
|
||||
const fileData = await useEntities("files").selectSingle(id)
|
||||
const res = await useNuxtApp().$api("/api/files/presigned",{
|
||||
method: "POST",
|
||||
body: {
|
||||
@@ -80,9 +85,8 @@ export const useFiles = () => {
|
||||
}
|
||||
})
|
||||
|
||||
console.log(res)
|
||||
|
||||
return res.files[0]
|
||||
const file = res.files?.[0] || null
|
||||
return file ? {...file, ...(fileData || {}), url: file.url} : null
|
||||
}
|
||||
|
||||
const downloadFile = async (id?: string, ids?: string[], returnAsBlob: Boolean = false) => {
|
||||
|
||||
@@ -50,6 +50,10 @@ const type = route.params.type
|
||||
|
||||
const dataType = dataStore.dataTypes[type]
|
||||
const canCreate = computed(() => {
|
||||
if (type === "filetags") {
|
||||
return true
|
||||
}
|
||||
|
||||
if (type === "members") {
|
||||
return has("members-create") || has("customers-create")
|
||||
}
|
||||
|
||||
@@ -2337,6 +2337,66 @@ export const useDataStore = defineStore('data', () => {
|
||||
labelSingle: "Datei",
|
||||
selectWithInformation: "*",
|
||||
},
|
||||
filetags: {
|
||||
isArchivable: true,
|
||||
label: "Dateitypen",
|
||||
labelSingle: "Dateityp",
|
||||
isStandardEntity: true,
|
||||
redirect: true,
|
||||
inputColumns: [
|
||||
"Allgemeines",
|
||||
"Automatik",
|
||||
],
|
||||
filters: [{
|
||||
name: "Archivierte ausblenden",
|
||||
default: true,
|
||||
"filterFunction": function (row) {
|
||||
return !row.archived
|
||||
}
|
||||
}],
|
||||
canArchiveFunction: function (row) {
|
||||
return !row.isSystemUsed
|
||||
},
|
||||
templateColumns: [
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
title: true,
|
||||
required: true,
|
||||
inputType: "text",
|
||||
inputColumn: "Allgemeines",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "color",
|
||||
label: "Farbe",
|
||||
inputType: "text",
|
||||
inputColumn: "Allgemeines",
|
||||
},
|
||||
{
|
||||
key: "createdDocumentType",
|
||||
label: "Ausgangsbeleg-Typ",
|
||||
inputType: "text",
|
||||
inputColumn: "Automatik",
|
||||
},
|
||||
{
|
||||
key: "incomingDocumentType",
|
||||
label: "Eingangsbeleg-Typ",
|
||||
inputType: "text",
|
||||
inputColumn: "Automatik",
|
||||
},
|
||||
{
|
||||
key: "isSystemUsed",
|
||||
label: "Systemtyp",
|
||||
inputType: "bool",
|
||||
inputColumn: "Automatik",
|
||||
disabledFunction: function () {
|
||||
return true
|
||||
}
|
||||
},
|
||||
],
|
||||
showTabs: [{label: "Informationen"}]
|
||||
},
|
||||
folders: {
|
||||
isArchivable: true,
|
||||
label: "Ordner",
|
||||
|
||||
Reference in New Issue
Block a user