Merge branch 'devCorrected' into 'beta'
Dev corrected See merge request fedeo/software!11
This commit is contained in:
@@ -3,7 +3,6 @@
|
|||||||
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
|
|||||||
@@ -172,13 +172,10 @@ const moveFile = async () => {
|
|||||||
</template>
|
</template>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div :class="useCapacitor().getIsNative() ? ['w-full'] : ['w-1/3']">
|
<div :class="useCapacitor().getIsNative() ? ['w-full'] : ['w-1/3']">
|
||||||
<object
|
<PDFViewer
|
||||||
class="bigPreview"
|
v-if="props.documentData.id && props.documentData.path.toLowerCase().includes('pdf')"
|
||||||
:data="`${props.documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
|
:file-id="props.documentData.id" />
|
||||||
type="application/pdf"
|
|
||||||
v-if="props.documentData.path.toLowerCase().includes('pdf')"
|
|
||||||
|
|
||||||
/>
|
|
||||||
<img
|
<img
|
||||||
class=" w-full"
|
class=" w-full"
|
||||||
:src="props.documentData.url"
|
:src="props.documentData.url"
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ defineShortcuts({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
@@ -150,9 +149,9 @@ const loadOptions = async () => {
|
|||||||
|
|
||||||
for await(const option of optionsToLoad) {
|
for await(const option of optionsToLoad) {
|
||||||
if(option.option === "countrys") {
|
if(option.option === "countrys") {
|
||||||
loadedOptions.value[option.option] = (await supabase.from("countrys").select()).data
|
loadedOptions.value[option.option] = useEntities("countrys").selectSpecial()
|
||||||
} else if(option.option === "units") {
|
} else if(option.option === "units") {
|
||||||
loadedOptions.value[option.option] = (await supabase.from("units").select()).data
|
loadedOptions.value[option.option] = useEntities("units").selectSpecial()
|
||||||
} else {
|
} else {
|
||||||
loadedOptions.value[option.option] = (await useEntities(option.option).select())
|
loadedOptions.value[option.option] = (await useEntities(option.option).select())
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useSum} from "~/composables/useSum.js";
|
import {useSum} from "~/composables/useSum.js";
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
/*'/': () => {
|
/*'/': () => {
|
||||||
//console.log(searchinput)
|
//console.log(searchinput)
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ const links = [{
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
shortcuts.value = true
|
shortcuts.value = true
|
||||||
}
|
}
|
||||||
}, {
|
},/* {
|
||||||
label: 'Tickets',
|
label: 'Tickets',
|
||||||
icon: 'i-heroicons-clipboard-document',
|
icon: 'i-heroicons-clipboard-document',
|
||||||
to: '/support',
|
to: '/support',
|
||||||
}, {
|
},*/ {
|
||||||
label: 'Webseite',
|
label: 'Webseite',
|
||||||
icon: 'i-heroicons-globe-europe-africa',
|
icon: 'i-heroicons-globe-europe-africa',
|
||||||
to: 'https://fedeo.de',
|
to: 'https://fedeo.de',
|
||||||
|
|||||||
@@ -29,10 +29,8 @@ const setup = async () => {
|
|||||||
if(await useCapacitor().getIsPhone()) platform.value = "mobile"
|
if(await useCapacitor().getIsPhone()) platform.value = "mobile"
|
||||||
|
|
||||||
if(props.type && props.elementId){
|
if(props.type && props.elementId){
|
||||||
//items.value = (await supabase.from("historyitems").select().eq(props.type,props.elementId).order("created_at",{ascending: true})).data || []
|
|
||||||
items.value = await useNuxtApp().$api(`/api/resource/${props.type}/${props.elementId}/history`)
|
items.value = await useNuxtApp().$api(`/api/resource/${props.type}/${props.elementId}/history`)
|
||||||
} /*else {
|
} /*else {
|
||||||
items.value = (await supabase.from("historyitems").select().order("created_at",{ascending: true})).data || []
|
|
||||||
|
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,11 +152,11 @@ const links = computed(() => {
|
|||||||
to: "/standardEntity/absencerequests",
|
to: "/standardEntity/absencerequests",
|
||||||
icon: "i-heroicons-document-text"
|
icon: "i-heroicons-document-text"
|
||||||
}] : [],
|
}] : [],
|
||||||
{
|
/*{
|
||||||
label: "Fahrten",
|
label: "Fahrten",
|
||||||
to: "/trackingTrips",
|
to: "/trackingTrips",
|
||||||
icon: "i-heroicons-map"
|
icon: "i-heroicons-map"
|
||||||
},
|
},*/
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
... [{
|
... [{
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, watch } from "vue"
|
import { ref, onMounted, watch } from "vue"
|
||||||
import { VPdfViewer } from "@vue-pdf-viewer/viewer"
|
import { VPdfViewer, useLicense } from "@vue-pdf-viewer/viewer"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// Beispiel: "FEDEO/26/filesbyid/11990345-8711-4e23-8851-c50f028fc915/RE25-1081.pdf"
|
// Beispiel: "FEDEO/26/filesbyid/11990345-8711-4e23-8851-c50f028fc915/RE25-1081.pdf"
|
||||||
fileId: {
|
fileId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
},
|
||||||
|
uri: {
|
||||||
|
type: String,
|
||||||
},
|
},
|
||||||
scale: {
|
scale: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -14,6 +16,9 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
useLicense(config.public.pdfViewerLicense || "eyJkYXRhIjoiZXlKMElqb2laR1YyWld4dmNHVnlJaXdpWVhaMUlqb3hOemt3TmpNNU9UazVMQ0prYlNJNklteHZZMkZzYUc5emREb3pNREF3SWl3aWJpSTZJbVkxT1RRM1ptWTBOVFJsTkdKaU16a2lMQ0psZUhBaU9qRTNPVEEyTXprNU9Ua3NJbVJ0ZENJNkluTndaV05wWm1saklpd2ljQ0k2SW5acFpYZGxjaUo5Iiwic2lnbmF0dXJlIjoibGVpVmNkbENzQStiNCt5ODBNVnZzY2lHcEpjZ3F4T2M1R2V3VFJJVzY5SkQwSGxteERRcmtmSzQwOWhzYXdJN2pSVXM3QmdvNzJIU3ZYRmZtS1Rhejl1N0o0eEt0Nk1VL2E5cjFMblA1aUZzZFBtemRMbFJFSDJoSG00Zmk2Y25UbEhnQTdHekkrL2JVVlJhUEJXYWFDdlFSK3ByRHg2NHl4aFhjOElPT2swb1pldWtKc2tCTjNCS0NiZ3VkSGhmbXB5TE9WUDc4SUR4U1FNR3BOU2I4eWRqakdQSHBuNjZLQWVOWGpRc05EbHNIdmJkaFFyNk1ZaldzV3JkRm50ditPR2FyT2MwQzJLNVJmZkRKWTBldVpXOFNiRTRLNlJkWTVSY084eVpBNzcrQWVlV0hOdHg0SllhV1hFMW5sYmZLZDVvM3V6b0dLTFYzODBUMmlvSERnPT0ifQ==")
|
||||||
|
|
||||||
const pdfSrc = ref(null) // ObjectURL fürs Viewer
|
const pdfSrc = ref(null) // ObjectURL fürs Viewer
|
||||||
const { $api } = useNuxtApp()
|
const { $api } = useNuxtApp()
|
||||||
|
|
||||||
@@ -30,7 +35,16 @@ async function loadPdf(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => loadPdf(props.fileId))
|
onMounted(() => {
|
||||||
|
if(props.fileId) {
|
||||||
|
loadPdf(props.fileId)
|
||||||
|
} else if(props.uri) {
|
||||||
|
pdfSrc.value = props.uri
|
||||||
|
}
|
||||||
|
|
||||||
|
//window.addEventListener("resize", handleZoomTool("pageWidth"), true);
|
||||||
|
|
||||||
|
})
|
||||||
watch(() => props.fileId, (newPath) => {
|
watch(() => props.fileId, (newPath) => {
|
||||||
if (newPath) loadPdf(newPath)
|
if (newPath) loadPdf(newPath)
|
||||||
})
|
})
|
||||||
@@ -38,6 +52,7 @@ watch(() => props.fileId, (newPath) => {
|
|||||||
const vpvRef = ref(null);
|
const vpvRef = ref(null);
|
||||||
|
|
||||||
//Zoom Control
|
//Zoom Control
|
||||||
|
|
||||||
const zoomControl = computed(() => vpvRef.value?.zoomControl)
|
const zoomControl = computed(() => vpvRef.value?.zoomControl)
|
||||||
|
|
||||||
const currentScale = computed(() => {
|
const currentScale = computed(() => {
|
||||||
@@ -45,6 +60,7 @@ const currentScale = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleZoomTool = (type) => {
|
const handleZoomTool = (type) => {
|
||||||
|
console.log(type)
|
||||||
const zoomCtrl = unref(zoomControl)
|
const zoomCtrl = unref(zoomControl)
|
||||||
if (!zoomCtrl) return
|
if (!zoomCtrl) return
|
||||||
|
|
||||||
@@ -57,7 +73,6 @@ const handleZoomTool = (type) => {
|
|||||||
zoomCtrl.zoom(type)
|
zoomCtrl.zoom(type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Page Control
|
//Page Control
|
||||||
const pageControl = computed(() => vpvRef.value?.pageControl)
|
const pageControl = computed(() => vpvRef.value?.pageControl)
|
||||||
const currentPageInput = computed(() => pageControl.value?.currentPage)
|
const currentPageInput = computed(() => pageControl.value?.currentPage)
|
||||||
@@ -93,10 +108,12 @@ const handleKeyPress = (event) => {
|
|||||||
//Handle Download
|
//Handle Download
|
||||||
const downloadControl = computed(() => vpvRef.value?.downloadControl)
|
const downloadControl = computed(() => vpvRef.value?.downloadControl)
|
||||||
|
|
||||||
const handleDownloadFile = () => {
|
const handleDownloadFile = async () => {
|
||||||
const downloadCtrl = unref(downloadControl)
|
await useFiles().downloadFile(props.fileId)
|
||||||
|
|
||||||
|
/*const downloadCtrl = unref(downloadControl)
|
||||||
if (!downloadCtrl) return
|
if (!downloadCtrl) return
|
||||||
downloadCtrl.download()
|
downloadCtrl.download()*/
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(downloadControl, (downloadCtrl) => {
|
watch(downloadControl, (downloadCtrl) => {
|
||||||
@@ -136,6 +153,11 @@ watch(downloadControl, (downloadCtrl) => {
|
|||||||
icon="i-heroicons-magnifying-glass-plus"
|
icon="i-heroicons-magnifying-glass-plus"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
></UButton>
|
></UButton>
|
||||||
|
<UButton
|
||||||
|
@click="handleDownloadFile"
|
||||||
|
variant="outline"
|
||||||
|
icon="i-heroicons-arrow-down-on-square"
|
||||||
|
/>
|
||||||
<UButton
|
<UButton
|
||||||
@click="prevPage"
|
@click="prevPage"
|
||||||
:disabled="isPreviousPageButtonDisable"
|
:disabled="isPreviousPageButtonDisable"
|
||||||
@@ -143,11 +165,7 @@ watch(downloadControl, (downloadCtrl) => {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
></UButton>
|
></UButton>
|
||||||
|
|
||||||
<UButton
|
|
||||||
@click="handleDownloadFile"
|
|
||||||
variant="outline"
|
|
||||||
icon="i-heroicons-arrow-down-on-square"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Page number input and total pages display -->
|
<!-- Page number input and total pages display -->
|
||||||
<div class="flex items-center text-sm font-normal">
|
<div class="flex items-center text-sm font-normal">
|
||||||
@@ -179,7 +197,11 @@ watch(downloadControl, (downloadCtrl) => {
|
|||||||
:toolbar-options="false"
|
:toolbar-options="false"
|
||||||
ref="vpvRef"
|
ref="vpvRef"
|
||||||
/>
|
/>
|
||||||
<div v-else>Lade PDF…</div>
|
<div v-else>
|
||||||
|
<UProgress
|
||||||
|
class="mt-5 w-2/3 mx-auto"
|
||||||
|
animation="carousel"></UProgress>
|
||||||
|
</div>
|
||||||
</div></template>
|
</div></template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
let unallocatedStatements = ref(0)
|
let unallocatedStatements = ref(0)
|
||||||
let bankaccounts = ref([])
|
let bankaccounts = ref([])
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import {useDataStore} from "~/stores/data.js";
|
|
||||||
|
|
||||||
|
|
||||||
export const useSupabaseSelect = async (relation,select = '*', sortColumn = null, ascending = true,noArchivedFiltering = false) => {
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
let data = null
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
|
|
||||||
const dataType = dataStore.dataTypes[relation]
|
|
||||||
|
|
||||||
if(sortColumn !== null ) {
|
|
||||||
data = (await supabase
|
|
||||||
.from(relation)
|
|
||||||
.select(select)
|
|
||||||
.eq("tenant", profileStore.currentTenant)
|
|
||||||
.order(sortColumn, {ascending: ascending})).data
|
|
||||||
} else {
|
|
||||||
data = (await supabase
|
|
||||||
.from(relation)
|
|
||||||
.select(select)
|
|
||||||
.eq("tenant", profileStore.currentTenant)).data
|
|
||||||
}
|
|
||||||
|
|
||||||
if(dataType && dataType.isArchivable && !noArchivedFiltering) {
|
|
||||||
data = data.filter(i => !i.archived)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSupabaseSelectSingle = async (relation,idToEq,select = '*' ) => {
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
let data = null
|
|
||||||
|
|
||||||
|
|
||||||
if(idToEq !== null) {
|
|
||||||
data = (await supabase
|
|
||||||
.from(relation)
|
|
||||||
.select(select)
|
|
||||||
.eq("tenant", profileStore.currentTenant)
|
|
||||||
.eq("id",idToEq)
|
|
||||||
.single()).data
|
|
||||||
} else {
|
|
||||||
data = (await supabase
|
|
||||||
.from(relation)
|
|
||||||
.select(select)
|
|
||||||
.eq("tenant", profileStore.currentTenant)
|
|
||||||
.single()).data
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,7 @@ services:
|
|||||||
# networks:
|
# networks:
|
||||||
# - traefik
|
# - traefik
|
||||||
environment:
|
environment:
|
||||||
SUPABASE_URL: "https://uwppvcxflrcsibuzsbil.supabase.co"
|
NUXT_PUBLIC_API_BASE: "https://backend.fedeo.io"
|
||||||
SUPABASE_KEY: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo"
|
|
||||||
# labels:
|
# labels:
|
||||||
# - "traefik.enable=true"
|
# - "traefik.enable=true"
|
||||||
# - "traefik.docker.network=traefik"
|
# - "traefik.docker.network=traefik"
|
||||||
|
|||||||
@@ -57,5 +57,13 @@ export default defineNuxtConfig({
|
|||||||
prefix: "Tiptap"
|
prefix: "Tiptap"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
runtimeConfig: {
|
||||||
|
|
||||||
|
public: {
|
||||||
|
apiBase: '',
|
||||||
|
pdfviewerLicense: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
compatibilityDate: '2024-12-18'
|
compatibilityDate: '2024-12-18'
|
||||||
})
|
})
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const route = useRoute()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
|
|
||||||
const searchString = ref("")
|
|
||||||
const showAssigned = ref(false)
|
|
||||||
const selectedAccount = ref(0)
|
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
|
||||||
let statements = dataStore.bankStatements
|
|
||||||
|
|
||||||
if(!showAssigned.value) {
|
|
||||||
statements = statements.filter(statement => !statement.customerInvoice || !statement.vendorInvoice)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(selectedAccount.value !== 0) {
|
|
||||||
statements = statements.filter(statement => statement.account === selectedAccount.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(searchString.value.length > 0) {
|
|
||||||
statements = statements.filter(item => {
|
|
||||||
return Object.values(item).some((value) => {
|
|
||||||
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return statements
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const showStatementSlideover = ref(false)
|
|
||||||
const selectedStatement = ref({})
|
|
||||||
|
|
||||||
const selectStatement = (statement) => {
|
|
||||||
selectedStatement.value = statement
|
|
||||||
showStatementSlideover.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const statementColumns = [
|
|
||||||
{
|
|
||||||
key:"amount",
|
|
||||||
label: "Betrag"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"date",
|
|
||||||
label: "Datum",
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "credName",
|
|
||||||
label: "Empfänger"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "debName",
|
|
||||||
label: "Sender"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "text",
|
|
||||||
label: "Verwendungszweck"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<USlideover
|
|
||||||
v-model="showStatementSlideover"
|
|
||||||
>
|
|
||||||
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<DevOnly>
|
|
||||||
{{selectedStatement}}
|
|
||||||
</DevOnly>
|
|
||||||
</UCard>
|
|
||||||
|
|
||||||
</USlideover>
|
|
||||||
|
|
||||||
<InputGroup :gap="2">
|
|
||||||
<UInput
|
|
||||||
v-model="searchString"
|
|
||||||
placeholder="Suche..."
|
|
||||||
/>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.bankAccounts.filter(account => account.used)"
|
|
||||||
v-model="selectedAccount"
|
|
||||||
option-attribute="iban"
|
|
||||||
value-attribute="id"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.bankAccounts.find(account => account.id === selectedAccount) ? dataStore.bankAccounts.find(account => account.id === selectedAccount).iban : "Kontoauswählen"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
<UCheckbox
|
|
||||||
v-model="showAssigned"
|
|
||||||
label="Zugeordnete Anzeigen"
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
<UTable
|
|
||||||
:rows="filteredRows"
|
|
||||||
:columns="statementColumns"
|
|
||||||
@select="selectStatement"
|
|
||||||
>
|
|
||||||
<template #amount-data="{row}">
|
|
||||||
<span
|
|
||||||
v-if="row.amount >= 0"
|
|
||||||
class="text-primary-500"
|
|
||||||
>
|
|
||||||
{{row.amount.toFixed(2) + " €"}}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="row.amount < 0"
|
|
||||||
class="text-rose-600"
|
|
||||||
>
|
|
||||||
{{row.amount.toFixed(2) + " €"}}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template #date-data="{row}">
|
|
||||||
{{dayjs(row.date).format("DD.MM.YY")}}
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const profiles = ref([])
|
|
||||||
|
|
||||||
const itemInfo = ref({})
|
|
||||||
const selectedProfiles = ref([])
|
|
||||||
|
|
||||||
const setup = async () => {
|
|
||||||
profiles.value = await useSupabaseSelect("profiles")
|
|
||||||
selectedProfiles.value = [profileStore.activeProfile.id]
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
|
|
||||||
const createChat = async () => {
|
|
||||||
const {data,error} = await supabase.from("chats").insert({
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
name: itemInfo.value.name
|
|
||||||
}).select()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else if(data){
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
let memberships = selectedProfiles.value.map(i => {
|
|
||||||
return {
|
|
||||||
profile_id: i,
|
|
||||||
chat_id: data[0].id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const {error} = await supabase.from("chatmembers").insert(memberships)
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
navigateTo("/chats")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar>
|
|
||||||
<template #left>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-chevron-left"
|
|
||||||
variant="outline"
|
|
||||||
@click="navigateTo(`/chats`)"
|
|
||||||
>
|
|
||||||
Chats
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
<template #center>
|
|
||||||
<h1
|
|
||||||
v-if="itemInfo"
|
|
||||||
class="text-xl font-medium"
|
|
||||||
>Chat Erstellen</h1>
|
|
||||||
</template>
|
|
||||||
<template #right>
|
|
||||||
<UButton
|
|
||||||
@click="createChat"
|
|
||||||
>
|
|
||||||
Erstellen
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="navigateTo(`/chats`)"
|
|
||||||
color="red"
|
|
||||||
class="ml-2"
|
|
||||||
>
|
|
||||||
Abbrechen
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UDashboardPanelContent>
|
|
||||||
<UForm>
|
|
||||||
<UFormGroup
|
|
||||||
label="Name:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.name"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Beteiligte Benutzer:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="selectedProfiles"
|
|
||||||
:options="profiles"
|
|
||||||
option-attribute="fullName"
|
|
||||||
value-attribute="id"
|
|
||||||
searchable
|
|
||||||
multiple
|
|
||||||
:search-attributes="['fullName']"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{selectedProfiles.length > 0 ? selectedProfiles.map(i => profileStore.getProfileById(i).fullName).join(", ") : "Keine Benutzer ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
</UForm>
|
|
||||||
</UDashboardPanelContent>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'/': () => {
|
|
||||||
//console.log(searchinput)
|
|
||||||
//searchinput.value.focus()
|
|
||||||
document.getElementById("searchinput").focus()
|
|
||||||
},
|
|
||||||
'+': () => {
|
|
||||||
router.push("/chats/create")
|
|
||||||
},
|
|
||||||
'Enter': {
|
|
||||||
usingInput: true,
|
|
||||||
handler: () => {
|
|
||||||
router.push(`/chats/show/${filteredRows.value[selectedItem.value].id}`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowdown': () => {
|
|
||||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
|
||||||
selectedItem.value += 1
|
|
||||||
} else {
|
|
||||||
selectedItem.value = 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowup': () => {
|
|
||||||
if(selectedItem.value === 0) {
|
|
||||||
selectedItem.value = filteredRows.value.length - 1
|
|
||||||
} else {
|
|
||||||
selectedItem.value -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const items = ref([])
|
|
||||||
const selectedItem = ref(0)
|
|
||||||
|
|
||||||
const setup = async () => {
|
|
||||||
items.value = await useSupabaseSelect("chats","*, profiles(*)")
|
|
||||||
//items.value = await useSupabaseSelect("chats","*, profiles(*), chatmessages(*)")
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateColumns = [
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "Name"
|
|
||||||
},{
|
|
||||||
key: "profiles",
|
|
||||||
label: "Mitglieder"
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
const selectedColumns = ref(templateColumns)
|
|
||||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
|
||||||
|
|
||||||
const searchString = ref('')
|
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
|
||||||
|
|
||||||
return useListFilter(searchString.value, items.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
setup()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar title="Chats" :badge="filteredRows.length">
|
|
||||||
<template #right>
|
|
||||||
<UInput
|
|
||||||
id="searchinput"
|
|
||||||
v-model="searchString"
|
|
||||||
icon="i-heroicons-funnel"
|
|
||||||
autocomplete="off"
|
|
||||||
placeholder="Suche..."
|
|
||||||
class="hidden lg:block"
|
|
||||||
@keydown.esc="$event.target.blur()"
|
|
||||||
>
|
|
||||||
<template #trailing>
|
|
||||||
<UKbd value="/" />
|
|
||||||
</template>
|
|
||||||
</UInput>
|
|
||||||
|
|
||||||
<UButton @click="router.push(`/chats/create`)">+ Chat</UButton>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UDashboardToolbar>
|
|
||||||
<template #right>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="selectedColumns"
|
|
||||||
icon="i-heroicons-adjustments-horizontal-solid"
|
|
||||||
:options="templateColumns"
|
|
||||||
multiple
|
|
||||||
class="hidden lg:block"
|
|
||||||
by="key"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
Spalten
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</template>
|
|
||||||
</UDashboardToolbar>
|
|
||||||
<UTable
|
|
||||||
:rows="filteredRows"
|
|
||||||
:columns="columns"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
|
||||||
@select="(i) => router.push(`/chats/show/${i.id}`) "
|
|
||||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Chats anzuzeigen' }"
|
|
||||||
>
|
|
||||||
<template #name-data="{row}">
|
|
||||||
<span class="text-primary-500 font-bold" v-if="row === filteredRows[selectedItem]">{{row.name}}</span>
|
|
||||||
<span v-else>{{row.name}}</span>
|
|
||||||
</template>
|
|
||||||
<template #profiles-data="{row}">
|
|
||||||
{{row.profiles.map(i => i.fullName).join(", ")}}
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { format, isToday } from 'date-fns'
|
|
||||||
import dayjs from "dayjs"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
' ': () => {
|
|
||||||
document.getElementById("textinput").focus()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const itemInfo = ref({})
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const setup = async () => {
|
|
||||||
itemInfo.value = await useSupabaseSelectSingle("chats",useRoute().params.id,"*, profiles(*), chatmessages(*)")
|
|
||||||
|
|
||||||
let unseenMessages = itemInfo.value.chatmessages.filter(i => !i.seenBy.includes(profileStore.activeProfile.id))
|
|
||||||
|
|
||||||
|
|
||||||
for await (const message of unseenMessages){
|
|
||||||
await supabase.from("chatmessages").update({seenBy: [...message.seenBy, profileStore.activeProfile.id]}).eq("id",message.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
setup()
|
|
||||||
|
|
||||||
const messageText = ref("")
|
|
||||||
const sendMessage = async () => {
|
|
||||||
if(messageText.value.length > 0) {
|
|
||||||
const message = {
|
|
||||||
origin: profileStore.activeProfile.id,
|
|
||||||
destinationchat: itemInfo.value.id,
|
|
||||||
text: messageText.value,
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
seenBy: [profileStore.activeProfile.id]
|
|
||||||
}
|
|
||||||
|
|
||||||
const {data,error} = await supabase.from("chatmessages").insert(message)
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
//Reset
|
|
||||||
messageText.value = ""
|
|
||||||
//Create Notifications
|
|
||||||
let notifications = itemInfo.value.profiles.filter(i => i.id !== profileStore.activeProfile.id).map(i => {
|
|
||||||
return {
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
profile: i.id,
|
|
||||||
initiatingProfile: profileStore.activeProfile.id,
|
|
||||||
title: `Sie haben eine neue Nachricht im Chat ${itemInfo.value.name}`,
|
|
||||||
link: `/chats/show/${itemInfo.value.id}`,
|
|
||||||
message: message.text
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(notifications)
|
|
||||||
|
|
||||||
const {error} = await supabase.from("notifications").insert(notifications)
|
|
||||||
|
|
||||||
setup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar :title="itemInfo.name">
|
|
||||||
<template #center>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<template #right>
|
|
||||||
<UAvatarGroup size="sm" :max="2">
|
|
||||||
<UAvatar
|
|
||||||
v-if="itemInfo.profiles"
|
|
||||||
v-for="avatar in itemInfo.profiles.map(i => i.fullName)"
|
|
||||||
:alt="avatar" size="sm" />
|
|
||||||
</UAvatarGroup>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<div class="scrollList p-5">
|
|
||||||
<UAlert
|
|
||||||
:color="message.seenBy.includes(profileStore.activeProfile.id) ? 'white' : 'primary'"
|
|
||||||
:variant="message.seenBy.includes(profileStore.activeProfile.id) ? 'solid' : 'outline'"
|
|
||||||
class="my-2"
|
|
||||||
v-for="message in itemInfo.chatmessages"
|
|
||||||
:description="message.text"
|
|
||||||
:avatar="{ alt: profileStore.getProfileById(message.origin).fullName }"
|
|
||||||
:title="`${profileStore.getProfileById(message.origin).fullName} - ${isToday(new Date(message.created_at)) ? dayjs(message.created_at).format('HH:mm') : dayjs(message.created_at).format('DD.MM.YYYY HH:mm')}`"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row justify-between p-5">
|
|
||||||
<UInput
|
|
||||||
class="flex-auto mr-2"
|
|
||||||
v-model="messageText"
|
|
||||||
@keyup.enter="sendMessage"
|
|
||||||
placeholder="Deine Nachricht"
|
|
||||||
id="textinput"
|
|
||||||
/>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-chevron-double-right-solid"
|
|
||||||
@click="sendMessage"
|
|
||||||
:disabled="messageText.length === 0"
|
|
||||||
>
|
|
||||||
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -134,7 +134,6 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
if (route.query.loadMode === "deliveryNotes") {
|
if (route.query.loadMode === "deliveryNotes") {
|
||||||
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
||||||
//let linkedDocuments = (await supabase.from("createddocuments").select().in("id", JSON.parse(route.query.linkedDocuments))).data
|
|
||||||
|
|
||||||
//TODO: Implement Checking for Same Customer, Contact and Project
|
//TODO: Implement Checking for Same Customer, Contact and Project
|
||||||
|
|
||||||
@@ -203,7 +202,6 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
} else if (route.query.loadMode === "finalInvoice") {
|
} else if (route.query.loadMode === "finalInvoice") {
|
||||||
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
||||||
//let linkedDocuments = (await supabase.from("createddocuments").select().in("id", JSON.parse(route.query.linkedDocuments))).data
|
|
||||||
|
|
||||||
//TODO: Implement Checking for Same Customer, Contact and Project
|
//TODO: Implement Checking for Same Customer, Contact and Project
|
||||||
|
|
||||||
@@ -3030,12 +3028,17 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
>
|
>
|
||||||
Show
|
Show
|
||||||
</UButton>-->
|
</UButton>-->
|
||||||
<object
|
<PDFViewer
|
||||||
|
v-if="showDocument"
|
||||||
|
:uri="uri"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- <object
|
||||||
:data="uri"
|
:data="uri"
|
||||||
v-if="showDocument"
|
v-if="showDocument"
|
||||||
type="application/pdf"
|
type="application/pdf"
|
||||||
class="w-full previewDocumentMobile"
|
class="w-full previewDocumentMobile"
|
||||||
/>
|
/>-->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
//TODO: BACKENDCHANGE EMAIL SENDING
|
//TODO: BACKENDCHANGE EMAIL SENDING
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -24,7 +23,9 @@ const loadedDocuments = ref([])
|
|||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
const noAccountsPresent = ref(false)
|
const noAccountsPresent = ref(false)
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
emailAccounts.value = await useEntities("emailAccounts").select()
|
//emailAccounts.value = await useEntities("emailAccounts").select()
|
||||||
|
|
||||||
|
emailAccounts.value = await useNuxtApp().$api("/api/email/accounts")
|
||||||
|
|
||||||
if(emailAccounts.value.length === 0) {
|
if(emailAccounts.value.length === 0) {
|
||||||
noAccountsPresent.value = true
|
noAccountsPresent.value = true
|
||||||
@@ -130,7 +131,6 @@ const sendEmail = async () => {
|
|||||||
|
|
||||||
for await (const doc of loadedDocuments.value) {
|
for await (const doc of loadedDocuments.value) {
|
||||||
|
|
||||||
//const {data,error} = await supabase.storage.from("filesdev").download(doc.path)
|
|
||||||
|
|
||||||
const res = await useFiles().downloadFile(doc.id, null, true)
|
const res = await useFiles().downloadFile(doc.id, null, true)
|
||||||
|
|
||||||
@@ -143,7 +143,9 @@ const sendEmail = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await useNuxtApp().$api("/api/emailasuser/send",{
|
console.log(body)
|
||||||
|
|
||||||
|
const res = await useNuxtApp().$api("/api/email/send",{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: body,
|
body: body,
|
||||||
})
|
})
|
||||||
@@ -151,9 +153,6 @@ const sendEmail = async () => {
|
|||||||
console.log(res)
|
console.log(res)
|
||||||
|
|
||||||
|
|
||||||
/*const { data, error } = await supabase.functions.invoke('send_email', {
|
|
||||||
body
|
|
||||||
})*/
|
|
||||||
if(!res.success) {
|
if(!res.success) {
|
||||||
toast.add({title: "Fehler beim Absenden der E-Mail", color: "rose"})
|
toast.add({title: "Fehler beim Absenden der E-Mail", color: "rose"})
|
||||||
|
|
||||||
@@ -208,7 +207,7 @@ const sendEmail = async () => {
|
|||||||
>
|
>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
:options="emailAccounts"
|
:options="emailAccounts"
|
||||||
option-attribute="emailAddress"
|
option-attribute="email"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
v-model="emailData.account"
|
v-model="emailData.account"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
|
|
||||||
const currentSubmission = (await supabase.from("formSubmits").select().eq('id',route.params.id)).data[0]
|
|
||||||
const form = (await supabase.from("forms").select().eq('id',currentSubmission.formType)).data[0]
|
|
||||||
|
|
||||||
const formData = ref({})
|
|
||||||
const submitted = ref(currentSubmission.submitted)
|
|
||||||
|
|
||||||
const submitForm = async () => {
|
|
||||||
|
|
||||||
submitted.value = true
|
|
||||||
|
|
||||||
console.log(formData.value)
|
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("formSubmits")
|
|
||||||
.update({values: formData.value, submitted: true})
|
|
||||||
.eq('id',currentSubmission.id)
|
|
||||||
.select()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else if( data) {
|
|
||||||
formData.value = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<UForm
|
|
||||||
v-if="!submitted"
|
|
||||||
@submit="submitForm"
|
|
||||||
@reset="formData = {}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="item in form.fields"
|
|
||||||
>
|
|
||||||
<p v-if="item.type === 'header'">{{item.label}}</p>
|
|
||||||
<UFormGroup
|
|
||||||
v-else-if="item.type.includes('Input')"
|
|
||||||
:label="item.required ? item.label + '*' : item.label"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-if="item.type === 'textInput'"
|
|
||||||
v-model="formData[item.key]"
|
|
||||||
:required="item.required"
|
|
||||||
/>
|
|
||||||
<UInput
|
|
||||||
v-else-if="item.type === 'numberInput'"
|
|
||||||
v-model="formData[item.key]"
|
|
||||||
:required="item.required"
|
|
||||||
type="number"
|
|
||||||
inputmode="numeric"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<UButton type="submit">
|
|
||||||
Abschicken
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
type="reset"
|
|
||||||
color="rose"
|
|
||||||
class="m-2"
|
|
||||||
>
|
|
||||||
Zurücksetzen
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
|
|
||||||
</UForm>
|
|
||||||
<div v-else>
|
|
||||||
Dieses Formular wurde bereits abgeschickt. Möchten Sie erneut Daten abschicken, sprechen Sie bitte Ihren Ansprechpartner an, um das Formular freizuschalten.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,674 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import InputGroup from "~/components/InputGroup.vue";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const itemInfo = ref({
|
|
||||||
vendor: 0,
|
|
||||||
expense: true,
|
|
||||||
reference: "",
|
|
||||||
date: null,
|
|
||||||
dueDate: null,
|
|
||||||
paymentType: "Überweisung",
|
|
||||||
description: "",
|
|
||||||
state: "Entwurf",
|
|
||||||
accounts: [
|
|
||||||
{
|
|
||||||
account: null,
|
|
||||||
amountNet: null,
|
|
||||||
amountTax: null,
|
|
||||||
taxType: "19",
|
|
||||||
costCentre: null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const availableDocuments = ref([])
|
|
||||||
const costcentres = ref([])
|
|
||||||
const accounts = ref([])
|
|
||||||
const vendors = ref([])
|
|
||||||
const setup = async () => {
|
|
||||||
let filetype = (await supabase.from("filetags").select().eq("tenant",profileStore.currentTenant).eq("incomingDocumentType","invoices").single()).data.id
|
|
||||||
console.log(filetype)
|
|
||||||
let ids = (await supabase.from("files").select("id").eq("tenant",profileStore.currentTenant).eq("type", filetype).is("incominginvoice",null)).data.map(i => i.id)
|
|
||||||
availableDocuments.value = await useFiles().selectSomeDocuments(ids)
|
|
||||||
accounts.value = (await supabase.from("accounts").select().order("number",{ascending:true})).data
|
|
||||||
vendors.value = await useSupabaseSelect("vendors")
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
|
|
||||||
const loadCostCentres = async () => {
|
|
||||||
costcentres.value = await useSupabaseSelect("costcentres")
|
|
||||||
|
|
||||||
}
|
|
||||||
loadCostCentres()
|
|
||||||
|
|
||||||
const useNetMode = ref(false)
|
|
||||||
|
|
||||||
const loadedFile = ref(null)
|
|
||||||
const loadFile = async (id) => {
|
|
||||||
console.log(id)
|
|
||||||
loadedFile.value = await useFiles().selectDocument(id)
|
|
||||||
console.log(loadedFile.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const changeNetMode = (mode) => {
|
|
||||||
useNetMode.value = mode
|
|
||||||
|
|
||||||
itemInfo.value.accounts = [{account: null,amountNet: null,amountTax: null,taxType: '19'}]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const taxOptions = ref([
|
|
||||||
{
|
|
||||||
label: "19% USt",
|
|
||||||
percentage: 19,
|
|
||||||
key: "19"
|
|
||||||
},{
|
|
||||||
label: "7% USt",
|
|
||||||
percentage: 7,
|
|
||||||
key: "7"
|
|
||||||
},{
|
|
||||||
label: "Innergemeintschaftlicher Erwerb 19%",
|
|
||||||
percentage: 0,
|
|
||||||
key: "19I"
|
|
||||||
},{
|
|
||||||
label: "Innergemeintschaftlicher Erwerb 7%",
|
|
||||||
percentage: 0,
|
|
||||||
key: "7I"
|
|
||||||
},{
|
|
||||||
label: "§13b UStG",
|
|
||||||
percentage: 0,
|
|
||||||
key: "13B"
|
|
||||||
},{
|
|
||||||
label: "Keine USt",
|
|
||||||
percentage: 0,
|
|
||||||
key: "null"
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
const totalCalculated = computed(() => {
|
|
||||||
let totalNet = 0
|
|
||||||
let totalAmount19Tax = 0
|
|
||||||
let totalAmount7Tax = 0
|
|
||||||
let totalAmount0Tax = 0
|
|
||||||
let totalGross = 0
|
|
||||||
|
|
||||||
itemInfo.value.accounts.forEach(account => {
|
|
||||||
if(account.amountNet) totalNet += account.amountNet
|
|
||||||
|
|
||||||
if(account.taxType === "19" && account.amountTax) {
|
|
||||||
totalAmount19Tax += account.amountTax
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
totalGross = Number(totalNet + totalAmount19Tax)
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalNet,
|
|
||||||
totalAmount19Tax,
|
|
||||||
totalGross
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
const createIncomingInvoice = async () => {
|
|
||||||
const data = await dataStore.createNewItem('incominginvoices',itemInfo.value,true)
|
|
||||||
|
|
||||||
const {error} = await supabase.from("files").update({incominginvoice: data.id}).eq("id",loadedFile.value.id)
|
|
||||||
|
|
||||||
router.push(`/incominginvoices/show/${data.id}`)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const setCostCentre = async (item,data) => {
|
|
||||||
await loadCostCentres()
|
|
||||||
item.costCentre = data.id
|
|
||||||
}
|
|
||||||
|
|
||||||
const gptLoading = ref(false)
|
|
||||||
|
|
||||||
const getInvoiceData = async () => {
|
|
||||||
gptLoading.value = true
|
|
||||||
console.log(loadedFile.value)
|
|
||||||
|
|
||||||
|
|
||||||
//loadedFile.value.url
|
|
||||||
|
|
||||||
|
|
||||||
/*let data = {
|
|
||||||
"invoice_number": "3423478673",
|
|
||||||
"invoice_date": "2025-05-30",
|
|
||||||
"invoice_type": "incoming",
|
|
||||||
"delivery_type": "null",
|
|
||||||
"delivery_note_number": "null",
|
|
||||||
"reference": "null",
|
|
||||||
"issuer": {
|
|
||||||
"name": "Boels Rental Germany GmbH",
|
|
||||||
"address": "Emeranstraße 49-51, 85622 Feldkirchen, Deutschland",
|
|
||||||
"phone": "+49-(0)1801663225",
|
|
||||||
"email": "fakturierung@boels.de",
|
|
||||||
"bank": "ABN AMRO Bank N.V.",
|
|
||||||
"bic": "ABNANL2A",
|
|
||||||
"iban": "NL09 ABNA 0520 5585 61"
|
|
||||||
},
|
|
||||||
"recipient": {
|
|
||||||
"name": "Federspiel Technology UG",
|
|
||||||
"address": "Am Schwarzen Brack 14, 26452 Sande, Deutschland",
|
|
||||||
"phone": "null",
|
|
||||||
"email": "null"
|
|
||||||
},
|
|
||||||
"invoice_items": [
|
|
||||||
{
|
|
||||||
"description": "Bautrockner 50 ltr.",
|
|
||||||
"unit": "piece",
|
|
||||||
"quantity": 1,
|
|
||||||
"total": 395.22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Servicepauschale Kat. A",
|
|
||||||
"unit": "piece",
|
|
||||||
"quantity": 1,
|
|
||||||
"total": 32.1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Haftungsbegrenzung A: (Schäden, exkl. Feuer/Diebstahl/Einbruch)",
|
|
||||||
"unit": "piece",
|
|
||||||
"quantity": 1,
|
|
||||||
"total": 3.2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Haftungsbegrenzung B: (Feuer/Diebstahl/Einbruch)",
|
|
||||||
"unit": "piece",
|
|
||||||
"quantity": 1,
|
|
||||||
"total": 16.93
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subtotal": 89.1,
|
|
||||||
"tax_rate": 19,
|
|
||||||
"tax": 16.93,
|
|
||||||
"total": 106.03,
|
|
||||||
"terms": "Dieser Betrag wird automatisch mittels Lastschrift von ihrem Konto eingezogen"
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
console.log(data.subtotal)*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let data = await useFunctions().useGetInvoiceData(loadedFile.value)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(data.invoice_number) itemInfo.value.reference = data.invoice_number
|
|
||||||
if(data.invoice_date) itemInfo.value.date = dayjs(data.invoice_date)
|
|
||||||
if(data.issuer.id) itemInfo.value.vendor = data.issuer.id
|
|
||||||
if(data.invoice_duedate) itemInfo.value.dueDate = dayjs(data.invoice_duedate)
|
|
||||||
if(data.terms) itemInfo.value.paymentType = data.terms
|
|
||||||
if(data.subtotal) {
|
|
||||||
itemInfo.value.accounts = [
|
|
||||||
{
|
|
||||||
account: null,
|
|
||||||
amountNet: data.subtotal,
|
|
||||||
amountTax: data.tax,
|
|
||||||
taxType: String(data.tax_rate),
|
|
||||||
costCentre: null,
|
|
||||||
amountGross: Number(data.subtotal) + Number(data.tax)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if(data.terms === "Direct Debit") {
|
|
||||||
itemInfo.value.paymentType = "Einzug"
|
|
||||||
} else if(data.terms === "Transfer") {
|
|
||||||
itemInfo.value.paymentType = "Überweisung"
|
|
||||||
} else if(data.terms === "Credit Card") {
|
|
||||||
itemInfo.value.paymentType = "Kreditkarte"
|
|
||||||
} else if(data.terms === "Other") {
|
|
||||||
itemInfo.value.paymentType = "Sonstiges"
|
|
||||||
}
|
|
||||||
|
|
||||||
let description = ""
|
|
||||||
|
|
||||||
if(data.delivery_note_number) description += `Lieferschein: ${data.delivery_note_number} \n`
|
|
||||||
if(data.reference) description += `Referenz: ${data.reference} \n`
|
|
||||||
if(data.invoice_items) {
|
|
||||||
data.invoice_items.forEach(item => {
|
|
||||||
description += `${item.description} - ${item.quantity} ${item.unit} - ${item.total}\n`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
itemInfo.value.description = description
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
gptLoading.value = false
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar :title="'Eingangsbeleg erstellen'">
|
|
||||||
<template #right>
|
|
||||||
<UButton
|
|
||||||
@click="createIncomingInvoice"
|
|
||||||
>
|
|
||||||
Speichern
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UDashboardPanelContent>
|
|
||||||
<div v-if="!loadedFile">
|
|
||||||
<DocumentList
|
|
||||||
v-if="availableDocuments.length > 0"
|
|
||||||
:documents="availableDocuments"
|
|
||||||
:return-document-id="true"
|
|
||||||
@selectDocument="(documentId) => loadFile(documentId)"
|
|
||||||
/>
|
|
||||||
<div v-else class="w-full text-center">
|
|
||||||
<span class="text-xl font-medium mt-10">Keine Dateien zum zuweisen verfügbar</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="flex justify-between mt-5 workingContainer"
|
|
||||||
>
|
|
||||||
<object
|
|
||||||
v-if="loadedFile"
|
|
||||||
:data="loadedFile.url + '#toolbar=0&navpanes=0&scrollbar=0&statusbar=0&messages=0&scrollbar=0'"
|
|
||||||
type="application/pdf"
|
|
||||||
class="mx-5 documentPreview"
|
|
||||||
/>
|
|
||||||
<div class="w-3/5 mx-5">
|
|
||||||
|
|
||||||
<div v-if="mode === 'show'">
|
|
||||||
|
|
||||||
<div class="truncate mb-5">
|
|
||||||
<p>Status: {{itemInfo.state}}</p>
|
|
||||||
<p>Datum: {{dayjs(itemInfo.date).format('DD.MM.YYYY')}}</p>
|
|
||||||
<p>Fälligkeitsdatum: {{dayjs(itemInfo.dueDate).format('DD.MM.YYYY')}}</p>
|
|
||||||
<p>Lieferant: <nuxt-link :to="`/vendors/show/${itemInfo.vendor}`">{{dataStore.getVendorById(itemInfo.vendor).name}}</nuxt-link></p>
|
|
||||||
<p>Bezahlt: {{itemInfo.paid}}</p>
|
|
||||||
<p>Beschreibung: {{itemInfo.description}}</p>
|
|
||||||
|
|
||||||
<!-- TODO: Buchungszeilen darstellen -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<HistoryDisplay
|
|
||||||
type="incomingInvoice"
|
|
||||||
v-if="itemInfo"
|
|
||||||
:element-id="itemInfo.id"
|
|
||||||
:render-headline="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class=" scrollContainer">
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-sparkles"
|
|
||||||
class="my-3"
|
|
||||||
variant="outline"
|
|
||||||
@click="getInvoiceData"
|
|
||||||
:disabled="gptLoading"
|
|
||||||
>
|
|
||||||
KI - Vorschlag
|
|
||||||
<UProgress v-if="gptLoading" animation="carousel"/>
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
<InputGroup class="mb-3">
|
|
||||||
<UButton
|
|
||||||
:variant="itemInfo.expense ? 'solid' : 'outline'"
|
|
||||||
@click="itemInfo.expense = true"
|
|
||||||
>
|
|
||||||
Ausgabe
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
:variant="!itemInfo.expense ? 'solid' : 'outline'"
|
|
||||||
@click="itemInfo.expense = false"
|
|
||||||
>
|
|
||||||
Einnahme
|
|
||||||
</UButton>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
<UFormGroup label="Lieferant:" >
|
|
||||||
<InputGroup>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="itemInfo.vendor"
|
|
||||||
:options="dataStore.vendors"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
searchable
|
|
||||||
:search-attributes="['name','vendorNumber']"
|
|
||||||
class="flex-auto"
|
|
||||||
searchable-placeholder="Suche..."
|
|
||||||
:color="!itemInfo.vendor ? 'rose' : 'primary'"
|
|
||||||
@change="vendors.find(i => i.id === itemInfo.vendor).defaultPaymentMethod ? itemInfo.paymentType = vendors.find(i => i.id === itemInfo.vendor).defaultPaymentMethod : null"
|
|
||||||
>
|
|
||||||
<template #option="{option}">
|
|
||||||
{{option.vendorNumber}} - {{option.name}}
|
|
||||||
</template>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor) ? dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor).name : 'Lieferant auswählen'}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
<EntityModalButtons
|
|
||||||
type="vendors"
|
|
||||||
:id="itemInfo.vendor"
|
|
||||||
@return-data="(data) => itemInfo.vendor = data.id"
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
class="mt-3"
|
|
||||||
label="Rechnungsreferenz:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.reference"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<InputGroup class="mt-3" gap="2">
|
|
||||||
<UFormGroup label="Rechnungsdatum:">
|
|
||||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-calendar-days-20-solid"
|
|
||||||
:label="itemInfo.date ? dayjs(itemInfo.date).format('DD.MM.YYYY') : 'Datum auswählen'"
|
|
||||||
variant="outline"
|
|
||||||
:color="!itemInfo.date ? 'rose' : 'primary'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #panel="{ close }">
|
|
||||||
<LazyDatePicker v-model="itemInfo.date" @close="itemInfo.dueDate = itemInfo.date" />
|
|
||||||
</template>
|
|
||||||
</UPopover>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup label="Fälligkeitsdatum:">
|
|
||||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-calendar-days-20-solid"
|
|
||||||
:label="itemInfo.dueDate ? dayjs(itemInfo.dueDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
|
||||||
variant="outline"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #panel="{ close }">
|
|
||||||
<LazyDatePicker v-model="itemInfo.dueDate" @close="close" />
|
|
||||||
</template>
|
|
||||||
</UPopover>
|
|
||||||
</UFormGroup>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<UFormGroup label="Zahlart:" >
|
|
||||||
<USelectMenu
|
|
||||||
:options="['Einzug','Kreditkarte','Überweisung','Sonstiges']"
|
|
||||||
v-model="itemInfo.paymentType"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup label="Beschreibung:" >
|
|
||||||
<UTextarea
|
|
||||||
v-model="itemInfo.description"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<InputGroup class="my-3">
|
|
||||||
<UButton
|
|
||||||
:variant="!useNetMode ? 'solid' : 'outline'"
|
|
||||||
@click="changeNetMode(false)"
|
|
||||||
>
|
|
||||||
Brutto
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
:variant="useNetMode ? 'solid' : 'outline'"
|
|
||||||
@click="changeNetMode(true)"
|
|
||||||
>
|
|
||||||
Netto
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Brutto
|
|
||||||
<UToggle
|
|
||||||
v-model="useNetMode"
|
|
||||||
@update:model-value="itemInfo.accounts = [{account: null,amountNet: null,amountTax: null,taxType: '19'}]"
|
|
||||||
/>
|
|
||||||
Netto-->
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
<table v-if="itemInfo.accounts.length > 1" class="w-full">
|
|
||||||
<tr>
|
|
||||||
<td>Gesamt exkl. Steuer: </td>
|
|
||||||
<td class="text-right">{{totalCalculated.totalNet.toFixed(2).replace(".",",")}} €</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>19% Steuer: </td>
|
|
||||||
<td class="text-right">{{totalCalculated.totalAmount19Tax.toFixed(2).replace(".",",")}} €</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Gesamt inkl. Steuer: </td>
|
|
||||||
<td class="text-right">{{totalCalculated.totalGross.toFixed(2).replace(".",",")}} €</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="my-3"
|
|
||||||
v-for="(item,index) in itemInfo.accounts"
|
|
||||||
>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Kategorie"
|
|
||||||
class="mb-3"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="accounts"
|
|
||||||
option-attribute="label"
|
|
||||||
value-attribute="id"
|
|
||||||
searchable
|
|
||||||
:search-attributes="['label']"
|
|
||||||
searchable-placeholder="Suche..."
|
|
||||||
v-model="item.account"
|
|
||||||
:color="!item.account ? 'rose' : 'primary'"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{accounts.find(account => account.id === item.account) ? accounts.find(account => account.id === item.account).label : "Keine Kategorie ausgewählt" }}
|
|
||||||
</template>
|
|
||||||
<template #option="{option}">
|
|
||||||
{{option.number}} - {{option.label}}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Kostenstelle"
|
|
||||||
class=" mb-3"
|
|
||||||
>
|
|
||||||
<InputGroup class="w-full">
|
|
||||||
<USelectMenu
|
|
||||||
:options="costcentres"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
searchable
|
|
||||||
:search-attributes="['label']"
|
|
||||||
searchable-placeholder="Suche..."
|
|
||||||
v-model="item.costCentre"
|
|
||||||
class="flex-auto"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{costcentres.find(i => i.id === item.costCentre) ? costcentres.find(i => i.id === item.costCentre).name : "Keine Kostenstelle ausgewählt" }}
|
|
||||||
</template>
|
|
||||||
<template #option="{option}">
|
|
||||||
<span v-if="option.vehicle">{{option.number}} - Fahrzeug - {{option.name}}</span>
|
|
||||||
<span v-else-if="option.project">{{option.number}} - Projekt - {{option.name}}</span>
|
|
||||||
<span v-else-if="option.inventoryitem">{{option.number}} - Inventarartikel - {{option.name}}</span>
|
|
||||||
<span v-else>{{option.number}} - {{option.name}}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</USelectMenu>
|
|
||||||
<UButton
|
|
||||||
variant="outline"
|
|
||||||
color="rose"
|
|
||||||
v-if="item.costCentre"
|
|
||||||
icon="i-heroicons-x-mark"
|
|
||||||
@click="item.costCentre = null"
|
|
||||||
/>
|
|
||||||
<EntityModalButtons
|
|
||||||
type="costcentres"
|
|
||||||
:id="item.costCentre"
|
|
||||||
@return-data="(data) => setCostCentre(item,data)"
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<InputGroup>
|
|
||||||
<UFormGroup
|
|
||||||
v-if="useNetMode"
|
|
||||||
label="Gesamtbetrag exkl. Steuer in EUR"
|
|
||||||
class="flex-auto truncate"
|
|
||||||
:help="item.taxType !== null ? `Betrag inkl. Steuern: ${String(Number(item.amountNet + item.amountTax).toFixed(2)).replace('.',',')} €` : 'Zuerst Steuertyp festlegen' "
|
|
||||||
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
v-model="item.amountNet"
|
|
||||||
:color="!item.amountNet ? 'rose' : 'primary'"
|
|
||||||
:disabled="item.taxType === null"
|
|
||||||
@keyup="item.amountTax = Number((item.amountNet * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)),
|
|
||||||
item.amountGross = Number(item.amountNet) + Number(item.amountTax)"
|
|
||||||
>
|
|
||||||
<template #trailing>
|
|
||||||
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
|
||||||
</template>
|
|
||||||
</UInput>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
v-else
|
|
||||||
label="Gesamtbetrag inkl. Steuer in EUR"
|
|
||||||
class="flex-auto"
|
|
||||||
:help="item.taxType !== null ? `Betrag exkl. Steuern: ${item.amountNet ? String(item.amountNet.toFixed(2)).replace('.',',') : '0,00'} €` : 'Zuerst Steuertyp festlegen' "
|
|
||||||
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
:disabled="item.taxType === null"
|
|
||||||
v-model="item.amountGross"
|
|
||||||
:color="!item.amountGross ? 'rose' : 'primary'"
|
|
||||||
:ui-menu="{ width: 'min-w-max' }"
|
|
||||||
@keyup="item.amountNet = Number((item.amountGross / (1 + Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)),
|
|
||||||
item.amountTax = Number((item.amountGross - item.amountNet).toFixed(2))"
|
|
||||||
>
|
|
||||||
<template #trailing>
|
|
||||||
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
|
||||||
</template>
|
|
||||||
</UInput>
|
|
||||||
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Umsatzsteuer"
|
|
||||||
class="w-32"
|
|
||||||
:help="`Betrag: ${item.amountTax ? String(item.amountTax).replace('.',',') : '0,00'} €`"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="taxOptions"
|
|
||||||
v-model="item.taxType"
|
|
||||||
value-attribute="key"
|
|
||||||
:ui-menu="{ width: 'min-w-max' }"
|
|
||||||
option-attribute="label"
|
|
||||||
@change="item.amountNet = Number((item.amountGross / (1 + Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)),
|
|
||||||
item.amountTax = Number(((item.amountNet ? item.amountNet : 0) * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2))"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
<span class="truncate">{{taxOptions.find(i => i.key === item.taxType) ? taxOptions.find(i => i.key === item.taxType).label : ""}}</span>
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
|
|
||||||
<UButton
|
|
||||||
class="mt-3"
|
|
||||||
@click="itemInfo.accounts = [...itemInfo.accounts.slice(0,index+1),{account:null, amountNet: null, amountTax:null, taxType: '19'} , ...itemInfo.accounts.slice(index+1)]"
|
|
||||||
>
|
|
||||||
Betrag aufteilen
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
v-if="index !== 0"
|
|
||||||
class="mt-3"
|
|
||||||
variant="ghost"
|
|
||||||
color="rose"
|
|
||||||
@click="itemInfo.accounts = itemInfo.accounts.filter((account,itemIndex) => itemIndex !== index)"
|
|
||||||
>
|
|
||||||
Position entfernen
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</UDashboardPanelContent>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.documentPreview {
|
|
||||||
aspect-ratio: 1 / 1.414;
|
|
||||||
height: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.scrollContainer {
|
|
||||||
overflow-y: scroll;
|
|
||||||
padding-left: 1em;
|
|
||||||
padding-right: 1em;
|
|
||||||
height: 75vh;
|
|
||||||
-ms-overflow-style: none; /* IE and Edge */
|
|
||||||
scrollbar-width: none; /* Firefox */
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollContainer::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.lineItemRow {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workingContainer {
|
|
||||||
height: 80vh;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
import InputGroup from "~/components/InputGroup.vue";
|
import InputGroup from "~/components/InputGroup.vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const type = "incominginvoices"
|
|||||||
const dataType = dataStore.dataTypes[type]
|
const dataType = dataStore.dataTypes[type]
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
//items.value = await useSupabaseSelect("incominginvoices","*, vendor(id,name), statementallocations(id,amount)","created_at",false)
|
|
||||||
items.value = await useEntities(type).select("*, vendor(id,name), statementallocations(id,amount)",sort.value.column,sort.value.direction === "asc")
|
items.value = await useEntities(type).select("*, vendor(id,name), statementallocations(id,amount)",sort.value.column,sort.value.direction === "asc")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import dayjs from "dayjs";
|
|||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -43,7 +42,6 @@ const loading = ref(true)
|
|||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
if((mode.value === "show") && route.params.id){
|
if((mode.value === "show") && route.params.id){
|
||||||
//itemInfo.value = await useSupabaseSelectSingle("incominginvoices",route.params.id,"*, files(*), vendor(*)")
|
|
||||||
itemInfo.value = await useEntities("incominginvoices").selectSingle(route.params.id,"*, files(*), vendor(*)")
|
itemInfo.value = await useEntities("incominginvoices").selectSingle(route.params.id,"*, files(*), vendor(*)")
|
||||||
if(process.dev) console.log(itemInfo.value)
|
if(process.dev) console.log(itemInfo.value)
|
||||||
currentDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
|
currentDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
|
||||||
|
|||||||
@@ -66,23 +66,8 @@ definePageMeta({
|
|||||||
middleware: 'redirect-to-mobile-index'
|
middleware: 'redirect-to-mobile-index'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
import DisplayPresentProfiles from "~/components/noAutoLoad/displayPresentProfiles.vue";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const toast = useToast()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const { isNotificationsSlideoverOpen } = useDashboard()
|
const { isNotificationsSlideoverOpen } = useDashboard()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const user = useSupabaseUser()
|
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,426 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const router = useRouter()
|
|
||||||
const mode = ref("incoming")
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const inventoryChangeData = ref({
|
|
||||||
productId: null,
|
|
||||||
sourceSpaceId: null,
|
|
||||||
sourceProjectId: null,
|
|
||||||
destinationSpaceId: null,
|
|
||||||
destinationProjectId: null,
|
|
||||||
quantity: 1,
|
|
||||||
serials: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const resetInput = () => {
|
|
||||||
inventoryChangeData.value = {
|
|
||||||
productId: null,
|
|
||||||
sourceSpaceId: null,
|
|
||||||
sourceProjectId: null,
|
|
||||||
destinationSpaceId: null,
|
|
||||||
destinationProjectId: null,
|
|
||||||
quantity: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const createMovement = async () => {
|
|
||||||
|
|
||||||
let movements = []
|
|
||||||
|
|
||||||
if(mode.value === 'incoming'){
|
|
||||||
|
|
||||||
let movement = {
|
|
||||||
productId: inventoryChangeData.value.productId,
|
|
||||||
spaceId: inventoryChangeData.value.destinationSpaceId,
|
|
||||||
projectId: inventoryChangeData.value.destinationProjectId,
|
|
||||||
quantity: inventoryChangeData.value.quantity,
|
|
||||||
profileId: profileStore.activeProfile.id,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
|
|
||||||
movements.push(movement)
|
|
||||||
|
|
||||||
/*const {error} = await supabase
|
|
||||||
.from("movements")
|
|
||||||
.insert([inventoryChangeData.value])
|
|
||||||
.select()
|
|
||||||
if(error) console.log(error)*/
|
|
||||||
|
|
||||||
|
|
||||||
} else if (mode.value === 'outgoing'){
|
|
||||||
|
|
||||||
let movement = {
|
|
||||||
productId: inventoryChangeData.value.productId,
|
|
||||||
spaceId: inventoryChangeData.value.sourceSpaceId,
|
|
||||||
projectId: inventoryChangeData.value.sourceProjectId,
|
|
||||||
quantity: inventoryChangeData.value.quantity * -1,
|
|
||||||
profileId: profileStore.activeProfile.id,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
|
|
||||||
movements.push(movement)
|
|
||||||
} else if (mode.value === 'change'){
|
|
||||||
let outMovement = {
|
|
||||||
productId: inventoryChangeData.value.productId,
|
|
||||||
spaceId: inventoryChangeData.value.sourceSpaceId,
|
|
||||||
projectId: inventoryChangeData.value.sourceProjectId,
|
|
||||||
quantity: inventoryChangeData.value.quantity * -1,
|
|
||||||
profileId: profileStore.activeProfile.id,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
let inMovement = {
|
|
||||||
productId: inventoryChangeData.value.productId,
|
|
||||||
spaceId: inventoryChangeData.value.destinationSpaceId,
|
|
||||||
projectId: inventoryChangeData.value.destinationProjectId,
|
|
||||||
quantity: inventoryChangeData.value.quantity,
|
|
||||||
profileId: profileStore.activeProfile.id,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
|
|
||||||
movements.push(outMovement)
|
|
||||||
movements.push(inMovement)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(movements)
|
|
||||||
|
|
||||||
const {error} = await supabase
|
|
||||||
.from("movements")
|
|
||||||
.insert(movements)
|
|
||||||
.select()
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
resetInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
meta_enter: {
|
|
||||||
usingInput: true,
|
|
||||||
handler: () => {
|
|
||||||
createMovement()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function checkProductId(productId) {
|
|
||||||
return dataStore.products.filter(product =>product.id === productId).length > 0;
|
|
||||||
}
|
|
||||||
function checkSpaceId(spaceId) {
|
|
||||||
return dataStore.spaces.filter(space => space.id === spaceId).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkProjectId(projectId) {
|
|
||||||
return dataStore.projects.some(i => i.id === projectId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeFocusToSpaceId() {
|
|
||||||
document.getElementById('spaceIdInput').focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeFocusToQuantity() {
|
|
||||||
document.getElementById('quantityInput').focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeFocusToBarcode() {
|
|
||||||
document.getElementById('barcodeInput').focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const findProductByBarcodeOrEAN = (input) => {
|
|
||||||
return dataStore.products.find(i => i.barcode === input || i.ean === input || i.articleNumber === input)
|
|
||||||
}
|
|
||||||
|
|
||||||
const findSpaceBySpaceNumber = (input) => {
|
|
||||||
return dataStore.spaces.find(i => i.spaceNumber === input)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const barcodeInput = ref("")
|
|
||||||
const showBarcodeTip = ref(true)
|
|
||||||
const serialInput = ref("")
|
|
||||||
|
|
||||||
const processBarcodeInput = () => {
|
|
||||||
if(findProductByBarcodeOrEAN(barcodeInput.value) && !findSpaceBySpaceNumber(barcodeInput.value)){
|
|
||||||
//Set Product
|
|
||||||
|
|
||||||
inventoryChangeData.value.productId = findProductByBarcodeOrEAN(barcodeInput.value).id
|
|
||||||
} else if (!findProductByBarcodeOrEAN(barcodeInput.value) && findSpaceBySpaceNumber(barcodeInput.value)){
|
|
||||||
//Set Space
|
|
||||||
|
|
||||||
if(mode.value === 'incoming'){
|
|
||||||
inventoryChangeData.value.destinationSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
|
||||||
} else if(mode.value === 'outgoing') {
|
|
||||||
inventoryChangeData.value.sourceSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
|
||||||
} else if(mode.value === 'change') {
|
|
||||||
if(!inventoryChangeData.value.sourceSpaceId){
|
|
||||||
inventoryChangeData.value.sourceSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
|
||||||
} else {
|
|
||||||
inventoryChangeData.value.destinationSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//console.log(findSpaceBySpaceNumber(barcodeInput.value))
|
|
||||||
}
|
|
||||||
barcodeInput.value = ""
|
|
||||||
//console.log(movementData.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar
|
|
||||||
title="Lager Vorgänge"
|
|
||||||
>
|
|
||||||
<template #right>
|
|
||||||
<UButton
|
|
||||||
@click="resetInput"
|
|
||||||
class="mt-3"
|
|
||||||
color="rose"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
Abbrechen
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="createMovement"
|
|
||||||
:disabled="mode === '' && checkSpaceId(inventoryChangeData.spaceId) && checkProductId(inventoryChangeData.productId)"
|
|
||||||
class="mt-3"
|
|
||||||
>
|
|
||||||
Bestätigen
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UDashboardPanelContent>
|
|
||||||
<div class="w-80 mx-auto mt-5">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<UButton
|
|
||||||
@click="mode = 'incoming'"
|
|
||||||
class="my-2"
|
|
||||||
:variant="mode === 'incoming' ? 'solid' : 'outline'"
|
|
||||||
>Wareneingang</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="mode = 'outgoing'"
|
|
||||||
class="my-2"
|
|
||||||
:variant="mode === 'outgoing' ? 'solid' : 'outline'"
|
|
||||||
>Warenausgang</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="mode = 'change'"
|
|
||||||
class="my-2"
|
|
||||||
:variant="mode === 'change' ? 'solid' : 'outline'"
|
|
||||||
>Umlagern</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<UAlert
|
|
||||||
title="Info"
|
|
||||||
variant="outline"
|
|
||||||
color="primary"
|
|
||||||
v-if="showBarcodeTip"
|
|
||||||
@close="showBarcodeTip = false"
|
|
||||||
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'link', padded: false }"
|
|
||||||
description="Über die Barcode Eingabe können folgende Werte automatisch erkannt werden: Quelllagerplatz, Ziellagerplatz, Artikel (EAN oder Barcode). Es wird immer zuerst der Quell- und anschließend der Ziellagerplatz ausgefüllt."
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <UTooltip
|
|
||||||
text="Über die Barcode Eingabe könenn folgende Werte automatisch erkannt werden: Quell Lagerplatz, Ziellagerplatz, Artikel(EAN oder Barcode). Es wird immer zuerst der Quell- und anschließend der Ziellagerplatz ausgefüllt."
|
|
||||||
>-->
|
|
||||||
<UFormGroup
|
|
||||||
label="Barcode:"
|
|
||||||
class="mt-3"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
@keyup.enter="processBarcodeInput"
|
|
||||||
@focusout="processBarcodeInput"
|
|
||||||
@input="processBarcodeInput"
|
|
||||||
v-model="barcodeInput"
|
|
||||||
id="barcodeInput"
|
|
||||||
|
|
||||||
/>
|
|
||||||
|
|
||||||
</UFormGroup>
|
|
||||||
<!-- <template #text>
|
|
||||||
<span class="text-wrap">Über die Barcode Eingabe könenn folgende Werte automatisch erkannt werden: Quell Lagerplatz, Ziellagerplatz, Artikel(EAN oder Barcode). Es wird immer zuerst der Quell- und anschließend der Ziellagerplatz ausgefüllt.</span>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</UTooltip>-->
|
|
||||||
|
|
||||||
<UDivider
|
|
||||||
class="mt-5 w-80"
|
|
||||||
v-if="mode !== 'incoming'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Quell Lagerplatz:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
v-if="mode !== 'incoming' "
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.spaces"
|
|
||||||
searchable
|
|
||||||
option-attribute="spaceNumber"
|
|
||||||
:color="checkSpaceId(inventoryChangeData.sourceSpaceId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.sourceSpaceId"
|
|
||||||
@change="inventoryChangeData.sourceProjectId = null"
|
|
||||||
value-attribute="id"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.spaces.find(space => space.id === inventoryChangeData.sourceSpaceId) ? dataStore.spaces.find(space => space.id === inventoryChangeData.sourceSpaceId).description : "Kein Lagerplatz ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Quell Projekt:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
v-if="mode !== 'incoming' "
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.projects"
|
|
||||||
searchable
|
|
||||||
option-attribute="name"
|
|
||||||
:color="checkProjectId(inventoryChangeData.sourceProjectId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.sourceProjectId"
|
|
||||||
@change="inventoryChangeData.sourceSpaceId = null"
|
|
||||||
value-attribute="id"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.getProjectById(inventoryChangeData.sourceProjectId) ? dataStore.getProjectById(inventoryChangeData.sourceProjectId).name : "Kein Projekt ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UDivider
|
|
||||||
class="mt-5 w-80"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Artikel:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.products"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
variant="outline"
|
|
||||||
searchable
|
|
||||||
:search-attributes="['name','ean', 'barcode']"
|
|
||||||
:color="checkProductId(inventoryChangeData.productId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.productId"
|
|
||||||
v-on:select="changeFocusToSpaceId"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.products.find(product => product.id === inventoryChangeData.productId) ? dataStore.products.find(product => product.id === inventoryChangeData.productId).name : "Bitte Artikel auswählen"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UDivider
|
|
||||||
class="mt-5 w-80"
|
|
||||||
v-if="mode !== 'outgoing'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Ziel Lagerplatz:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
v-if="mode !== 'outgoing'"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.spaces"
|
|
||||||
searchable
|
|
||||||
option-attribute="spaceNumber"
|
|
||||||
:color="checkSpaceId(inventoryChangeData.destinationSpaceId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.destinationSpaceId"
|
|
||||||
@change="inventoryChangeData.destinationProjectId = null"
|
|
||||||
value-attribute="id"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.spaces.find(space => space.id === inventoryChangeData.destinationSpaceId) ? dataStore.spaces.find(space => space.id === inventoryChangeData.destinationSpaceId).description : "Kein Lagerplatz ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Ziel Projekt:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
v-if="mode !== 'outgoing'"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.projects"
|
|
||||||
searchable
|
|
||||||
option-attribute="name"
|
|
||||||
:color="checkProjectId(inventoryChangeData.destinationProjectId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.destinationProjectId"
|
|
||||||
value-attribute="id"
|
|
||||||
@change="inventoryChangeData.destinationSpaceId = null"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.getProjectById(inventoryChangeData.destinationProjectId) ? dataStore.getProjectById(inventoryChangeData.destinationProjectId).name : "Kein Projekt ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UDivider
|
|
||||||
class="mt-5 w-80"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Anzahl:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
variant="outline"
|
|
||||||
color="primary"
|
|
||||||
placeholder="Anzahl"
|
|
||||||
v-model="inventoryChangeData.quantity"
|
|
||||||
type="number"
|
|
||||||
id="quantityInput"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Seriennummern:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
>
|
|
||||||
<InputGroup class="w-full">
|
|
||||||
<UInput
|
|
||||||
variant="outline"
|
|
||||||
color="primary"
|
|
||||||
placeholder="Seriennummern"
|
|
||||||
v-model="serialInput"
|
|
||||||
/>
|
|
||||||
<UButton
|
|
||||||
@click="inventoryChangeData.serials.push(serialInput)"
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</UButton>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li v-for="serial in inventoryChangeData.serials">{{serial}}</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</UDashboardPanelContent>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
<template>
|
|
||||||
<UDashboardNavbar title="Bestände" :badge="filteredRows.length">
|
|
||||||
<template #right>
|
|
||||||
<UInput
|
|
||||||
id="searchinput"
|
|
||||||
v-model="searchString"
|
|
||||||
icon="i-heroicons-funnel"
|
|
||||||
autocomplete="off"
|
|
||||||
placeholder="Suche..."
|
|
||||||
class="hidden lg:block"
|
|
||||||
@keydown.esc="$event.target.blur()"
|
|
||||||
>
|
|
||||||
<template #trailing>
|
|
||||||
<UKbd value="/" />
|
|
||||||
</template>
|
|
||||||
</UInput>
|
|
||||||
|
|
||||||
<UButton @click="router.push(`/products/create`)">+ Artikel</UButton>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
|
|
||||||
<UDashboardToolbar>
|
|
||||||
|
|
||||||
<template #right>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="selectedColumns"
|
|
||||||
icon="i-heroicons-adjustments-horizontal-solid"
|
|
||||||
:options="templateColumns"
|
|
||||||
multiple
|
|
||||||
class="hidden lg:block"
|
|
||||||
by="key"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
Spalten
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</template>
|
|
||||||
</UDashboardToolbar>
|
|
||||||
<UTable
|
|
||||||
:rows="filteredRows"
|
|
||||||
:columns="columns"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
|
||||||
@select="(i) => router.push(`/products/show/${i.id}`) "
|
|
||||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Artikel anzuzeigen' }"
|
|
||||||
>
|
|
||||||
<template #name-data="{row}">
|
|
||||||
<span
|
|
||||||
v-if="row === filteredRows[selectedItem]"
|
|
||||||
class="text-primary-500 font-bold">{{row.name}}</span>
|
|
||||||
<span v-else>
|
|
||||||
{{row.name}}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<template #stock-data="{row}">
|
|
||||||
{{`${dataStore.getStockByProductId(row.id)} ${(dataStore.units.find(unit => unit.id === row.unit) ? dataStore.units.find(unit => unit.id === row.unit).name : "")}`}}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</UTable>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'/': () => {
|
|
||||||
document.getElementById("searchinput").focus()
|
|
||||||
},
|
|
||||||
'+': () => {
|
|
||||||
router.push("/products/create")
|
|
||||||
},
|
|
||||||
'Enter': {
|
|
||||||
usingInput: true,
|
|
||||||
handler: () => {
|
|
||||||
router.push(`/products/show/${filteredRows.value[selectedItem.value].id}`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowdown': () => {
|
|
||||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
|
||||||
selectedItem.value += 1
|
|
||||||
} else {
|
|
||||||
selectedItem.value = 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowup': () => {
|
|
||||||
if(selectedItem.value === 0) {
|
|
||||||
selectedItem.value = filteredRows.value.length - 1
|
|
||||||
} else {
|
|
||||||
selectedItem.value -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const items = ref([])
|
|
||||||
const selectedItem = ref(0)
|
|
||||||
|
|
||||||
const setupPage = async () => {
|
|
||||||
items.value = await useSupabaseSelect("products","*")
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
|
|
||||||
|
|
||||||
const templateColumns = [
|
|
||||||
{
|
|
||||||
key: "stock",
|
|
||||||
label: "Bestand"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "Name",
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const selectedColumns = ref(templateColumns)
|
|
||||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
|
||||||
|
|
||||||
const templateTags = computed(() => {
|
|
||||||
let temp = []
|
|
||||||
|
|
||||||
dataStore.products.forEach(row => {
|
|
||||||
row.tags.forEach(tag => {
|
|
||||||
if(!temp.includes(tag)) temp.push(tag)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return temp
|
|
||||||
|
|
||||||
})
|
|
||||||
const selectedTags = ref(templateTags.value)
|
|
||||||
|
|
||||||
const searchString = ref('')
|
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
|
||||||
let temp = items.value.filter(i => i.tags.some(x => selectedTags.value.includes(x)) || i.tags.length === 0)
|
|
||||||
|
|
||||||
return useSearch(searchString.value, temp)
|
|
||||||
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||||
import DocumentList from "~/components/DocumentList.vue";
|
|
||||||
import DocumentUpload from "~/components/DocumentUpload.vue";
|
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,7 @@ const checkBIC = async () => {
|
|||||||
|
|
||||||
const generateLink = async (bankId) => {
|
const generateLink = async (bankId) => {
|
||||||
try {
|
try {
|
||||||
/*const {data,error} = await supabase.functions.invoke(`bankstatement_gateway`,{
|
|
||||||
body: {
|
|
||||||
method: "generateLink",
|
|
||||||
institutionId: bankData.value.id,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
})*/
|
|
||||||
const link = await useFunctions().useBankingGenerateLink(bankId || bankData.value.id)
|
const link = await useFunctions().useBankingGenerateLink(bankId || bankData.value.id)
|
||||||
|
|
||||||
await navigateTo(link, {
|
await navigateTo(link, {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const items = [{
|
const items = [{
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar >
|
|
||||||
<template #left>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-chevron-left"
|
|
||||||
variant="outline"
|
|
||||||
@click="router.push(`/settings/labels`)"
|
|
||||||
>
|
|
||||||
Labels
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
<template #center>
|
|
||||||
<h1
|
|
||||||
v-if="itemInfo"
|
|
||||||
class="text-xl font-medium"
|
|
||||||
>{{itemInfo.name ? `Label: ${itemInfo.name}` : (mode === 'create' ? 'Label erstellen' : 'Label bearbeiten')}}</h1>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
|
|
||||||
<UTabs
|
|
||||||
:items="[{label: 'Informationen'}]"
|
|
||||||
v-if="mode === 'show' && itemInfo"
|
|
||||||
class="p-5"
|
|
||||||
v-model="openTab"
|
|
||||||
>
|
|
||||||
<template #item="{item}">
|
|
||||||
<UCard class="mt-5">
|
|
||||||
<div
|
|
||||||
v-if="item.label === 'Informationen'"
|
|
||||||
class="flex flex-row"
|
|
||||||
>
|
|
||||||
<div class="w-1/2 mr-5">
|
|
||||||
<UDivider>Allgemeines</UDivider>
|
|
||||||
<Toolbar>
|
|
||||||
<UButton @click="usePrintLabel('0dbe30f3-3008-4cde-8a7c-e785b1c22bfc','ZD411',useGenerateZPL(itemInfo.handlebarsZPL,{barcode:'XXX'}))">Test Druck</UButton>
|
|
||||||
|
|
||||||
</Toolbar>
|
|
||||||
<p>Name: {{itemInfo.name}}</p>
|
|
||||||
<p>Breite in Zoll: {{itemInfo.widthInch}}"</p>
|
|
||||||
<p>Höhe in Zoll: {{itemInfo.heightInch}}"</p>
|
|
||||||
<p>ZPL:</p>
|
|
||||||
<pre>{{itemInfo.handlebarsZPL}}</pre>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="w-1/2">
|
|
||||||
<UDivider>Vorschau</UDivider>
|
|
||||||
<img
|
|
||||||
class="mx-auto mt-5"
|
|
||||||
v-if="demoZPL"
|
|
||||||
:src="`https://api.labelary.com/v1/printers/8dpmm/labels/${itemInfo.widthInch}x${itemInfo.heightInch}/0/${demoZPL}`"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</UCard>
|
|
||||||
</template>
|
|
||||||
</UTabs>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'backspace': () => {
|
|
||||||
router.push("/settings/labels")
|
|
||||||
},
|
|
||||||
'arrowleft': () => {
|
|
||||||
if(openTab.value > 0){
|
|
||||||
openTab.value -= 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowright': () => {
|
|
||||||
if(openTab.value < 3) {
|
|
||||||
openTab.value += 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const mode = useRoute().params.mode
|
|
||||||
const openTab = ref(0)
|
|
||||||
|
|
||||||
const itemInfo = ref({})
|
|
||||||
|
|
||||||
const setupPage = async () => {
|
|
||||||
itemInfo.value = await useSupabaseSelectSingle("printLabels",useRoute().params.id,'*')
|
|
||||||
renderDemoZPL()
|
|
||||||
}
|
|
||||||
|
|
||||||
const demoZPL = ref("")
|
|
||||||
const renderDemoZPL = () => {
|
|
||||||
let template = Handlebars.compile(itemInfo.value.handlebarsZPL)
|
|
||||||
|
|
||||||
demoZPL.value = template({barcode: "XXX"})
|
|
||||||
}
|
|
||||||
|
|
||||||
const printLabel = async () => {
|
|
||||||
await supabase.from("printJobs").insert({
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
rawContent: useGenerateZPL(itemInfo.value.handlebarsZPL,{barcode:"XXX"}),
|
|
||||||
printerName: "ZD411",
|
|
||||||
printServer: "0dbe30f3-3008-4cde-8a7c-e785b1c22bfc"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
img {
|
|
||||||
border: 1px solid black
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar
|
|
||||||
title="Labels"
|
|
||||||
>
|
|
||||||
<template #right>
|
|
||||||
<UInput
|
|
||||||
id="searchinput"
|
|
||||||
v-model="searchString"
|
|
||||||
icon="i-heroicons-funnel"
|
|
||||||
autocomplete="off"
|
|
||||||
placeholder="Suche..."
|
|
||||||
class="hidden lg:block"
|
|
||||||
@keydown.esc="$event.target.blur()"
|
|
||||||
>
|
|
||||||
<template #trailing>
|
|
||||||
<UKbd value="/" />
|
|
||||||
</template>
|
|
||||||
</UInput>
|
|
||||||
|
|
||||||
<UButton @click="router.push(`/settings/labels/create`)" disabled>+ Label</UButton>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<!-- <UDashboardToolbar>
|
|
||||||
|
|
||||||
</UDashboardToolbar>-->
|
|
||||||
|
|
||||||
<UTable
|
|
||||||
:rows="items"
|
|
||||||
:columns="columns"
|
|
||||||
@select="(i) => router.push(`/settings/labels/show/${i.id}`)"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
|
||||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Artikel anzuzeigen' }"
|
|
||||||
>
|
|
||||||
<template #name-data="{row}">
|
|
||||||
<span
|
|
||||||
v-if="row === filteredRows[selectedItem]"
|
|
||||||
class="text-primary-500 font-bold">{{row.name}}</span>
|
|
||||||
<span v-else>
|
|
||||||
{{row.name}}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'/': () => {
|
|
||||||
//console.log(searchinput)
|
|
||||||
//searchinput.value.focus()
|
|
||||||
document.getElementById("searchinput").focus()
|
|
||||||
},
|
|
||||||
'+': () => {
|
|
||||||
router.push("/settings/labels/create")
|
|
||||||
},
|
|
||||||
'Enter': {
|
|
||||||
usingInput: true,
|
|
||||||
handler: () => {
|
|
||||||
router.push(`/settings/labels/show/${filteredRows.value[selectedItem.value].id}`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowdown': () => {
|
|
||||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
|
||||||
selectedItem.value += 1
|
|
||||||
} else {
|
|
||||||
selectedItem.value = 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowup': () => {
|
|
||||||
if(selectedItem.value === 0) {
|
|
||||||
selectedItem.value = filteredRows.value.length - 1
|
|
||||||
} else {
|
|
||||||
selectedItem.value -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const items = ref([])
|
|
||||||
const selectedItem = ref(0)
|
|
||||||
|
|
||||||
const setupPage = async () => {
|
|
||||||
items.value = await useSupabaseSelect("printLabels","*")
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
|
|
||||||
const templateColumns = [{key: 'name',label:'Name'},{key: 'widthInch',label:'Breite in Zoll'},{key: 'heightInch',label:'Höhe in Zoll'}]
|
|
||||||
const selectedColumns = ref(templateColumns)
|
|
||||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
|
||||||
|
|
||||||
const searchString = ref('')
|
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
|
||||||
|
|
||||||
return useSearch(searchString.value, items.value)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const data = await supabase.from("profiles").select('* , tenants (id, name)')
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
let rights = {
|
|
||||||
createUser: {label: "Benutzer erstellen"},
|
|
||||||
modifyUser: {label: "Benutzer bearbeiten"},
|
|
||||||
deactivateUser: {label: "Benutzer sperren"},
|
|
||||||
createProject: {label: "Projekt erstellen"},
|
|
||||||
viewOwnProjects: {label: "Eigene Projekte sehen"},
|
|
||||||
viewAllProjects: {label: "Alle Projekte sehen"},
|
|
||||||
createTask: {label: "Aufgabe erstellen"},
|
|
||||||
viewOwnTasks: {label:"Eigene Aufgaben sehen"},
|
|
||||||
viewAllTasks: {label: "Alle Aufgaben sehen"},
|
|
||||||
trackOwnTime: {label:"Eigene Zeite erfassen"},
|
|
||||||
createOwnTime: {label:"Eigene Zeiten erstellen"},
|
|
||||||
createTime: {label:"Zeiten erstellen"},
|
|
||||||
viewOwnTimes: {label:"Eigene Zeiten anzeigen"},
|
|
||||||
viewTimes: {label:"Zeiten anzeigen"},
|
|
||||||
}
|
|
||||||
|
|
||||||
let roles = [
|
|
||||||
{
|
|
||||||
key: "tenantAdmin",
|
|
||||||
label: "Firmenadministrator",
|
|
||||||
rights: [
|
|
||||||
...Object.keys(rights)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"worker",
|
|
||||||
label: "Monteur",
|
|
||||||
rights: [
|
|
||||||
"viewOwnProjects",
|
|
||||||
"createTasks",
|
|
||||||
"viewOwnTasks"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"manager",
|
|
||||||
label: "Vorarbeiter",
|
|
||||||
rights: [
|
|
||||||
"createProjects",
|
|
||||||
"viewOwnProjects",
|
|
||||||
"createTasks",
|
|
||||||
"viewOwnTasks",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"booker",
|
|
||||||
label: "Buchhalter",
|
|
||||||
rights: [
|
|
||||||
"createTasks",
|
|
||||||
"viewOwnTasks",
|
|
||||||
"createTime",
|
|
||||||
"viewAllTimes"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -6,12 +6,13 @@ import dayjs from "dayjs";
|
|||||||
const supabase = useSupabaseClient()
|
const supabase = useSupabaseClient()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const itemInfo = ref({})
|
const itemInfo = ref({})
|
||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
itemInfo.value = (await supabase.from("tickets").select("*, ticketmessages(*, profile(fullName, tenant, id)), created_by(*)").eq("id",useRoute().params.id).single()).data
|
itemInfo.value = await useEntities("tickets").selectSingle(useRoute().params.id,"*, ticketmessages(*), created_by(*)")
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,86 +21,17 @@ setup()
|
|||||||
const messageContent = ref("")
|
const messageContent = ref("")
|
||||||
|
|
||||||
const addMessage = async () => {
|
const addMessage = async () => {
|
||||||
const {data,error} = await supabase.from("ticketmessages").insert({
|
const res = await useEntities("ticketmessages").create({
|
||||||
profile: profileStore.activeProfile.id,
|
auth_user: auth.user.user_id,
|
||||||
content: messageContent.value,
|
content: messageContent.value,
|
||||||
ticket: itemInfo.value.id,
|
ticket: itemInfo.value.id,
|
||||||
internal: false,
|
internal: false,
|
||||||
type: "Nachricht"
|
type: "Nachricht"
|
||||||
}).select().single()
|
})
|
||||||
|
|
||||||
if(error) {
|
|
||||||
toast.add({title: "Erstellen fehlgeschlagen", color: "rose"})
|
|
||||||
} else {
|
|
||||||
toast.add({title: "Erstellen erfolgreich"})
|
|
||||||
messageContent.value=""
|
|
||||||
setup()
|
|
||||||
if(profileStore.currentTenant !== 5) {
|
|
||||||
await useFunctions().useSendTelegramNotification(`Neue Nachricht im Ticket ${useRoute().params.id} von ${profileStore.activeProfile.fullName}: ${data.content}`)
|
|
||||||
} else if(profileStore.activeProfile.id !== itemInfo.value.created_by) {
|
|
||||||
|
|
||||||
let notification = {
|
|
||||||
tenant: itemInfo.value.tenant,
|
|
||||||
profile: itemInfo.value.created_by.id,
|
|
||||||
initiatingProfile: profileStore.activeProfile.id,
|
|
||||||
title: `Sie haben eine neue Nachricht von ${profileStore.activeProfile.fullName} im Ticket ${itemInfo.value.title}`,
|
|
||||||
link: `/support/${itemInfo.value.id}`,
|
|
||||||
message: data.content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(notification)
|
|
||||||
|
|
||||||
|
|
||||||
const {error} = await supabase.from("notifications").insert(notification)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const showAddEntryModal = ref(false)
|
|
||||||
const addEntryData = ref({})
|
|
||||||
const addEntry = async () => {
|
|
||||||
const {data,error} = await supabase.from("ticketmessages").insert({
|
|
||||||
profile: profileStore.activeProfile.id,
|
|
||||||
content: addEntryData.value.content,
|
|
||||||
ticket: itemInfo.value.id,
|
|
||||||
internal: addEntryData.value.internal,
|
|
||||||
type: addEntryData.value.type
|
|
||||||
}).select().single()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
toast.add({title: "Erstellen fehlgeschlagen", color: "rose"})
|
|
||||||
} else {
|
|
||||||
toast.add({title: "Erstellen erfolgreich"})
|
|
||||||
addEntryData.value = {}
|
|
||||||
setup()
|
|
||||||
showAddEntryModal.value = false
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeTicket = async () => {
|
|
||||||
const {data, error} = await supabase.from("tickets").update({status: "Geschlossen"}).eq("id",useRoute().params.id).single()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
addEntryData.value.type = "Notiz"
|
|
||||||
addEntryData.value.internal = false
|
|
||||||
addEntryData.value.content = `Ticket durch ${profileStore.activeProfile.fullName} geschlossen`
|
|
||||||
|
|
||||||
addEntry()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -120,7 +52,7 @@ const closeTicket = async () => {
|
|||||||
{{itemInfo.title}}
|
{{itemInfo.title}}
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
<UButton
|
<!-- <UButton
|
||||||
v-if="profileStore.currentTenant === 5"
|
v-if="profileStore.currentTenant === 5"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="closeTicket"
|
@click="closeTicket"
|
||||||
@@ -171,45 +103,14 @@ const closeTicket = async () => {
|
|||||||
</UButton>
|
</UButton>
|
||||||
</template>
|
</template>
|
||||||
</UCard>
|
</UCard>
|
||||||
</UModal>
|
</UModal>-->
|
||||||
</template>
|
</template>
|
||||||
</UDashboardNavbar>
|
</UDashboardNavbar>
|
||||||
<UDashboardPanelContent v-if="loaded">
|
<UDashboardPanelContent v-if="loaded">
|
||||||
<div
|
|
||||||
v-if="profileStore.currentTenant === 5"
|
|
||||||
v-for="item in itemInfo.ticketmessages"
|
|
||||||
class="mb-3 flex flex-row p-5"
|
|
||||||
:style="item.internal ? 'border: 1px solid red; border-radius: 15px' : item.profile.tenant === 5 ? 'border: 1px solid #69c350; border-radius: 15px' : 'border: 1px solid #fff; border-radius: 15px'"
|
|
||||||
>
|
|
||||||
<UAvatar :alt="item.profile.fullName" class="mr-3"/>
|
|
||||||
<div>
|
|
||||||
<p class="text-xl">{{item.type}} - {{item.profile.fullName}}</p>
|
|
||||||
<p v-html="item.content"></p>
|
|
||||||
<p class="mt-1 text-gray-600 dark:text-gray-400">{{dayjs(item.created_at).format("DD.MM.YYYY HH:mm")}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <UAlert
|
|
||||||
v-if="profileStore.currentTenant === 5"
|
|
||||||
v-for="item in itemInfo.ticketmessages"
|
|
||||||
:avatar="{ alt: item.profile.fullName}"
|
|
||||||
:title="`${item.type} - ${item.profile.fullName}`"
|
|
||||||
class="mb-3"
|
|
||||||
:color="item.internal ? 'rose' : item.profile.tenant === 5 ? 'primary' : 'white'"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
<template #description>
|
|
||||||
<span v-html="item.content"></span>
|
|
||||||
<p class="mt-1 text-gray-600 dark:text-gray-400">{{dayjs(item.created_at).format("DD.MM.YYYY HH:mm")}}</p>
|
|
||||||
</template>
|
|
||||||
</UAlert>-->
|
|
||||||
<UAlert
|
<UAlert
|
||||||
v-else
|
|
||||||
v-for="item in itemInfo.ticketmessages.filter(i => !i.internal)"
|
v-for="item in itemInfo.ticketmessages.filter(i => !i.internal)"
|
||||||
:avatar="{ alt: item.profile.fullName}"
|
:title="`${item.type}`"
|
||||||
:title="`${item.type} - ${item.profile.fullName}`"
|
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
:color="item.profile.tenant === 5 ? 'primary' : 'white'"
|
:color="item.profile.tenant === 5 ? 'primary' : 'white'"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -1,42 +1,30 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import {useFunctions} from "~/composables/useFunctions.js";
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const itemInfo = ref({})
|
const itemInfo = ref({})
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
|
||||||
const createTicket = async () => {
|
const createTicket = async () => {
|
||||||
|
|
||||||
const {data:ticketData,error:ticketError} = await supabase.from("tickets").insert({
|
const ticketRes = await useEntities("tickets").create({
|
||||||
title: itemInfo.value.title,
|
title: itemInfo.value.title,
|
||||||
created_by: profileStore.activeProfile.id,
|
})
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}).select().single()
|
|
||||||
|
|
||||||
if(ticketError) {
|
console.log(ticketRes)
|
||||||
console.error(ticketError)
|
|
||||||
} else {
|
const ticketMsgRes = await useEntities("ticketmessages").create({
|
||||||
console.log(ticketData)
|
ticket: ticketRes.id,
|
||||||
const {data:messageData,error:messageError} = await supabase.from("ticketmessages").insert({
|
created_by: auth.user.user_id,
|
||||||
ticket: ticketData.id,
|
|
||||||
profile: profileStore.activeProfile.id,
|
|
||||||
content: itemInfo.value.content,
|
content: itemInfo.value.content,
|
||||||
internal: false
|
internal: false
|
||||||
})
|
})
|
||||||
|
|
||||||
if(messageError) {
|
await router.push(`/support/${ticketRes.id}`)
|
||||||
console.log(messageError)
|
|
||||||
} else {
|
|
||||||
console.log(ticketData)
|
|
||||||
//useFunctions().useSendTelegramNotification(`Ticket von ${profileStore.activeProfile.fullName} erstellt : ${itemInfo.value.content}`)
|
|
||||||
router.push(`/support/${ticketData.id}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ const showClosedTickets = ref(false)
|
|||||||
const selectedTenant = ref(null)
|
const selectedTenant = ref(null)
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
if(profileStore.currentTenant === 5) {
|
/*if(profileStore.currentTenant === 5) {
|
||||||
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*), tenant(*)").order("created_at", {ascending: false})).data
|
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*), tenant(*)").order("created_at", {ascending: false})).data
|
||||||
} else {
|
} else {
|
||||||
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*)").eq("tenant",profileStore.currentTenant).order("created_at", {ascending: false})).data
|
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*)").eq("tenant",profileStore.currentTenant).order("created_at", {ascending: false})).data
|
||||||
@@ -19,7 +18,8 @@ const setup = async () => {
|
|||||||
|
|
||||||
if(profileStore.currentTenant === 5) {
|
if(profileStore.currentTenant === 5) {
|
||||||
tenants.value = (await supabase.from("tenants").select().order("id")).data
|
tenants.value = (await supabase.from("tenants").select().order("id")).data
|
||||||
}
|
}*/
|
||||||
|
tickets.value = await useEntities("tickets").select("*,created_by(*), ticketmessages(*)", "created_at", false)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,283 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'backspace': () => {
|
|
||||||
router.push("/trackingTrips")
|
|
||||||
},
|
|
||||||
'arrowleft': () => {
|
|
||||||
if(openTab.value > 0){
|
|
||||||
openTab.value -= 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowright': () => {
|
|
||||||
if(openTab.value < 3) {
|
|
||||||
openTab.value += 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const toast = useToast()
|
|
||||||
const id = ref(route.params.id ? route.params.id : null )
|
|
||||||
const openTab = ref(0)
|
|
||||||
|
|
||||||
|
|
||||||
//Working
|
|
||||||
const mode = ref(route.params.mode || "show")
|
|
||||||
const itemInfo = ref({
|
|
||||||
name: "",
|
|
||||||
type: "Geschäftlich",
|
|
||||||
driver: null,
|
|
||||||
active: true
|
|
||||||
})
|
|
||||||
const oldItemInfo = ref({})
|
|
||||||
|
|
||||||
const profiles = ref([])
|
|
||||||
const projects = ref([])
|
|
||||||
|
|
||||||
//Functions
|
|
||||||
const setupPage = async () => {
|
|
||||||
if(mode.value === "show" ){
|
|
||||||
itemInfo.value = await useSupabaseSelectSingle("trackingtrips",route.params.id,"*, trackingDevice(*, vehicle(*))")
|
|
||||||
} else if(mode.value === "edit") {
|
|
||||||
itemInfo.value = await useSupabaseSelectSingle("trackingtrips",route.params.id,"*")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
|
||||||
|
|
||||||
profiles.value = await useSupabaseSelect("profiles","*")
|
|
||||||
projects.value = await useSupabaseSelect("projects","*")
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelEditorCreate = () => {
|
|
||||||
if(itemInfo.value) {
|
|
||||||
router.push(`/trackingTrips/show/${itemInfo.value.id}`)
|
|
||||||
} else {
|
|
||||||
router.push(`/trackingTrips`)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getRowAmount = (row) => {
|
|
||||||
let amount = 0
|
|
||||||
|
|
||||||
row.accounts.forEach(account => {
|
|
||||||
amount += account.amountNet
|
|
||||||
amount += account.amountTax
|
|
||||||
})
|
|
||||||
|
|
||||||
return amount
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
|
|
||||||
const zoom = ref(6)
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar
|
|
||||||
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
|
|
||||||
>
|
|
||||||
<template #left>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-chevron-left"
|
|
||||||
variant="outline"
|
|
||||||
@click="router.push(`/trackingTrips`)"
|
|
||||||
>
|
|
||||||
Fahrten
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
<template #center>
|
|
||||||
<h1
|
|
||||||
v-if="itemInfo"
|
|
||||||
:class="['text-xl','font-medium' ]"
|
|
||||||
><UIcon name="i-heroicons-lock-closed" v-if="itemInfo.fixed"/><UIcon name="i-heroicons-lock-open" v-else/> {{itemInfo ? `Fahrt vom: ${dayjs(itemInfo.startTime).format("DD.MM.YY HH:mm")} über ${(itemInfo.distance/1000).toFixed(2)} km` : '' }} </h1>
|
|
||||||
</template>
|
|
||||||
<template #right>
|
|
||||||
<UButton
|
|
||||||
@click="cancelEditorCreate"
|
|
||||||
color="red"
|
|
||||||
variant="outline"
|
|
||||||
v-if="mode === 'edit' || mode === 'create'"
|
|
||||||
>
|
|
||||||
Abbrechen
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
v-if="mode === 'edit'"
|
|
||||||
variant="outline"
|
|
||||||
color="primary"
|
|
||||||
icon="i-mdi-content-save"
|
|
||||||
@click="dataStore.updateItem('trackingtrips',itemInfo, oldItemInfo)"
|
|
||||||
>
|
|
||||||
Entwurf
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
v-if="mode === 'edit'"
|
|
||||||
icon="i-mdi-content-save"
|
|
||||||
@click="dataStore.updateItem('trackingtrips',{...itemInfo, fixed:true}, oldItemInfo)"
|
|
||||||
>
|
|
||||||
Festschreiben
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
v-if="mode === 'show' && !itemInfo.fixed"
|
|
||||||
@click="router.push(`/trackingTrips/edit/${itemInfo.id}`)"
|
|
||||||
>
|
|
||||||
Bearbeiten
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<UTabs
|
|
||||||
:items="[{label: 'Informationen'}]"
|
|
||||||
v-if="mode === 'show' && itemInfo.id"
|
|
||||||
class="p-5"
|
|
||||||
v-model="openTab"
|
|
||||||
>
|
|
||||||
<template #item="{item}">
|
|
||||||
<div v-if="item.label === 'Informationen'" class="flex flex-col mt-5">
|
|
||||||
<div class="flex flex-row" style="height: 40vh">
|
|
||||||
<div class="w-1/2 mr-5">
|
|
||||||
<UCard>
|
|
||||||
<table class="w-full">
|
|
||||||
<tr>
|
|
||||||
<td>Festgeschrieben:</td>
|
|
||||||
<td>{{itemInfo.fixed ? "Ja" : "Nein"}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Fahrzeug:</td>
|
|
||||||
<td><nuxt-link :to="`/vehicles/show/${itemInfo.trackingDevice.vehicle.id}`">{{itemInfo.trackingDevice.vehicle.licensePlate}}</nuxt-link></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Typ:</td>
|
|
||||||
<td>{{itemInfo.type ? itemInfo.type : "-"}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Fahrer:</td>
|
|
||||||
<td>{{profiles.find(i => i.id === itemInfo.driver) ? profiles.find(i => i.id === itemInfo.driver).fullName : "-"}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Startzeit:</td>
|
|
||||||
<td>{{dayjs(itemInfo.startTime).format("DD.MM.YY HH:mm")}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Endzeit:</td>
|
|
||||||
<td>{{dayjs(itemInfo.endTime).format("DD.MM.YY HH:mm")}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Entfernung:</td>
|
|
||||||
<td>{{(itemInfo.distance/1000).toFixed(2)}} km</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Beschreibung:</td>
|
|
||||||
<td>{{itemInfo.description ? itemInfo.description : "-" }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
</UCard>
|
|
||||||
<UCard class="mt-5">
|
|
||||||
<Map :markers="[[itemInfo.startLatitude, itemInfo.startLongitude],[itemInfo.endLatitude, itemInfo.endLongitude]]"
|
|
||||||
:startMarker="[itemInfo.startLatitude, itemInfo.startLongitude]"
|
|
||||||
:endMarker="[itemInfo.endLatitude, itemInfo.endLongitude]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</UCard>
|
|
||||||
</div>
|
|
||||||
<div class="w-1/2">
|
|
||||||
<UCard>
|
|
||||||
<div style="height: 75vh">
|
|
||||||
<HistoryDisplay
|
|
||||||
type="trackingtrip"
|
|
||||||
v-if="itemInfo"
|
|
||||||
:element-id="itemInfo.id"
|
|
||||||
render-headline
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</UCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="h-40 mt-5">
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</UTabs>
|
|
||||||
<UForm
|
|
||||||
v-else-if="mode === 'edit' || mode === 'create'"
|
|
||||||
class="p-5"
|
|
||||||
>
|
|
||||||
<UFormGroup
|
|
||||||
label="Typ:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="itemInfo.type"
|
|
||||||
:options="['Privat','Geschäftlich','Arbeitsweg']"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Fahrer:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="itemInfo.driver"
|
|
||||||
:options="[{id: null, fullName: 'Kein Fahrer'},...profiles]"
|
|
||||||
option-attribute="fullName"
|
|
||||||
value-attribute="id"
|
|
||||||
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{profiles.find(profile => profile.id === itemInfo.driver) ? profiles.find(profile => profile.id === itemInfo.driver).fullName : 'Kein Fahrer ausgewählt'}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Projekt:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="itemInfo.project"
|
|
||||||
:options="projects"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Beschreibung:"
|
|
||||||
>
|
|
||||||
<UTextarea
|
|
||||||
v-model="itemInfo.description"
|
|
||||||
rows="6"
|
|
||||||
maxrows="6"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
</UForm>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
td {
|
|
||||||
border-bottom: 1px solid lightgrey;
|
|
||||||
vertical-align: top;
|
|
||||||
padding-bottom: 0.15em;
|
|
||||||
padding-top: 0.15em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
<template>
|
|
||||||
<UDashboardNavbar title="Fahrten" :badge="filteredRows.length">
|
|
||||||
<template #right>
|
|
||||||
<UInput
|
|
||||||
id="searchinput"
|
|
||||||
v-model="searchString"
|
|
||||||
icon="i-heroicons-funnel"
|
|
||||||
autocomplete="off"
|
|
||||||
placeholder="Suche..."
|
|
||||||
class="hidden lg:block"
|
|
||||||
@keydown.esc="$event.target.blur()"
|
|
||||||
>
|
|
||||||
<template #trailing>
|
|
||||||
<UKbd value="/" />
|
|
||||||
</template>
|
|
||||||
</UInput>
|
|
||||||
|
|
||||||
<!-- <UButton @click="router.push(`/vehicles/create`)">+ Fahrzeug</UButton>-->
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
|
|
||||||
<UDashboardToolbar>
|
|
||||||
<template #right>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="selectedColumns"
|
|
||||||
icon="i-heroicons-adjustments-horizontal-solid"
|
|
||||||
:options="templateColumns"
|
|
||||||
multiple
|
|
||||||
class="hidden lg:block"
|
|
||||||
by="key"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
Spalten
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</template>
|
|
||||||
</UDashboardToolbar>
|
|
||||||
<UTable
|
|
||||||
:rows="filteredRows"
|
|
||||||
:columns="columns"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
|
||||||
@select="(i) => router.push(`/trackingTrips/show/${i.id}`) "
|
|
||||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Fahrten anzuzeigen' }"
|
|
||||||
>
|
|
||||||
<template #fixed-data="{row}">
|
|
||||||
<UIcon name="i-heroicons-lock-closed" v-if="row.fixed"/>
|
|
||||||
<div v-else></div>
|
|
||||||
</template>
|
|
||||||
<template #licensePlate-data="{row}">
|
|
||||||
<span v-if="row === filteredRows[selectedItem]" class="font-bold text-primary-500">{{row.vehicle.licensePlate}}</span>
|
|
||||||
<span v-else>{{row.vehicle.licensePlate}}</span>
|
|
||||||
</template>
|
|
||||||
<template #startTime-data="{row}">
|
|
||||||
{{dayjs(row.startTime).format("DD.MM.YY HH:mm")}}
|
|
||||||
</template>
|
|
||||||
<template #endTime-data="{row}">
|
|
||||||
{{dayjs(row.endTime).format("DD.MM.YY HH:mm")}}
|
|
||||||
</template>
|
|
||||||
<template #distance-data="{row}">
|
|
||||||
{{row.distance > 0 ? (row.distance/1000).toFixed(2) : 0 }} km
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'/': () => {
|
|
||||||
//console.log(searchinput)
|
|
||||||
//searchinput.value.focus()
|
|
||||||
document.getElementById("searchinput").focus()
|
|
||||||
},
|
|
||||||
'Enter': {
|
|
||||||
usingInput: true,
|
|
||||||
handler: () => {
|
|
||||||
router.push(`/trackingTrips/show/${filteredRows.value[selectedItem.value].id}`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowdown': () => {
|
|
||||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
|
||||||
selectedItem.value += 1
|
|
||||||
} else {
|
|
||||||
selectedItem.value = 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowup': () => {
|
|
||||||
if(selectedItem.value === 0) {
|
|
||||||
selectedItem.value = filteredRows.value.length - 1
|
|
||||||
} else {
|
|
||||||
selectedItem.value -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const items = ref([])
|
|
||||||
const selectedItem = ref(0)
|
|
||||||
|
|
||||||
const setupPage = async () => {
|
|
||||||
items.value = await useSupabaseSelect("trackingtrips","*, trackingDevice(*), vehicle (*)","startTime",false)
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
|
|
||||||
const templateColumns = [
|
|
||||||
{
|
|
||||||
key: 'fixed',
|
|
||||||
label: ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'licensePlate',
|
|
||||||
label: "Kennzeichen:",
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "type",
|
|
||||||
label: "Typ:",
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "startTime",
|
|
||||||
label: "Startzeit:",
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "endTime",
|
|
||||||
label: "Endzeit:",
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "distance",
|
|
||||||
label: "Entfernung:",
|
|
||||||
sortable: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
const selectedColumns = ref(templateColumns)
|
|
||||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
|
||||||
|
|
||||||
const searchString = ref('')
|
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
|
||||||
return useSearch(searchString.value, items.value)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import {Preferences} from "@capacitor/preferences";
|
import {Preferences} from "@capacitor/preferences";
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
const api = $fetch.create({
|
const api = $fetch.create({
|
||||||
baseURL: /*"http://192.168.1.227:3100"*/ "https://backend.fedeo.io",
|
baseURL: config.public.apiBase,/*"http://192.168.1.227:3100" "https://backend.fedeo.io"*/
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
async onRequest({options}) {
|
async onRequest({options}) {
|
||||||
// Token aus Cookie holen
|
// Token aus Cookie holen
|
||||||
|
|||||||
145
stores/data.js
145
stores/data.js
@@ -3,7 +3,6 @@ import dayjs from "dayjs"
|
|||||||
//import {typeOf} from "uri-js/dist/esnext/util";
|
//import {typeOf} from "uri-js/dist/esnext/util";
|
||||||
import {useNumberRange} from "~/composables/useNumberRange.js";
|
import {useNumberRange} from "~/composables/useNumberRange.js";
|
||||||
|
|
||||||
//const supabase = createClient('https://uwppvcxflrcsibuzsbil.supabase.co','eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo')
|
|
||||||
|
|
||||||
import projecttype from "~/components/columnRenderings/projecttype.vue"
|
import projecttype from "~/components/columnRenderings/projecttype.vue"
|
||||||
import customer from "~/components/columnRenderings/customer.vue"
|
import customer from "~/components/columnRenderings/customer.vue"
|
||||||
@@ -49,7 +48,6 @@ import sepaDate from "~/components/columnRenderings/sepaDate.vue";
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export const useDataStore = defineStore('data', () => {
|
export const useDataStore = defineStore('data', () => {
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -1566,6 +1564,18 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
tickets: {
|
||||||
|
isArchivable: true,
|
||||||
|
label: "Tickets",
|
||||||
|
labelSingle: "Ticket",
|
||||||
|
|
||||||
|
},
|
||||||
|
ticketmessages: {
|
||||||
|
isArchivable: true,
|
||||||
|
label: "Nachrichten",
|
||||||
|
labelSingle: "Nachricht",
|
||||||
|
|
||||||
|
},
|
||||||
files: {
|
files: {
|
||||||
isArchivable: true,
|
isArchivable: true,
|
||||||
label: "Dateien",
|
label: "Dateien",
|
||||||
@@ -2593,136 +2603,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
async function createNewItem (dataType,data,noRedirect=false){
|
|
||||||
if(typeof(data) === 'object') {
|
|
||||||
data = {...data, tenant: profileStore.currentTenant}
|
|
||||||
} else if(typeof(data) === 'array') {
|
|
||||||
data.map(i => {
|
|
||||||
return {
|
|
||||||
...i,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(dataType)
|
|
||||||
if(dataTypes[dataType].numberRangeHolder) {
|
|
||||||
|
|
||||||
if(!data[dataTypes[dataType].numberRangeHolder]) {
|
|
||||||
data[dataTypes[dataType].numberRangeHolder] = await useFunctions().useNextNumber(dataType)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} else if(dataType === "createddocuments" && data.type !== "serialInvoices") {
|
|
||||||
/*if(data.state !== "Entwurf") {
|
|
||||||
console.log(data.type)
|
|
||||||
|
|
||||||
let type = ""
|
|
||||||
if(data.type === "advanceInvoices"){
|
|
||||||
type = "invoices"
|
|
||||||
} else {
|
|
||||||
type = data.type
|
|
||||||
}
|
|
||||||
|
|
||||||
const numberRange = useNumberRange(type)
|
|
||||||
data.documentNumber = await numberRange.useNextNumber()
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const {data:supabaseData,error:supabaseError} = await supabase
|
|
||||||
.from(dataType)
|
|
||||||
.insert(data)
|
|
||||||
.select()
|
|
||||||
|
|
||||||
if(supabaseError) {
|
|
||||||
console.log(supabaseError)
|
|
||||||
toast.add({title: "Es ist ein Fehler bei der Erstellung aufgetreten", color: "rose"})
|
|
||||||
} else if (supabaseData) {
|
|
||||||
console.log(supabaseData)
|
|
||||||
|
|
||||||
let returnData = supabaseData[0]
|
|
||||||
|
|
||||||
await generateHistoryItems(dataType, supabaseData[0])
|
|
||||||
|
|
||||||
/*if(!["statementallocations","absencerequests", "productcategories", "servicecategories", "projecttypes", "checks", "profiles","services", "inventoryitems", "inventoryitemgroups", "incominginvoices", "costcentres", "ownaccounts"].includes(dataType) ){
|
|
||||||
await eval( dataType + '.value.push(' + JSON.stringify(...supabaseData) + ')')
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
toast.add({title: `${dataTypes[dataType].labelSingle} hinzugefügt`})
|
|
||||||
if(dataTypes[dataType].redirect && !noRedirect) {
|
|
||||||
if(dataTypes[dataType].isStandardEntity) {
|
|
||||||
await router.push(dataTypes[dataType].redirectToList ? `/standardEntity/${dataType}` : `/standardEntity/${dataType}/show/${returnData.id}`)
|
|
||||||
} else {
|
|
||||||
await router.push(dataTypes[dataType].redirectToList ? `/${dataType}` : `/${dataType}/show/${returnData.id}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modal.close()
|
|
||||||
return supabaseData[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateItem (dataType, data, oldData = null, noRedirect = false) {
|
|
||||||
//console.log(dataType, data)
|
|
||||||
//Temporary Fix TODO: Remove and build Solution
|
|
||||||
data = JSON.parse(JSON.stringify(data))
|
|
||||||
delete data.users
|
|
||||||
|
|
||||||
if(oldData) {
|
|
||||||
oldData = JSON.parse(JSON.stringify(oldData))
|
|
||||||
delete oldData.users
|
|
||||||
}
|
|
||||||
|
|
||||||
const {tenants, ...newData} = data
|
|
||||||
|
|
||||||
/*if(dataType === "createddocuments" && data.type !== "serialInvoices") {
|
|
||||||
if(data.state !== "Entwurf") {
|
|
||||||
console.log(data.type)
|
|
||||||
|
|
||||||
let type = ""
|
|
||||||
if(data.type === "advanceInvoices"){
|
|
||||||
type = "invoices"
|
|
||||||
} else {
|
|
||||||
type = data.type
|
|
||||||
}
|
|
||||||
|
|
||||||
const numberRange = useNumberRange(type)
|
|
||||||
data.documentNumber = await numberRange.useNextNumber()
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await generateHistoryItems(dataType,data,oldData)
|
|
||||||
|
|
||||||
const {data:supabaseData,error: supabaseError} = await supabase
|
|
||||||
.from(dataType)
|
|
||||||
.update(newData)
|
|
||||||
.eq('id',newData.id)
|
|
||||||
.select()
|
|
||||||
|
|
||||||
if(supabaseError) {
|
|
||||||
console.log(supabaseError)
|
|
||||||
toast.add({title: `Fehler beim Speichern`, color: 'rose'})
|
|
||||||
} else if(supabaseData) {
|
|
||||||
//await eval(dataType + '.value[' + dataType + '.value.findIndex(i => i.id === ' + JSON.stringify(data.id) + ')] = ' + JSON.stringify(supabaseData[0]))
|
|
||||||
//if(dataType === 'profiles') await fetchProfiles()
|
|
||||||
toast.add({title: `${dataTypes[dataType].labelSingle} gespeichert`})
|
|
||||||
if(dataTypes[dataType].redirect && !noRedirect) {
|
|
||||||
if(dataTypes[dataType].isStandardEntity) {
|
|
||||||
await router.push(dataTypes[dataType].redirectToList ? `/standardEntity/${dataType}` : `/standardEntity/${dataType}/show/${data.id}`)
|
|
||||||
} else {
|
|
||||||
await router.push(dataTypes[dataType].redirectToList ? `/${dataType}` : `/${dataType}/show/${data.id}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modal.close()
|
|
||||||
return supabaseData[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -2732,7 +2613,5 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
return {
|
return {
|
||||||
dataTypes,
|
dataTypes,
|
||||||
documentTypesForCreation,
|
documentTypesForCreation,
|
||||||
createNewItem,
|
|
||||||
updateItem,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user