Merge branch 'refs/heads/dev' into beta

This commit is contained in:
2025-03-16 19:14:15 +01:00
6 changed files with 288 additions and 47 deletions

View File

@@ -0,0 +1,106 @@
<script setup>
const dataStore = useDataStore()
const modal = useModal()
const router = useRouter()
const props = defineProps({
id: {
type: String,
required: true
},
})
const emit = defineEmits(["updateNeeded","returnData"])
const documentTypeToUse = ref("invoices")
const optionsToImport = ref({
taxType: true,
customer: true,
letterhead: true,
contact: true,
deliveryDateType: true,
deliveryDate: true,
deliveryDateEnd: true,
documentDate: false,
paymentDays: true,
customSurchargePercentage: true,
contactPerson: true,
plant: true,
project:true,
title: true,
description: true,
startText: true,
rows: true,
endText: true,
})
const mappings = ref({
customer: "Kunde",
taxType: "Steuertyp",
letterhead: "Briefpapier",
contact: "Ansprechpartner",
deliveryDateType: "Lieferdatumsart",
deliveryDate: "Lieferdatum / Lieferzeitraum Start",
deliveryDateEnd: "Lieferzeitraum Ende",
documentDate: "Belegdatum",
paymentDays: "Zahlungsziel in Tagen",
customSurchargePercentage: "Individueller Aufschlag",
contactPerson: "Ansprechpartner Mitarbeiter",
plant: "Objekt",
project: "Projekt",
title: "Titel",
description: "Beschreibung",
startText: "Einleitung",
rows: "Positionen",
endText: "Nachbemerkung",
})
const startImport = () => {
router.push(`/createDocument/edit/?linkedDocument=${props.id}&type=${documentTypeToUse.value}&optionsToImport=${encodeURIComponent(JSON.stringify(optionsToImport.value))}`)
modal.close()
}
</script>
<template>
<UModal :fullscreen="false">
<UCard>
<template #header>
Erstelltes Dokument Kopieren
</template>
<UFormGroup
label="Dokumententyp:"
class="mb-3"
>
<USelectMenu
:options="Object.keys(dataStore.documentTypesForCreation).map(key => { return { ...dataStore.documentTypesForCreation[key], key}})"
value-attribute="key"
option-attribute="labelSingle"
v-model="documentTypeToUse"
>
</USelectMenu>
</UFormGroup>
<UCheckbox
v-for="key in Object.keys(optionsToImport)"
v-model="optionsToImport[key]"
:label="mappings[key]"/>
<template #footer>
<UButton
@click="startImport"
>
Kopieren
</UButton>
</template>
</UCard>
</UModal>
</template>
<style scoped>
</style>

View File

@@ -1,10 +1,13 @@
import {useDataStore} from "~/stores/data.js";
export const useSupabaseSelect = async (relation,select = '*', sortColumn = null, ascending = true) => {
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
@@ -19,6 +22,10 @@ export const useSupabaseSelect = async (relation,select = '*', sortColumn = null
.eq("tenant", profileStore.currentTenant)).data
}
if(dataType && dataType.isArchivable && !noArchivedFiltering) {
data = data.filter(i => !i.archived)
}
return data
}

View File

