Added Frontend
This commit is contained in:
32
frontend/composables/useCapacitor.js
Normal file
32
frontend/composables/useCapacitor.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import {Capacitor} from "@capacitor/core";
|
||||
import {Device} from "@capacitor/device";
|
||||
import {Network} from "@capacitor/network";
|
||||
|
||||
const override = false
|
||||
|
||||
export const useCapacitor = () => {
|
||||
const getPlatform = () => {
|
||||
return Capacitor.getPlatform()
|
||||
}
|
||||
|
||||
const getDeviceInfo = async () => {
|
||||
return await Device.getInfo()
|
||||
}
|
||||
|
||||
const getIsPhone = async () => {
|
||||
let deviceInfo = await useCapacitor().getDeviceInfo()
|
||||
|
||||
return override || deviceInfo.model.toLowerCase().includes('iphone')
|
||||
}
|
||||
|
||||
const getIsNative = () => {
|
||||
return override || Capacitor.isNativePlatform()
|
||||
}
|
||||
|
||||
const getNetworkStatus = async () => {
|
||||
return await Network.getStatus()
|
||||
}
|
||||
|
||||
|
||||
return {getPlatform, getDeviceInfo, getNetworkStatus, getIsPhone, getIsNative}
|
||||
}
|
||||
3
frontend/composables/useCurrency.js
Normal file
3
frontend/composables/useCurrency.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export const useCurrency = (value,currencyString = " €") => {
|
||||
return `${Number(value).toFixed(2).replace(".",",")} ${currencyString}`.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||
}
|
||||
34
frontend/composables/useDashboard.ts
Normal file
34
frontend/composables/useDashboard.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
|
||||
const _useDashboard = () => {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const isHelpSlideoverOpen = ref(false)
|
||||
const isNotificationsSlideoverOpen = ref(false)
|
||||
|
||||
defineShortcuts({
|
||||
'g-h': () => router.push('/'),
|
||||
'g-a': () => router.push('/standardEntity/tasks'),
|
||||
'g-d': () => router.push('/files'),
|
||||
'g-k': () => router.push('/standardEntity/customers'),
|
||||
'g-l': () => router.push('/standardEntity/vendors'),
|
||||
'g-s': () => router.push('/settings'),
|
||||
'g-p': () => router.push('/standardEntity/projects'),
|
||||
'g-v': () => router.push('/standardEntity/contracts'),
|
||||
'g-o': () => router.push('/standardEntity/plants'),
|
||||
'?': () => isHelpSlideoverOpen.value = !isHelpSlideoverOpen.value,
|
||||
n: () => isNotificationsSlideoverOpen.value = !isNotificationsSlideoverOpen.value
|
||||
})
|
||||
|
||||
watch(() => route.fullPath, () => {
|
||||
isHelpSlideoverOpen.value = false
|
||||
isNotificationsSlideoverOpen.value = false
|
||||
})
|
||||
|
||||
return {
|
||||
isHelpSlideoverOpen,
|
||||
isNotificationsSlideoverOpen
|
||||
}
|
||||
}
|
||||
|
||||
export const useDashboard = createSharedComposable(_useDashboard)
|
||||
220
frontend/composables/useEntities.ts
Normal file
220
frontend/composables/useEntities.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
|
||||
import { useDataStore } from "~/stores/data"
|
||||
|
||||
export const useEntities = (
|
||||
relation: string,
|
||||
) => {
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
|
||||
const dataType = dataStore.dataTypes[relation]
|
||||
|
||||
const select = async (
|
||||
select: string = "*",
|
||||
sortColumn: string | null = null,
|
||||
ascending: boolean = false,
|
||||
noArchivedFiltering: boolean = false
|
||||
) => {
|
||||
|
||||
const res = await useNuxtApp().$api(`/api/resource/${relation}`, {
|
||||
method: "GET",
|
||||
params: {
|
||||
select,
|
||||
sort: sortColumn || undefined,
|
||||
asc: ascending
|
||||
}
|
||||
})
|
||||
|
||||
let data = res || []
|
||||
|
||||
if (dataType && dataType.isArchivable && !noArchivedFiltering) {
|
||||
data = data.filter((i: any) => !i.archived)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
const selectPaginated = async (options: {
|
||||
select?: string
|
||||
filters?: Record<string, any>
|
||||
sort?: { field: string; direction?: 'asc' | 'desc' }[]
|
||||
page?: number
|
||||
limit?: number
|
||||
includeArchived?: boolean
|
||||
noPagination?: boolean,
|
||||
search?: string,
|
||||
searchColumns?: string[],
|
||||
distinctColumns?: string[],
|
||||
}): Promise<{ data: any[]; meta: any }> => {
|
||||
const {
|
||||
select = '*',
|
||||
filters = {},
|
||||
sort = [],
|
||||
page = 1,
|
||||
limit = 25,
|
||||
includeArchived = false,
|
||||
noPagination = false,
|
||||
search,
|
||||
searchColumns = [],
|
||||
distinctColumns = [],
|
||||
} = options
|
||||
|
||||
const queryParams: Record<string, any> = {
|
||||
select,
|
||||
page,
|
||||
limit,
|
||||
noPagination: noPagination ? 'true' : undefined
|
||||
}
|
||||
|
||||
// --- 🔍 Search-Parameter (optional) ---
|
||||
if (search && search.trim().length > 0) {
|
||||
queryParams.search = search.trim()
|
||||
}
|
||||
if (searchColumns.length > 0) queryParams.searchColumns = searchColumns.join(',')
|
||||
if (distinctColumns.length > 0) queryParams.distinctColumns = distinctColumns.join(',')
|
||||
|
||||
|
||||
// --- Sortierung ---
|
||||
if (sort.length > 0) {
|
||||
queryParams.sort = sort
|
||||
.map(s => `${s.field}:${s.direction || 'asc'}`)
|
||||
.join(',')
|
||||
}
|
||||
|
||||
// --- Filter ---
|
||||
for (const [key, value] of Object.entries(filters)) {
|
||||
if (Array.isArray(value)) {
|
||||
queryParams[`filter[${key}]`] = value.join(',')
|
||||
} else {
|
||||
queryParams[`filter[${key}]`] = value
|
||||
}
|
||||
}
|
||||
|
||||
const response = await useNuxtApp().$api(`/api/resource/${relation}/paginated`, {
|
||||
method: 'GET',
|
||||
params: queryParams
|
||||
})
|
||||
|
||||
if (!response) {
|
||||
return { data: [], meta: null }
|
||||
}
|
||||
|
||||
let data = response.data || []
|
||||
const meta = response.queryConfig || {}
|
||||
|
||||
// --- Optional: Archivierte ausblenden ---
|
||||
if (!includeArchived) {
|
||||
data = data.filter((i: any) => !i.archived)
|
||||
}
|
||||
|
||||
return { data, meta }
|
||||
}
|
||||
|
||||
const selectSpecial = async (
|
||||
select: string = "*",
|
||||
sortColumn: string | null = null,
|
||||
ascending: boolean = false,
|
||||
) => {
|
||||
|
||||
const res = await useNuxtApp().$api(`/api/resource-special/${relation}`, {
|
||||
method: "GET",
|
||||
params: {
|
||||
select,
|
||||
sort: sortColumn || undefined,
|
||||
asc: ascending
|
||||
}
|
||||
})
|
||||
|
||||
return res || []
|
||||
}
|
||||
|
||||
const selectSingle = async (
|
||||
idToEq: string | number,
|
||||
select: string = "*",
|
||||
withInformation: boolean = true
|
||||
) => {
|
||||
if (!idToEq) return null
|
||||
|
||||
const res = await useNuxtApp().$api(withInformation ? `/api/resource/${relation}/${idToEq}` : `/api/resource/${relation}/${idToEq}/true`, {
|
||||
method: "GET",
|
||||
params: { select }
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const create = async (
|
||||
payload: Record<string, any>,
|
||||
noRedirect: boolean = false
|
||||
) => {
|
||||
|
||||
|
||||
const res = await useNuxtApp().$api(`/api/resource/${relation}`, {
|
||||
method: "POST",
|
||||
body: payload
|
||||
})
|
||||
|
||||
toast.add({title: `${dataType.labelSingle} hinzugefügt`})
|
||||
if(dataType.redirect && !noRedirect) {
|
||||
if(dataType.isStandardEntity) {
|
||||
await router.push(dataType.redirectToList ? `/standardEntity/${relation}` : `/standardEntity/${relation}/show/${res.id}`)
|
||||
} else {
|
||||
await router.push(dataType.redirectToList ? `/${relation}` : `/${relation}/show/${res.id}`)
|
||||
}
|
||||
}
|
||||
//modal.close() TODO: Modal Close wenn in Modal
|
||||
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const update = async (
|
||||
id: string | number,
|
||||
payload: Record<string, any>,
|
||||
noRedirect: boolean = false
|
||||
) => {
|
||||
const res = await useNuxtApp().$api(`/api/resource/${relation}/${id}`, {
|
||||
method: "PUT",
|
||||
body: payload
|
||||
})
|
||||
|
||||
toast.add({title: `${dataType.labelSingle} geändert`})
|
||||
if(dataType.redirect && !noRedirect) {
|
||||
if(dataType.isStandardEntity) {
|
||||
await router.push(dataType.redirectToList ? `/standardEntity/${relation}` : `/standardEntity/${relation}/show/${res.id}`)
|
||||
} else {
|
||||
await router.push(dataType.redirectToList ? `/${relation}` : `/${relation}/show/${res.id}`)
|
||||
}
|
||||
}
|
||||
//modal.close() TODO: Modal Close wenn in Modal
|
||||
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft Delete = archived = true
|
||||
*/
|
||||
const archive = async (
|
||||
id: string | number
|
||||
) => {
|
||||
const res = await useNuxtApp().$api(`/api/resource/${relation}/${id}`, {
|
||||
method: "PUT",
|
||||
body: { archived: true }
|
||||
})
|
||||
navigateTo(dataType.isStandardEntity ? `/standardEntity/${relation}` : `/${relation}`)
|
||||
|
||||
return res
|
||||
|
||||
}
|
||||
|
||||
return {select, create, update, archive, selectSingle, selectSpecial, selectPaginated}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
25
frontend/composables/useErrorLogging.js
Normal file
25
frontend/composables/useErrorLogging.js
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
export const useErrorLogging = (resourceType) => {
|
||||
const supabase = useSupabaseClient()
|
||||
const toast = useToast()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const logError = async (error) => {
|
||||
let errorData = {
|
||||
message: error,
|
||||
tenant: profileStore.currentTenant,
|
||||
profile: profileStore.activeProfile.id
|
||||
}
|
||||
|
||||
const {data:supabaseData,error:supabaseError} = await supabase.from("errors").insert(errorData).select().single()
|
||||
|
||||
if(supabaseError) {
|
||||
console.error(supabaseError)
|
||||
} else if(supabaseData) {
|
||||
return supabaseData.id
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return { logError}
|
||||
}
|
||||
142
frontend/composables/useFiles.ts
Normal file
142
frontend/composables/useFiles.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
|
||||
export const useFiles = () => {
|
||||
const supabase = useSupabaseClient()
|
||||
const toast = useToast()
|
||||
|
||||
const auth = useAuthStore()
|
||||
|
||||
let bucket = "filesdev"
|
||||
|
||||
const uploadFiles = async (fileData, files,tags, upsert) => {
|
||||
const uploadSingleFile = async (file) => {
|
||||
//Create File Entry to Get ID for Folder
|
||||
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append("file", file)
|
||||
formData.append("meta", JSON.stringify(fileData))
|
||||
|
||||
const {fileReturn} = await useNuxtApp().$api("/api/files/upload",{
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
}
|
||||
|
||||
if(files.length === 1) {
|
||||
await uploadSingleFile(files[0])
|
||||
} else if( files.length > 1) {
|
||||
|
||||
for(let i = 0; i < files.length; i++){
|
||||
await uploadSingleFile(files[i])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const selectDocuments = async (sortColumn = null, folder = null) => {
|
||||
let data = []
|
||||
data = await useEntities("files").select("*, incominginvoice(*), project(*), vendor(*), customer(*), contract(*), plant(*), createddocument(*), vehicle(*), product(*), profile(*), check(*), inventoryitem(*)")
|
||||
|
||||
|
||||
|
||||
const res = await useNuxtApp().$api("/api/files/presigned",{
|
||||
method: "POST",
|
||||
body: {
|
||||
ids: data.map(i => i.id)
|
||||
}
|
||||
})
|
||||
|
||||
console.log(res)
|
||||
|
||||
return res.files
|
||||
}
|
||||
|
||||
const selectSomeDocuments = async (documentIds, sortColumn = null, folder = null) => {
|
||||
|
||||
if(documentIds.length === 0) return []
|
||||
const res = await useNuxtApp().$api("/api/files/presigned",{
|
||||
method: "POST",
|
||||
body: {
|
||||
ids: documentIds
|
||||
}
|
||||
})
|
||||
|
||||
console.log(res)
|
||||
|
||||
return res.files
|
||||
|
||||
|
||||
}
|
||||
|
||||
const selectDocument = async (id) => {
|
||||
let documentIds = [id]
|
||||
if(documentIds.length === 0) return []
|
||||
const res = await useNuxtApp().$api("/api/files/presigned",{
|
||||
method: "POST",
|
||||
body: {
|
||||
ids: documentIds
|
||||
}
|
||||
})
|
||||
|
||||
console.log(res)
|
||||
|
||||
return res.files[0]
|
||||
}
|
||||
|
||||
const downloadFile = async (id?: string, ids?: string[], returnAsBlob: Boolean = false) => {
|
||||
const url = id ? `/api/files/download/${id}` : `/api/files/download`
|
||||
const body = ids ? { ids } : undefined
|
||||
|
||||
const res:any = await useNuxtApp().$api.raw(url, {
|
||||
method: "POST",
|
||||
body,
|
||||
responseType: "blob", // wichtig!
|
||||
})
|
||||
|
||||
// Dateiname bestimmen
|
||||
let filename = "download"
|
||||
|
||||
if (id) {
|
||||
// Einzeldatei → nimm den letzten Teil des Pfads aus Content-Disposition
|
||||
const contentDisposition = res.headers?.get("content-disposition")
|
||||
if (contentDisposition) {
|
||||
const match = contentDisposition.match(/filename="?([^"]+)"?/)
|
||||
if (match) filename = match[1]
|
||||
}
|
||||
} else {
|
||||
filename = "dateien.zip"
|
||||
}
|
||||
|
||||
// Direkt speichern
|
||||
const blob = res._data as Blob
|
||||
|
||||
if(returnAsBlob) {
|
||||
return blob
|
||||
} else {
|
||||
const link = document.createElement("a")
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = filename
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
const dataURLtoFile = (dataurl:string, filename:string) => {
|
||||
let arr = dataurl.split(","),
|
||||
//@ts-ignore
|
||||
mime = arr[0].match(/:(.*?);/)[1],
|
||||
bstr = atob(arr[arr.length - 1]),
|
||||
n = bstr.length,
|
||||
u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
return new File([u8arr], filename, {type: mime});
|
||||
}
|
||||
|
||||
|
||||
return {uploadFiles, selectDocuments, selectSomeDocuments, selectDocument, downloadFile, dataURLtoFile}
|
||||
}
|
||||
25
frontend/composables/useFormat.ts
Normal file
25
frontend/composables/useFormat.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export const useFormatDuration = (durationInMinutes:number,) => {
|
||||
if (!durationInMinutes || durationInMinutes <= 0) return "00:00"
|
||||
|
||||
const hrs = Math.floor(durationInMinutes / 60)
|
||||
const mins = Math.floor(durationInMinutes % 60)
|
||||
|
||||
return `${String(hrs).padStart(2, "0")}:${String(mins).padStart(2, "0")}`
|
||||
}
|
||||
|
||||
export const useFormatDurationDays = (start,end) => {
|
||||
const startDate = useNuxtApp().$dayjs(start);
|
||||
const endDate = useNuxtApp().$dayjs(end);
|
||||
|
||||
if(startDate.isBefore(endDate)){
|
||||
// inkl. beider Tage → +1
|
||||
const days = endDate.diff(startDate, "day") + 1;
|
||||
|
||||
return days + " Tag" + (days > 1 ? "e" : "");
|
||||
} else {
|
||||
const days = startDate.diff(endDate, "day") + 1;
|
||||
|
||||
return days + " Tag" + (days > 1 ? "e" : "");
|
||||
}
|
||||
|
||||
}
|
||||
138
frontend/composables/useFunctions.js
Normal file
138
frontend/composables/useFunctions.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import axios from "axios";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const baseURL = /*"http://192.168.1.129:3333"*/ /*"http://localhost:3333"*/ "https://functions.fedeo.io"
|
||||
|
||||
export const useFunctions = () => {
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const getWorkingTimesEvaluationData = async (user_id, startDate, endDate) => {
|
||||
// Der neue Endpunkt ist /staff/time/evaluation und erwartet die Benutzer-ID als targetUserId Query-Parameter.
|
||||
|
||||
// Wir bauen den Query-String zusammen.
|
||||
const queryParams = new URLSearchParams({
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
targetUserId: user_id, // Die ID wird als targetUserId übergeben
|
||||
});
|
||||
|
||||
// Der neue API-Pfad verwendet nur noch den Basis-Endpunkt.
|
||||
const url = `/api/staff/time/evaluation?${queryParams.toString()}`;
|
||||
|
||||
// Annahme: useNuxtApp().$api führt den GET-Request aus und liefert die Daten zurück.
|
||||
return (await useNuxtApp().$api(url));
|
||||
}
|
||||
|
||||
const useNextNumber = async (numberRange) => {
|
||||
|
||||
return (await useNuxtApp().$api(`/api/functions/usenextnumber/${numberRange}`,)).usedNumber
|
||||
|
||||
|
||||
}
|
||||
|
||||
const useCreateTicket = async (subject,message,url,source) => {
|
||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||
|
||||
const {data} = await axios({
|
||||
method: "POST",
|
||||
url: `${baseURL}/functions/createticket`,
|
||||
data: {
|
||||
subject,
|
||||
message,
|
||||
source,
|
||||
url
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`
|
||||
}
|
||||
})
|
||||
|
||||
return !!data.ticket_created;
|
||||
|
||||
}
|
||||
|
||||
const useBankingGenerateLink = async (institutionId) => {
|
||||
return (await useNuxtApp().$api(`/api/banking/link/${institutionId}`)).link
|
||||
}
|
||||
|
||||
const useCreatePDF = async (data,path,type) => {
|
||||
const returnData = await useNuxtApp().$api(`/api/functions/pdf/${type}`, {
|
||||
method: "POST",
|
||||
body: {
|
||||
data: data,
|
||||
backgroundPath: path,
|
||||
}
|
||||
})
|
||||
|
||||
console.log(returnData)
|
||||
|
||||
return `data:${returnData.mimeType};base64,${returnData.base64}`
|
||||
|
||||
}
|
||||
|
||||
const useZipCheck = async (zip) => {
|
||||
const returnData = await useNuxtApp().$api(`/api/functions/check-zip/${zip}`, {
|
||||
method: "GET",
|
||||
})
|
||||
|
||||
return returnData
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const useGetInvoiceData = async (file) => {
|
||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||
|
||||
const {data} = await axios({
|
||||
method: "POST",
|
||||
url: `${baseURL}/functions/getinvoicedatafromgpt`,
|
||||
data: {
|
||||
file
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`
|
||||
}
|
||||
})
|
||||
|
||||
console.log(data)
|
||||
|
||||
return data
|
||||
|
||||
}
|
||||
|
||||
const useSendTelegramNotification = async (message) => {
|
||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||
|
||||
const {data,error} = await axios({
|
||||
method: "POST",
|
||||
url: `${baseURL}/functions/sendtelegramnotification`,
|
||||
data: {
|
||||
message: message
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`
|
||||
}
|
||||
})
|
||||
|
||||
if(error){
|
||||
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const useBankingCheckInstitutions = async (bic) => {
|
||||
|
||||
return await useNuxtApp().$api(`/api/banking/institutions/${bic}`)
|
||||
|
||||
}
|
||||
|
||||
const useBankingListRequisitions = async (reqId) => {
|
||||
|
||||
return await useNuxtApp().$api(`/api/banking/requisitions/${reqId}`)
|
||||
|
||||
}
|
||||
|
||||
return {getWorkingTimesEvaluationData, useNextNumber, useCreateTicket, useBankingGenerateLink, useZipCheck, useBankingCheckInstitutions, useBankingListRequisitions, useCreatePDF,useGetInvoiceData, useSendTelegramNotification}
|
||||
}
|
||||
110
frontend/composables/useHelpdesk.ts
Normal file
110
frontend/composables/useHelpdesk.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
// composables/useHelpdeskApi.ts
|
||||
import { ref } from 'vue'
|
||||
import { useNuxtApp } from '#app'
|
||||
|
||||
export function useHelpdeskApi() {
|
||||
const { $api } = useNuxtApp()
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
const base = '/api/helpdesk'
|
||||
|
||||
// 🔹 Konversationen abrufen
|
||||
async function getConversations(status?: string) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const query = status ? `?status=${status}` : ''
|
||||
const data = await $api(`${base}/conversations${query}`)
|
||||
return data
|
||||
} catch (err: any) {
|
||||
error.value = err.message || 'Fehler beim Laden der Konversationen'
|
||||
return []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 🔹 Einzelne Konversation
|
||||
async function getConversation(id: string) {
|
||||
try {
|
||||
return await $api(`${base}/conversations/${id}`)
|
||||
} catch (err: any) {
|
||||
error.value = err.message
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 🔹 Nachrichten einer Konversation
|
||||
async function getMessages(conversationId: string) {
|
||||
try {
|
||||
return await $api(`${base}/conversations/${conversationId}/messages`)
|
||||
} catch (err: any) {
|
||||
error.value = err.message
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 🔹 Neue Nachricht senden
|
||||
async function sendMessage(conversationId: string, text: string) {
|
||||
try {
|
||||
return await $api(`${base}/conversations/${conversationId}/messages`, {
|
||||
method: 'POST',
|
||||
body: { text },
|
||||
})
|
||||
} catch (err: any) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function replyMessage(conversationId: string, text: string) {
|
||||
try {
|
||||
return await $api(`${base}/conversations/${conversationId}/reply`, {
|
||||
method: 'POST',
|
||||
body: { text },
|
||||
})
|
||||
} catch (err: any) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// 🔹 Neuen Kontakt (manuell) anlegen
|
||||
async function createContact(payload: { email?: string; phone?: string; display_name?: string }) {
|
||||
try {
|
||||
return await $api(`${base}/contacts`, {
|
||||
method: 'POST',
|
||||
body: payload,
|
||||
})
|
||||
} catch (err: any) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// 🔹 Konversation-Status ändern
|
||||
async function updateConversationStatus(conversationId: string, status: string) {
|
||||
try {
|
||||
return await $api(`${base}/conversations/${conversationId}/status`, {
|
||||
method: 'PATCH',
|
||||
body: { status },
|
||||
})
|
||||
} catch (err: any) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
error,
|
||||
getConversations,
|
||||
getConversation,
|
||||
getMessages,
|
||||
sendMessage,
|
||||
createContact,
|
||||
updateConversationStatus,
|
||||
replyMessage,
|
||||
}
|
||||
}
|
||||
61
frontend/composables/useNumberRange.js
Normal file
61
frontend/composables/useNumberRange.js
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
export const useNumberRange = (resourceType) => {
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const numberRanges = profileStore.ownTenant.numberRanges
|
||||
|
||||
const numberRange = numberRanges[resourceType]
|
||||
|
||||
const useNextNumber = async () => {
|
||||
|
||||
let nextNumber = numberRange.nextNumber
|
||||
|
||||
let newNumberRanges = numberRanges
|
||||
|
||||
newNumberRanges[resourceType].nextNumber += 1
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("tenants")
|
||||
.update({numberRanges: newNumberRanges})
|
||||
.eq('id',profileStore.currentTenant)
|
||||
|
||||
|
||||
await profileStore.fetchOwnTenant()
|
||||
|
||||
return (numberRange.prefix ? numberRange.prefix : "") + nextNumber + (numberRange.suffix ? numberRange.suffix : "")
|
||||
}
|
||||
|
||||
return { useNextNumber}
|
||||
}
|
||||
|
||||
/*export const useNumberRange = (resourceType) => {
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const {numberRanges} = storeToRefs(useDataStore())
|
||||
const {fetchNumberRanges} = useDataStore()
|
||||
|
||||
const numberRange = numberRanges.value.find(range => range.resourceType === resourceType)
|
||||
|
||||
|
||||
|
||||
|
||||
const useNextNumber = async () => {
|
||||
|
||||
let nextNumber = numberRange.nextNumber
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("numberranges")
|
||||
.update({nextNumber: nextNumber + 1})
|
||||
.eq('id',numberRange.id)
|
||||
|
||||
fetchNumberRanges()
|
||||
|
||||
return (numberRange.prefix ? numberRange.prefix : "") + nextNumber + (numberRange.suffix ? numberRange.suffix : "")
|
||||
|
||||
}
|
||||
|
||||
return { useNextNumber}
|
||||
}*/
|
||||
9
frontend/composables/usePermission.ts
Normal file
9
frontend/composables/usePermission.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function usePermission() {
|
||||
const auth = useAuthStore()
|
||||
|
||||
const has = (key: string) => {
|
||||
return auth.hasPermission(key)
|
||||
}
|
||||
|
||||
return { has }
|
||||
}
|
||||
27
frontend/composables/usePrintLabel.js
Normal file
27
frontend/composables/usePrintLabel.js
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
import Handlebars from "handlebars";
|
||||
|
||||
export const usePrintLabel = async (printServerId,printerName , rawZPL ) => {
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
await supabase.from("printJobs").insert({
|
||||
tenant: profileStore.currentTenant,
|
||||
rawContent: rawZPL,
|
||||
printerName: printerName,
|
||||
printServer: printServerId
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
export const useGenerateZPL = (rawZPL,data) => {
|
||||
let template = Handlebars.compile(rawZPL)
|
||||
|
||||
return template(data)
|
||||
}
|
||||
|
||||
|
||||
|
||||
309
frontend/composables/useRole.js
Normal file
309
frontend/composables/useRole.js
Normal file
@@ -0,0 +1,309 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const useRole = () => {
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const generalAvailableRights = ref({
|
||||
projects: {
|
||||
label: "Projekte",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"projects-viewAll": {
|
||||
label: "Alle Projekte einsehen",
|
||||
parent: "projects"
|
||||
},
|
||||
"projects-create": {
|
||||
label: "Projekte erstellen",
|
||||
parent: "projects"
|
||||
},
|
||||
contracts: {
|
||||
label: "Verträge",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"contracts-viewAll": {
|
||||
label: "Alle Verträge einsehen",
|
||||
parent: "contracts"
|
||||
},
|
||||
"contracts-create": {
|
||||
label: "Verträge erstellen",
|
||||
parent: "contracts"
|
||||
},
|
||||
plants: {
|
||||
label: "Objekte",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"plants-viewAll": {
|
||||
label: "Alle Objekte einsehen",
|
||||
parent: "plants"
|
||||
},
|
||||
"plants-create": {
|
||||
label: "Objekte erstellen",
|
||||
parent: "plants"
|
||||
},
|
||||
products: {
|
||||
label: "Artikel",
|
||||
showToAllUsers: true
|
||||
},
|
||||
"products-create": {
|
||||
label: "Artikel erstellen",
|
||||
parent: "products"
|
||||
},
|
||||
productcategories: {
|
||||
label: "Artikelkategorie",
|
||||
showToAllUsers: true
|
||||
},
|
||||
"productcategories-create": {
|
||||
label: "Artikelkategorie erstellen",
|
||||
parent: "productcategories"
|
||||
},
|
||||
services: {
|
||||
label: "Leistungen",
|
||||
showToAllUsers: true
|
||||
},
|
||||
"services-create": {
|
||||
label: "Leistungen erstellen",
|
||||
parent: "services"
|
||||
},
|
||||
servicecategories: {
|
||||
label: "Leistungskategorien",
|
||||
showToAllUsers: true
|
||||
},
|
||||
"servicecategories-create": {
|
||||
label: "Leistungskategorien erstellen",
|
||||
parent: "servicecategories"
|
||||
},
|
||||
customers: {
|
||||
label: "Kunden",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"customers-viewAll": {
|
||||
label: "Alle Kunden einsehen",
|
||||
parent: "customers"
|
||||
},
|
||||
"customers-create": {
|
||||
label: "Kunden erstellen",
|
||||
parent: "customers"
|
||||
},
|
||||
contacts: {
|
||||
label: "Kontakte",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"contacts-viewAll": {
|
||||
label: "Alle Kontakte einsehen",
|
||||
parent: "contacts"
|
||||
},
|
||||
"contacts-create": {
|
||||
label: "Kontakte erstellen",
|
||||
parent: "contacts"
|
||||
},
|
||||
vendors: {
|
||||
label: "Lieferanten",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"vendors-viewAll": {
|
||||
label: "Alle Lieferanten einsehen",
|
||||
parent: "vendors"
|
||||
},
|
||||
"vendors-create": {
|
||||
label: "Lieferanten erstellen",
|
||||
parent: "vendors"
|
||||
},
|
||||
checks: {
|
||||
label: "Überprüfungen",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"checks-viewAll": {
|
||||
label: "Alle Überprüfungen einsehen",
|
||||
parent: "checks"
|
||||
},
|
||||
"checks-create": {
|
||||
label: "Überprüfungen erstellen",
|
||||
parent: "checks"
|
||||
},
|
||||
vehicles: {
|
||||
label: "Fahrzeuge",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"vehicles-viewAll": {
|
||||
label: "Alle Fahrzeuge einsehen",
|
||||
parent: "vehicles"
|
||||
},
|
||||
"vehicles-create": {
|
||||
label: "Fahrzeuge erstellen",
|
||||
parent: "vehicles"
|
||||
},
|
||||
inventoryitems: {
|
||||
label: "Inventarartikel",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"inventoryitems-viewAll": {
|
||||
label: "Alle Inventarartikel einsehen",
|
||||
parent: "inventoryitems"
|
||||
},
|
||||
"inventoryitems-create": {
|
||||
label: "Inventarartikel erstellen",
|
||||
parent: "inventoryitems"
|
||||
},
|
||||
inventoryitemgroups: {
|
||||
parent: "inventoryitems",
|
||||
label: "Inventarartikelgruppen",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"inventoryitemgroups-viewAll": {
|
||||
label: "Alle Inventarartikelgruppen einsehen",
|
||||
parent: "inventoryitemgroups"
|
||||
},
|
||||
"inventoryitemgroups-create": {
|
||||
label: "Inventarartikelgruppen erstellen",
|
||||
parent: "inventoryitemgroups"
|
||||
},
|
||||
absencerequests: {
|
||||
label: "Abwesenheiten",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"absencerequests-viewAll": {
|
||||
label: "Alle Abwesenheiten einsehen",
|
||||
parent: "absencerequests"
|
||||
},
|
||||
"absencerequests-create": {
|
||||
label: "Abwesenheiten erstellen",
|
||||
parent: "absencerequests"
|
||||
},
|
||||
events: {
|
||||
label: "Termine",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"events-viewAll": {
|
||||
label: "Alle Termine einsehen",
|
||||
parent: "events"
|
||||
},
|
||||
"events-create": {
|
||||
label: "Termine erstellen",
|
||||
parent: "events"
|
||||
},
|
||||
spaces: {
|
||||
label: "Lagerplätze",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"spaces-viewAll": {
|
||||
label: "Alle Lagerplätze einsehen",
|
||||
parent: "spaces"
|
||||
},
|
||||
"spaces-create": {
|
||||
label: "Lagerplätze erstellen",
|
||||
parent: "spaces"
|
||||
},
|
||||
roles: {
|
||||
label: "Rollen",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"roles-viewAll": {
|
||||
label: "Alle Rollen einsehen",
|
||||
parent: "roles"
|
||||
},
|
||||
"roles-create": {
|
||||
label: "Rollen erstellen",
|
||||
parent: "roles"
|
||||
},
|
||||
tasks: {
|
||||
label: "Aufgaben",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"tasks-viewAll": {
|
||||
label: "Alle Aufgaben einsehen",
|
||||
parent: "tasks"
|
||||
},
|
||||
"tasks-create": {
|
||||
label: "Aufgaben erstellen",
|
||||
parent: "tasks"
|
||||
},
|
||||
costcentres: {
|
||||
label: "Kostenstellen",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"costcentres-viewAll": {
|
||||
label: "Alle Kostenstellen einsehen",
|
||||
parent: "costcentres"
|
||||
},
|
||||
"costcentres-create": {
|
||||
label: "Kostenstellen erstellen",
|
||||
parent: "costcentres"
|
||||
},
|
||||
ownaccounts: {
|
||||
label: "Buchungskonten",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"ownaccounts-viewAll": {
|
||||
label: "Alle Buchungskonten einsehen",
|
||||
parent: "ownaccounts"
|
||||
},
|
||||
"ownaccounts-create": {
|
||||
label: "Buchungskonten erstellen",
|
||||
parent: "ownaccounts"
|
||||
},
|
||||
documentboxes: {
|
||||
label: "Dokuemntenboxen",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"documentboxes-viewAll": {
|
||||
label: "Alle Dokuemntenboxen einsehen",
|
||||
parent: "documentboxesx"
|
||||
},
|
||||
"documentboxes-create": {
|
||||
label: "Dokuemntenboxen erstellen",
|
||||
parent: "documentboxes"
|
||||
},
|
||||
hourrates: {
|
||||
label: "Stundensätze",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"hourrates-viewAll": {
|
||||
label: "Alle Stundensätze einsehen",
|
||||
parent: "hourrates"
|
||||
},
|
||||
"hourrates-create": {
|
||||
label: "Stundensätze erstellen",
|
||||
parent: "hourrates"
|
||||
},
|
||||
"inventory": {
|
||||
label: "Lager",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
let role = profileStore.activeProfile.role
|
||||
|
||||
const checkRight = (right) => {
|
||||
let rightsToCheck = [right]
|
||||
|
||||
|
||||
//console.log(right.split("-"))
|
||||
|
||||
if(right.split("-").length > 1) {
|
||||
rightsToCheck.push(right.split("-")[0])
|
||||
}
|
||||
//console.log(rightsToCheck)
|
||||
|
||||
let hasAllNeccessaryRights = true
|
||||
|
||||
//console.log(role.rights)
|
||||
|
||||
rightsToCheck.forEach(i => {
|
||||
if(!role.rights.includes(i)){
|
||||
hasAllNeccessaryRights = false
|
||||
}
|
||||
})
|
||||
|
||||
return hasAllNeccessaryRights
|
||||
}
|
||||
|
||||
return {
|
||||
role,
|
||||
generalAvailableRights,
|
||||
checkRight
|
||||
}
|
||||
|
||||
}
|
||||
15
frontend/composables/useSearch.js
Normal file
15
frontend/composables/useSearch.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export const useSearch = (searchString,items) => {
|
||||
if(!searchString) {
|
||||
return items
|
||||
}
|
||||
|
||||
return items.filter(i => JSON.stringify(i).toLowerCase().includes(searchString.toLowerCase()))
|
||||
}
|
||||
|
||||
export const useListFilter = (searchString,items,showArchived = false) => {
|
||||
if(!searchString) {
|
||||
return items.filter(i => !i.archived)
|
||||
}
|
||||
|
||||
return items.filter(i => JSON.stringify(i).toLowerCase().includes(searchString.toLowerCase()) && !i.archived)
|
||||
}
|
||||
18
frontend/composables/useSort.js
Normal file
18
frontend/composables/useSort.js
Normal file
@@ -0,0 +1,18 @@
|
||||
export const useSort = (items,column,direction) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if(!searchString) {
|
||||
return items
|
||||
}
|
||||
|
||||
return items.filter(i => JSON.stringify(i).toLowerCase().includes(searchString.toLowerCase()))
|
||||
}
|
||||
119
frontend/composables/useStaffTime.ts
Normal file
119
frontend/composables/useStaffTime.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { useAuthStore } from '~/stores/auth'
|
||||
|
||||
export const useStaffTime = () => {
|
||||
const { $api, $dayjs } = useNuxtApp()
|
||||
const auth = useAuthStore()
|
||||
|
||||
// ... (list Funktion bleibt gleich) ...
|
||||
const list = async (filter?: { user_id?: string, from?: string, to?: string }) => {
|
||||
// ... (Code wie zuvor)
|
||||
const from = filter?.from || $dayjs().startOf('month').format('YYYY-MM-DD')
|
||||
const to = filter?.to || $dayjs().endOf('month').format('YYYY-MM-DD')
|
||||
const targetUserId = filter?.user_id || auth.user.id
|
||||
const params = new URLSearchParams({ from, to, targetUserId })
|
||||
try {
|
||||
const spans = await $api(`/api/staff/time/spans?${params.toString()}`)
|
||||
return (spans || []).map((span: any) => {
|
||||
const start = $dayjs(span.startedAt)
|
||||
const end = span.endedAt ? $dayjs(span.endedAt) : $dayjs()
|
||||
return {
|
||||
id: span.sourceEventIds && span.sourceEventIds.length > 0 ? span.sourceEventIds[0] : null,
|
||||
eventIds: span.sourceEventIds || [],
|
||||
state: span.status,
|
||||
started_at: span.startedAt,
|
||||
stopped_at: span.endedAt,
|
||||
duration_minutes: end.diff(start, 'minute'),
|
||||
user_id: targetUserId,
|
||||
type: span.type,
|
||||
description: span.payload?.description || ''
|
||||
}
|
||||
}).sort((a: any, b: any) => $dayjs(b.started_at).valueOf() - $dayjs(a.started_at).valueOf())
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden:", error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Startet "jetzt" (Live-Modus).
|
||||
* Kann optional eine Zeit empfangen (für manuelle Korrekturen),
|
||||
* aber wir nutzen dafür besser die createEntry Funktion unten.
|
||||
*/
|
||||
const start = async (description = "Arbeitszeit", time?: string) => {
|
||||
await $api('/api/staff/time/event', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
eventtype: 'work_start',
|
||||
eventtime: time || new Date().toISOString(), // 💡 Fix: Zeit akzeptieren
|
||||
payload: { description }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const stop = async () => {
|
||||
await $api('/api/staff/time/event', { method: 'POST', body: { eventtype: 'work_end', eventtime: new Date().toISOString() } })
|
||||
}
|
||||
|
||||
const submit = async (entry: any) => {
|
||||
const ids = entry.eventIds || (entry.id ? [entry.id] : [entry]);
|
||||
if (!ids || ids.length === 0) return
|
||||
await $api('/api/staff/time/submit', { method: 'POST', body: { eventIds: ids } })
|
||||
}
|
||||
|
||||
const approve = async (entry: any) => {
|
||||
if (!entry?.user_id) return
|
||||
const ids = entry.eventIds || [entry.id];
|
||||
await $api('/api/staff/time/approve', { method: 'POST', body: { eventIds: ids, employeeUserId: entry.user_id } })
|
||||
}
|
||||
|
||||
const reject = async (entry: any, reason = "Abgelehnt") => {
|
||||
if (!entry?.user_id) return
|
||||
const ids = entry.eventIds || [entry.id];
|
||||
await $api('/api/staff/time/reject', { method: 'POST', body: { eventIds: ids, employeeUserId: entry.user_id, reason } })
|
||||
}
|
||||
|
||||
const update = async (entry: any, newData: { start: string, end: string | null, type: string, description: string }) => {
|
||||
if (!entry || !entry.eventIds || entry.eventIds.length === 0) {
|
||||
throw new Error("Bearbeiten fehlgeschlagen: Keine IDs.")
|
||||
}
|
||||
await $api('/api/staff/time/edit', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
originalEventIds: entry.eventIds,
|
||||
newStart: newData.start,
|
||||
newEnd: newData.end,
|
||||
newType: newData.type,
|
||||
description: newData.description,
|
||||
reason: "Manuelle Bearbeitung"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 🆕 NEU: Manuellen Eintrag erstellen (Vergangenheit oder Zeitraum)
|
||||
const createEntry = async (data: { start: string, end: string | null, type: string, description: string }) => {
|
||||
// 1. Start Event senden
|
||||
// Wir nutzen den dynamischen Typ (work_start, vacation_start etc.)
|
||||
await $api('/api/staff/time/event', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
eventtype: `${data.type}_start`,
|
||||
eventtime: data.start,
|
||||
payload: { description: data.description }
|
||||
}
|
||||
})
|
||||
|
||||
// 2. End Event senden (falls vorhanden)
|
||||
if (data.end) {
|
||||
await $api('/api/staff/time/event', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
eventtype: `${data.type}_end`,
|
||||
eventtime: data.end
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { list, start, stop, submit, approve, reject, update, createEntry }
|
||||
}
|
||||
138
frontend/composables/useSum.js
Normal file
138
frontend/composables/useSum.js
Normal file
@@ -0,0 +1,138 @@
|
||||
|
||||
export const useSum = () => {
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const getIncomingInvoiceSum = (invoice) => {
|
||||
let sum = 0
|
||||
invoice.accounts.forEach(account => {
|
||||
|
||||
|
||||
sum += account.amountTax
|
||||
sum += account.amountNet
|
||||
|
||||
|
||||
|
||||
})
|
||||
return sum.toFixed(2)
|
||||
}
|
||||
|
||||
const getCreatedDocumentSum = (createddocument,createddocuments = []) => {
|
||||
let totalNet = 0
|
||||
let total19 = 0
|
||||
let total7 = 0
|
||||
|
||||
|
||||
|
||||
|
||||
createddocument.rows.forEach(row => {
|
||||
if(!['pagebreak','title','text'].includes(row.mode)){
|
||||
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(3)
|
||||
totalNet = totalNet + Number(rowPrice)
|
||||
|
||||
if(row.taxPercent === 19) {
|
||||
total19 = total19 + Number(rowPrice * 0.19)
|
||||
} else if(row.taxPercent === 7) {
|
||||
total7 = total7 + Number(rowPrice * 0.07)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
let totalGross = Number(totalNet.toFixed(2)) + Number(total19.toFixed(2)) + Number(total7.toFixed(2))
|
||||
|
||||
let totalGrossAlreadyPaid = 0
|
||||
|
||||
|
||||
createddocument.usedAdvanceInvoices.forEach(advanceInvoiceId => {
|
||||
let advanceInvoice = createddocuments.find(i => i.id === advanceInvoiceId)
|
||||
|
||||
let priceNet = advanceInvoice.rows.find(i => i.advanceInvoiceData).price
|
||||
|
||||
let partSum = priceNet * ((100 + advanceInvoice.rows.find(i => i.advanceInvoiceData).taxPercent) / 100)
|
||||
|
||||
totalGrossAlreadyPaid += partSum
|
||||
})
|
||||
|
||||
|
||||
let sumToPay = totalGross - totalGrossAlreadyPaid
|
||||
|
||||
return Number(sumToPay.toFixed(2))
|
||||
}
|
||||
|
||||
const getCreatedDocumentSumDetailed = (createddocument) => {
|
||||
let totalNet = 0
|
||||
let total19 = 0
|
||||
let total7 = 0
|
||||
|
||||
createddocument.rows.forEach(row => {
|
||||
if(!['pagebreak','title','text'].includes(row.mode)){
|
||||
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(3)
|
||||
totalNet = totalNet + Number(rowPrice)
|
||||
|
||||
if(row.taxPercent === 19) {
|
||||
total19 = total19 + Number(rowPrice * 0.19)
|
||||
} else if(row.taxPercent === 7) {
|
||||
total7 = total7 + Number(rowPrice * 0.07)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
//Title Sum
|
||||
|
||||
let titleSums = {}
|
||||
let lastTitle = ""
|
||||
|
||||
createddocument.rows.forEach(row => {
|
||||
if(row.mode === 'title'){
|
||||
titleSums[`${row.pos} - ${row.text}`] = 0
|
||||
lastTitle = `${row.pos} - ${row.text}`
|
||||
} else if(!['pagebreak','text'].includes(row.mode) && lastTitle !== ""){
|
||||
titleSums[lastTitle] = Number(titleSums[lastTitle]) + Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) )
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
let totalGross = Number(totalNet.toFixed(2)) + Number(total19.toFixed(2)) + Number(total7.toFixed(2))
|
||||
|
||||
let totalGrossAlreadyPaid = 0
|
||||
|
||||
createddocument.usedAdvanceInvoices.forEach(advanceInvoiceId => {
|
||||
let advanceInvoice = createddocuments.value.find(i => i.id === advanceInvoiceId)
|
||||
|
||||
let priceNet = advanceInvoice.rows.find(i => i.advanceInvoiceData).price
|
||||
|
||||
let partSum = priceNet * ((100 + advanceInvoice.rows.find(i => i.advanceInvoiceData).taxPercent) / 100)
|
||||
|
||||
totalGrossAlreadyPaid += partSum
|
||||
})
|
||||
|
||||
console.log(totalGrossAlreadyPaid)
|
||||
|
||||
let sumToPay = totalGross - totalGrossAlreadyPaid
|
||||
|
||||
return {
|
||||
titleSums: titleSums,
|
||||
totalNet: totalNet,
|
||||
total19: total19,
|
||||
total7: total7,
|
||||
totalGross: totalGross,
|
||||
totalGrossAlreadyPaid: totalGrossAlreadyPaid,
|
||||
totalSumToPay: sumToPay
|
||||
}
|
||||
}
|
||||
|
||||
const getIsPaid = (createddocument,createddocuments) => {
|
||||
let amountPaid = 0
|
||||
createddocument.statementallocations.forEach(allocation => amountPaid += allocation.amount)
|
||||
|
||||
return Number(amountPaid.toFixed(2)) === getCreatedDocumentSum(createddocument,createddocuments)
|
||||
}
|
||||
|
||||
return {getIncomingInvoiceSum, getCreatedDocumentSum, getCreatedDocumentSumDetailed, getIsPaid}
|
||||
|
||||
}
|
||||
28
frontend/composables/useUsers.ts
Normal file
28
frontend/composables/useUsers.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
import { useDataStore } from "~/stores/data"
|
||||
|
||||
export const useUsers = (
|
||||
id: string | number,
|
||||
) => {
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
|
||||
const getProfile = async () => {
|
||||
|
||||
|
||||
const res = await useNuxtApp().$api(`/api/profiles/${id}`, {
|
||||
method: "GET"
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
return {getProfile}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
25
frontend/composables/useZebraPrinter.js
Normal file
25
frontend/composables/useZebraPrinter.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import axios from 'axios'
|
||||
|
||||
export const useZebraStatus = async (relation,select = '*', sortColumn = null) => {
|
||||
const {data} = await axios({
|
||||
method:"get",
|
||||
url:"http://192.168.2.161/config.html",
|
||||
|
||||
})
|
||||
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
export const useZebraPstPrnt = async (zpl) => {
|
||||
try {
|
||||
const {data} = await axios({
|
||||
method: "post",
|
||||
url:"http://192.168.2.161/pstprnt",
|
||||
data: zpl
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user