@@ -3,7 +3,6 @@ import dayjs from "dayjs"
import Handlebars from "handlebars"
import { v4 as uuidv4 } from 'uuid';
import {useFunctions} from "~/composables/useFunctions.js";
import StandardEntityModal from "~/components/StandardEntityModal.vue";
import EntityModalButtons from "~/components/EntityModalButtons.vue";
const dataStore = useDataStore()
@@ -102,7 +101,12 @@ const setupPage = async () => {
if(servicecategories.value.length > 0) selectedServicecategorie.value = servicecategories.value[0].id
if(route.params) {
if(route.params.id) itemInfo.value = await useSupabaseSelectSingle("createddocuments", route.params.id)
if(route.params.id) {
itemInfo.value = await useSupabaseSelectSingle("createddocuments", route.params.id)
}
if(itemInfo.value.project) checkForOpenAdvanceInvoices()
@@ -201,19 +205,60 @@ const setupPage = async () => {
if(route.query.linkedDocument) {
itemInfo.value.linkedDocument = route.query.linkedDocument
let linkedDocument = await useSupabaseSelectSingle("createddocuments",itemInfo.value.linkedDocument)
itemInfo.value.rows = linkedDocument.rows
if(route.query.optionsToImport) {
//Import only true
let optionsToImport = JSON.parse(route.query.optionsToImport)
console.log(optionsToImport)
console.log(linkedDocument)
if(optionsToImport.taxType) itemInfo.value.taxType = linkedDocument.taxType
if(optionsToImport.customer) itemInfo.value.customer = linkedDocument.customer
if(optionsToImport.letterhead) itemInfo.value.letterhead = linkedDocument.letterhead
if(optionsToImport.contact) itemInfo.value.contact = linkedDocument.contact
if(optionsToImport.deliveryDateType) itemInfo.value.deliveryDateType = linkedDocument.deliveryDateType
if(optionsToImport.deliveryDate) itemInfo.value.deliveryDate = linkedDocument.deliveryDate
if(optionsToImport.deliveryDateEnd) itemInfo.value.deliveryDateEnd = linkedDocument.deliveryDateEnd
if(optionsToImport.documentDate) itemInfo.value.documentDate = linkedDocument.documentDate
if(optionsToImport.paymentDays) itemInfo.value.paymentDays = linkedDocument.paymentDays
if(optionsToImport.customSurchargePercentage) itemInfo.value.customSurchargePercentage = linkedDocument.customSurchargePercentage
if(optionsToImport.contactPerson) itemInfo.value.contactPerson = linkedDocument.contactPerson
if(optionsToImport.plant) itemInfo.value.plant = linkedDocument.plant
if(optionsToImport.project) itemInfo.value.project = linkedDocument.project
if(optionsToImport.title) itemInfo.value.title = linkedDocument.title
if(optionsToImport.description) itemInfo.value.description = linkedDocument.description
if(optionsToImport.startText) itemInfo.value.startText = linkedDocument.startText
if(optionsToImport.rows) itemInfo.value.rows = linkedDocument.rows
if(optionsToImport.endText) itemInfo.value.endText = linkedDocument.endText
} else {
// Import all
itemInfo.value.taxType = linkedDocument.taxType
itemInfo.value.customer = linkedDocument.customer
itemInfo.value.project = linkedDocument.project
itemInfo.value.letterhead = linkedDocument.letterhead
itemInfo.value.contact = linkedDocument.contact
itemInfo.value.description = linkedDocument.description
itemInfo.value.deliveryDate = linkedDocument.deliveryDate
itemInfo.value.deliveryDateType = linkedDocument.deliveryDateType
itemInfo.value.deliveryDate = linkedDocument.deliveryDate
itemInfo.value.deliveryDateEnd = linkedDocument.deliveryDateEnd
itemInfo.value.documentDate = linkedDocument.documentDate
itemInfo.value.paymentDays = linkedDocument.paymentDays
itemInfo.value.customSurchargePercentage = linkedDocument.customSurchargePercentage
itemInfo.value.contactPerson = linkedDocument.contactPerson
itemInfo.value.plant = linkedDocument.plant
itemInfo.value.project = linkedDocument.project
itemInfo.value.title = linkedDocument.title
itemInfo.value.description = linkedDocument.description
itemInfo.value.startText = linkedDocument.startText
itemInfo.value.rows = linkedDocument.rows
itemInfo.value.endText = linkedDocument.endText
}
setCustomerData()
setCustomerData(null,true)
if(route.query.loadMode === "storno") {
itemInfo.value.rows.forEach(row => {
@@ -329,7 +374,7 @@ const setTaxType = () => {
}
}
const setCustomerData = async (customerId) => {
const setCustomerData = async (customerId, loadOnlyAdress = false) => {
if(customerId){
itemInfo.value.customer = customerId
@@ -347,11 +392,14 @@ const setCustomerData = async (customerId) => {
itemInfo.value.address.city = customer.infoData.city
itemInfo.value.address.special = customer.infoData.special
if(customer.customPaymentDays) itemInfo.value.paymentDays = customer.customPaymentDays
if(!loadOnlyAdress && customer.customPaymentDays) itemInfo.value.paymentDays = customer.customPaymentDays
if(customer.customSurchargePercentage) itemInfo.value.customSurchargePercentage = customer.customSurchargePercentage
if(!loadOnlyAdress && customer.customSurchargePercentage) {
itemInfo.value.customSurchargePercentage = customer.customSurchargePercentage
updateCustomSurcharge()
}
if(contacts.value.filter(i => i.customer === itemInfo.value.customer).length === 1) {
if(!loadOnlyAdress && contacts.value.filter(i => i.customer === itemInfo.value.customer).length === 1) {
itemInfo.value.contact = contacts.value.filter(i => i.customer === itemInfo.value.customer)[0].id
}
@@ -432,6 +480,7 @@ const addPosition = (mode) => {
text: "",
quantity: 1,
unit: 1,
inputPrice: 0,
price: 0,
taxPercent: taxPercentage,
discountPercent: 0
@@ -444,6 +493,7 @@ const addPosition = (mode) => {
id: uuidv4(),
mode: "normal",
quantity: 1,
inputPrice: 0,
price: 0,
taxPercent: taxPercentage,
discountPercent: 0,
@@ -454,6 +504,7 @@ const addPosition = (mode) => {
id: uuidv4(),
mode: "service",
quantity: 1,
inputPrice: 0,
price: 0,
taxPercent: taxPercentage,
discountPercent: 0,
@@ -1075,7 +1126,7 @@ const saveDocument = async (state,resetup = false) => {
} else {
const data = await dataStore.createNewItem("createddocuments", createData)
console.log(data)
await router.push(`/createDocument/edit/${data[0].id}`)
await router.push(`/createDocument/edit/${data.id}`)
}
if(resetup) await setupPage()
@@ -1143,39 +1194,55 @@ const getTextTemplateByType = (type, pos) => {
const updateCustomSurcharge = () => {
itemInfo.value.rows.forEach(row => {
if(!["pagebreak","title","free","text"].includes(row.mode)) {
setRowData(row)
if(!["pagebreak","title","text"].includes(row.mode)) {
//setRowData(row)
row.price = Number((row.inputPrice * (1 + itemInfo.value.customSurchargePercentage /100)).toFixed(2))
}
})
}
const setRowData = (row) => {
console.log(row)
const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {}) => {
console.log("Set Row Data")
if(service && service.id) {
row.service = service.id
services.value = await useSupabaseSelect("services","*")
}
if(product && product.id) {
row.product = product.id
product.value = await useSupabaseSelect("products","*")
}
if(row.service) {
row.unit = services.value.find(i => i.id === row.service).unit
row.price = (services.value.find(i => i.id === row.service).sellingPriceComposed.total || services.value.find(i => i.id === row.service).sellingPrice) * (1 + itemInfo.value.customSurchargePercentage /100)
row.description = services.value.find(i => i.id === row.service).description
row.unit = service.unit ? service.unit : services.value.find(i => i.id === row.service).unit
row.inputPrice = ((service.sellingPriceComposed.total || service.sellingPrice) ? (service.sellingPriceComposed.total || service.sellingPrice) : (services.value.find(i => i.id === row.service).sellingPriceComposed.total || services.value.find(i => i.id === row.service).sellingPrice))
row.description = service.description ? service.description : (services.value.find(i => i.id === row.service) ? services.value.find(i => i.id === row.service).description : "")
if(['13b UStG','19 UStG'].includes(itemInfo.value.taxType)) {
row.taxPercent = 0
} else {
row.taxPercent = services.value.find(i => i.id === row.service).taxPercentage
row.taxPercent = service.taxPercentage ? service.taxPercentage : services.value.find(i => i.id === row.service).taxPercentage
}
}
if(row.product) {
row.unit = products.value.find(i => i.id === row.product).unit
row.price = products.value.find(i => i.id === row.product).sellingPrice * (1 + itemInfo.value.customSurchargePercentage /100)
row.description = products.value.find(i => i.id === row.product).description
console.log("Product Detected")
row.unit = product.unit ? product.unit : products.value.find(i => i.id === row.product).unit
row.inputPrice = (product.sellingPrice ? product.sellingPrice : products.value.find(i => i.id === row.product).sellingPrice)
//row.price = Number((row.originalPrice * (1 + itemInfo.value.customSurchargePercentage /100)).toFixed(2))
row.description = product.description ? product.description : (products.value.find(i => i.id === row.product) ? products.value.find(i => i.id === row.product).description : "")
if(['13b UStG','19 UStG'].includes(itemInfo.value.taxType)) {
row.taxPercent = 0
} else {
row.taxPercent = products.value.find(i => i.id === row.product).taxPercentage
row.taxPercent = product.taxPercentage ? product.taxPercentage : products.value.find(i => i.id === row.product).taxPercentage
}
}
updateCustomSurcharge()
}
@@ -1873,8 +1940,8 @@ const setRowData = (row) => {
<th class="pl-2">Menge</th>
<th class="pl-2">Einheit</th>
<th class="pl-2" v-if="itemInfo.type !== 'deliveryNotes'">Preis</th>
<th class="pl-2" v-if="itemInfo.type !== 'deliveryNotes'">Steuer</th>
<th class="pl-2" v-if="itemInfo.type !== 'deliveryNotes'">Rabatt</th>
<!-- <th class="pl-2" v-if="itemInfo.type !== 'deliveryNotes'">Steuer</th>
<th class="pl-2" v-if="itemInfo.type !== 'deliveryNotes'">Rabatt</th>-->
<th class="pl-2"> </th>
<th class="pl-2" v-if="itemInfo.type !== 'deliveryNotes'">Gesamt</th>
</tr>
@@ -1945,10 +2012,15 @@ const setRowData = (row) => {
@change="setRowData(row)"
>
<template #label>
<span class="truncate">{{row.product ? products.find(i => i.id === row.product).name : "Kein Produkt ausgewählt" }}</span>
<span class="truncate">{{products.find(i => i.id === row.product) ? products.find(i => i.id === row.product).name : "Kein Produkt ausgewählt" }}</span>
</template>
</USelectMenu>
<UButton
<EntityModalButtons
type="products"
:id="row.product"
@return-data="(data) => setRowData(row,null,data)"
/>
<!-- <UButton
icon="i-heroicons-magnifying-glass"
@click="showProductSelectionModal = true"
/>
@@ -1987,7 +2059,7 @@ const setRowData = (row) => {
</UTable>
</UCard>
</UModal>
</UModal>-->
</InputGroup>
</td>
@@ -2012,7 +2084,12 @@ const setRowData = (row) => {
<span class="truncate">{{services.find(i => i.id === row.service) ? services.find(i => i.id === row.service).name : "Keine Leistung ausgewählt" }}</span>
</template>
</USelectMenu>
<UButton
<EntityModalButtons
type="services"
:id="row.service"
@return-data="(data) => setRowData(row,data,null)"
/>
<!-- <UButton
icon="i-heroicons-magnifying-glass"
@click="showServiceSelectionModal = true"
/>
@@ -2049,7 +2126,7 @@ const setRowData = (row) => {
</UTable>
</UCard>
</UModal>
</UModal>-->
</InputGroup>
</td>
<td
@@ -2083,16 +2160,32 @@ const setRowData = (row) => {
v-if="!['pagebreak','title','text'].includes(row.mode) && itemInfo.type !== 'deliveryNotes'"
>
<UInput
v-model="row.price"
v-model="row.inputPrice"
type="number"
step="0.001"
@change="updateCustomSurcharge"
>
<template #trailing>
<template #leading>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
<template #trailing>
<span
v-if="row.price > row.inputPrice"
class="text-primary text-xs">
{{useCurrency(row.price)}}
</span>
<span
v-else-if="row.price < row.inputPrice"
class="text-rose-600 text-xs">
{{useCurrency(row.price)}}
</span>
<span
v-else
class="text-gray-500 dark:text-gray-400 text-xs"> </span>
</template>
</UInput>
</td>
<td
<!-- <td
class="w-40"
v-if="!['pagebreak','title','text'].includes(row.mode)&& itemInfo.type !== 'deliveryNotes'"
@@ -2110,8 +2203,8 @@ const setRowData = (row) => {
</template>
</USelectMenu>
</td>
<td
</td>-->
<!-- <td
class="w-40"
v-if="!['pagebreak','title','text'].includes(row.mode)&& itemInfo.type !== 'deliveryNotes'"
>
@@ -2125,7 +2218,7 @@ const setRowData = (row) => {
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
</template>
</UInput>
</td>
</td>-->
<td
class="w-40"
v-if="!['pagebreak','title','text'].includes(row.mode)"

View File

@@ -1,4 +1,6 @@
<script setup>
import CopyCreatedDocumentModal from "~/components/copyCreatedDocumentModal.vue";
definePageMeta({
middleware: "auth"
})
@@ -10,7 +12,7 @@ defineShortcuts({
})
const supabase = useSupabaseClient()
const modal = useModal()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
@@ -68,7 +70,16 @@ const openEmail = () => {
:to="dataStore.documents.find(i => i.createdDocument === itemInfo.id) ? dataStore.documents.find(i => i.createdDocument === itemInfo.id).url : ''"
target="_blank"
>In neuen Tab anzeigen</UButton>-->
<UTooltip
<UButton
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="modal.open(CopyCreatedDocumentModal, {
id: itemInfo.id,
})"
variant="outline"
>
Kopieren
</UButton>
<!-- <UTooltip
text="Kopieren in Angebot"
>
<UButton
@@ -122,7 +133,7 @@ const openEmail = () => {
>
Rechnung
</UButton>
</UTooltip>
</UTooltip>-->
<UButton
@click="openEmail"
icon="i-heroicons-envelope"

View File

@@ -31,7 +31,7 @@ const setupPage = async () => {
console.log(item.value)
} else if(mode.value === "list") {
//Load Data for List
items.value = await useSupabaseSelect(type, dataType.supabaseSelectWithInformation || "*", dataType.supabaseSortColumn,dataType.supabaseSortAscending || false)
items.value = await useSupabaseSelect(type, dataType.supabaseSelectWithInformation || "*", dataType.supabaseSortColumn,dataType.supabaseSortAscending || false, true)
}
loaded.value = true

View File

@@ -55,6 +55,7 @@ export const useDataStore = defineStore('data', () => {
const dataTypes = {
tasks: {
isArchivable: true,
label: "Aufgaben",
labelSingle: "Aufgabe",
isStandardEntity: true,
@@ -146,6 +147,7 @@ export const useDataStore = defineStore('data', () => {
showTabs: [{label: 'Informationen'}]
},
customers: {
isArchivable: true,
label: "Kunden",
labelSingle: "Kunde",
isStandardEntity: true,
@@ -310,6 +312,7 @@ export const useDataStore = defineStore('data', () => {
showTabs: [{label: 'Informationen'},{label: 'Ansprechpartner'},{label: 'Dateien'},{label: 'Ausgangsbelege'},{label: 'Projekte'},{label: 'Objekte'},{label: 'Verträge'}]
},
contacts: {
isArchivable: true,
label: "Kontakte",
labelSingle: "Kontakt",
isStandardEntity: true,
@@ -414,6 +417,7 @@ export const useDataStore = defineStore('data', () => {
]
},
contracts: {
isArchivable: true,
label: "Verträge",
labelSingle: "Vertrag",
isStandardEntity: true,
@@ -561,6 +565,7 @@ export const useDataStore = defineStore('data', () => {
showTabs: [{label: 'Informationen'},{label: 'Dateien'}]
},
absencerequests: {
isArchivable: true,
label: "Abwesenheiten",
labelSingle: "Abwesenheit",
isStandardEntity: true,
@@ -650,6 +655,7 @@ export const useDataStore = defineStore('data', () => {
showTabs: [{label: 'Informationen'}]
},
plants: {
isArchivable: true,
label: "Objekte",
labelSingle: "Objekt",
isStandardEntity: true,
@@ -702,6 +708,7 @@ export const useDataStore = defineStore('data', () => {
}]
},
products: {
isArchivable: true,
label: "Artikel",
labelSingle: "Artikel",
isStandardEntity: true,
@@ -793,6 +800,7 @@ export const useDataStore = defineStore('data', () => {
]
},
projects: {
isArchivable: true,
label: "Projekte",
labelSingle: "Projekt",
isStandardEntity: true,
@@ -923,6 +931,7 @@ export const useDataStore = defineStore('data', () => {
}*/]
},
vehicles: {
isArchivable: true,
label: "Fahrzeuge",
labelSingle: "Fahrzeug",
isStandardEntity: true,
@@ -1020,6 +1029,7 @@ export const useDataStore = defineStore('data', () => {
]
},
vendors: {
isArchivable: true,
label: "Lieferanten",
labelSingle: "Lieferant",
isStandardEntity: true,
@@ -1158,6 +1168,7 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Nachricht"
},
spaces: {
isArchivable: true,
label: "Lagerplätze",
labelSingle: "Lagerplatz",
isStandardEntity: true,
@@ -1288,6 +1299,7 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Benutzer"
},
createddocuments: {
isArchivable: true,
label: "Dokumente",
labelSingle: "Dokument"
},
@@ -1297,6 +1309,7 @@ export const useDataStore = defineStore('data', () => {
redirect:true
},
inventoryitems: {
isArchivable: true,
label: "Inventarartikel",
labelSingle: "Inventarartikel",
isStandardEntity: true,
@@ -1436,6 +1449,7 @@ export const useDataStore = defineStore('data', () => {
]
},
inventoryitemgroups: {
isArchivable: true,
label: "Inventarartikelgruppen",
labelSingle: "Inventarartikelgruppe",
isStandardEntity: true,
@@ -1500,6 +1514,7 @@ export const useDataStore = defineStore('data', () => {
]
},
services: {
isArchivable: true,
label: "Leistungen",
labelSingle: "Leistung",
isStandardEntity: true,
@@ -1599,6 +1614,7 @@ export const useDataStore = defineStore('data', () => {
label: "Verkaufspreis Gesamt pro Einheit",
inputType: "number",
inputTrailing: "EUR",
required: true,
/*disabledFunction: function (item) {
return item.sellingPriceComposed.worker || item.sellingPriceComposed.material
},*/
@@ -1626,6 +1642,7 @@ export const useDataStore = defineStore('data', () => {
]
},
events: {
isArchivable: true,
label: "Termine",
labelSingle: "Termin",
isStandardEntity: true,
@@ -1750,16 +1767,19 @@ export const useDataStore = defineStore('data', () => {
historyItemHolder: "profile"
},
workingtimes: {
isArchivable: true,
label: "Anwesenheiten",
labelSingle: "Anwesenheit",
redirect: true,
redirectToList: true
},
texttemplates: {
isArchivable: true,
label: "Textvorlagen",
labelSingle: "Textvorlage"
},
bankstatements: {
isArchivable: true,
label: "Kontobewegungen",
labelSingle: "Kontobewegung",
historyItemHolder: "bankStatement",
@@ -1769,6 +1789,7 @@ export const useDataStore = defineStore('data', () => {
labelSingle: "Bankzuweisung"
},
productcategories: {
isArchivable: true,
label: "Artikelkategorien",
labelSingle: "Artikelkategorie",
isStandardEntity: true,
@@ -1806,6 +1827,7 @@ export const useDataStore = defineStore('data', () => {
]
},
servicecategories: {
isArchivable: true,
label: "Leistungskategorien",
labelSingle: "Leistungskategorie",
isStandardEntity: true,
@@ -1849,12 +1871,14 @@ export const useDataStore = defineStore('data', () => {
historyItemHolder: "trackingtrip",
},
projecttypes: {
isArchivable: true,
label: "Projekttypen",
labelSingle: "Projekttyp",
redirect: true,
historyItemHolder: "projecttype"
},
checks: {
isArchivable: true,
label: "Überprüfungen",
labelSingle: "Überprüfung",
isStandardEntity: true,