Added Vehicles

Added Bankimport, BankAccounts, BankStatements
Some Visual Changes
Added Contacts
Changes in VendorInvoices
Added layouts with default an one for Login PAge
Added Input Group Component
This commit is contained in:
2023-12-22 17:50:22 +01:00
parent 8a1e2384d1
commit 9e092823e4
22 changed files with 1475 additions and 243 deletions

8
docu
View File

@@ -24,4 +24,10 @@ Admin Client:
Client Id: 7d1d57c6-6368-4aea-811d-9e651fd55a36
Client Secret: 30579e3c-8fbb-4684-a344-a521f962fd7c
Data Decryption Key: 3598063141d04b3b97c60c15fe4c9099
Data Decryption Key: 3598063141d04b3b97c60c15fe4c9099
GoCardless:
Sec: 65a3ffe63e403a19241ddd8f316566cb3f41e3fa065946ab582f881e16e8bed0f1b86ee29667950c88a43dcfb1c71d27049ee686a777f85d86383df28664ec55
Key: 478cba26-f265-4e17-b8a8-679e0e8a855b

View File

@@ -10,7 +10,7 @@ const colorMode = useColorMode()
const supabase = useSupabaseClient()
const tenants = (await supabase.from("tenants").select()).data
const {loaded, profiles} = storeToRefs(useDataStore())
const {fetchData, getProfileById} = useDataStore()
const {fetchData, getProfileById, clearStore} = useDataStore()
const userProfile = (user.value ? getProfileById(user.value.id) : {})
//console.log(userProfile)
@@ -43,6 +43,11 @@ const navLinks = [
to: "/vendors",
icon: "i-heroicons-truck"
},
{
label: "Kontakte",
to: "/contacts",
icon: "i-heroicons-user-group"
},
]
},
{
@@ -54,6 +59,11 @@ const navLinks = [
to: "/vendorinvoices",
icon: "i-heroicons-user-group"
},
{
label: "Bank",
to: "/banking",
icon: "i-heroicons-currency-euro"
},
]
},
{
@@ -128,6 +138,11 @@ const navLinks = [
to: "/inventory/spaces",
icon: "i-heroicons-square-3-stack-3d"
},
{
label: "Fahrzeuge",
to: "/vehicles",
icon: "i-heroicons-truck"
},
]
},
]
@@ -201,8 +216,9 @@ const items = [
icon: 'i-heroicons-cog-8-tooth',
to: "/settings/externalDevices"
},{
label: 'Settings',
icon: 'i-heroicons-cog-8-tooth'
label: 'Benutzer',
icon: 'i-heroicons-user-group',
to: "/settings/users"
}], /*[{
label: 'Documentation',
icon: 'i-heroicons-book-open'
@@ -215,9 +231,11 @@ const items = [
}],*/ [{
label: 'Sign out',
icon: 'i-heroicons-arrow-left-on-rectangle',
click: () => {
supabase.auth.signOut()
router.push("/login")
click: async () => {
await supabase.auth.signOut()
await clearStore()
await router.push("/login")
}
}]
]
@@ -251,6 +269,7 @@ const items = [
<UDropdown :items="items" :ui="{ item: { disabled: 'cursor-text select-text' } }" :popper="{ placement: 'bottom-start' }">
<UAvatar
:alt="userProfile ? userProfile.firstName + ' ' + userProfile.lastName : '' "
icon="i-heroicons-photo"
/>
<template #account="{ item }">
@@ -274,14 +293,11 @@ const items = [
</UHeader>
<UDivider />
<div class="m-3" id="contentContainer">
<NuxtPage
v-if="loaded"
/>
<div
v-else
>
<UProgress animation="carousel" />
</div>
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
</div>
<!-- <UFooter>
<template #left>

View File

@@ -16,7 +16,7 @@ const props = defineProps({
const {document, openShowModal:openShowModalProp } = props;
const {fetchDocuments, getDocumentTags, fetchVendorInvoices} = useDataStore()
const {projects, customers} = storeToRefs(useDataStore())
const {projects, customers, vendorInvoices} = storeToRefs(useDataStore())
const tags = getDocumentTags
const openShowModal = ref(false)
@@ -103,15 +103,35 @@ const updateDocument = async () => {
}
const createVendorInvoice = async () => {
const {data,error} = await supabase
const {data:vendorInvoiceData,error:vendorInvoiceError} = await supabase
.from("vendorInvoices")
.insert([{
document: document.id,
}])
.select()
if(error) {
console.log(error)
} else if(data) {
if(vendorInvoiceError) {
console.log(vendorInvoiceError)
} else if(vendorInvoiceData) {
const {data:documentData,error:documentError} = await supabase
.from("documents")
.update({
vendorInvoice: vendorInvoiceData[0].id
})
.eq('id',document.id)
.select()
if(documentError) {
console.log(documentError)
} else {
toast.add({title: "Dokument aktualisiert"})
fetchDocuments()
openShowModal.value = false
}
fetchVendorInvoices()
await router.push("/vendorinvoices")
}
@@ -135,6 +155,10 @@ const createVendorInvoice = async () => {
v-model="document.selected"
class="ml-2"
/>
<br>
<UBadge
v-if="document.vendorInvoice"
>{{vendorInvoices.find(item => item.id === document.vendorInvoice) ? vendorInvoices.find(item => item.id === document.vendorInvoice).reference : ''}}</UBadge>
</div>
@@ -226,7 +250,7 @@ const createVendorInvoice = async () => {
.documentListItem {
display:block;
width: 15vw;
height: 30vh;
height: 33vh;
padding:1em;
margin: 0.7em;
border: 1px solid lightgrey;

View File

@@ -0,0 +1,20 @@
<script setup>
const props = defineProps({
gap: {
type: Number,
default: 1
}
})
const {gap} = props
</script>
<template>
<div :class="`flex items-center gap-${gap}`">
<slot/>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,21 @@
<script setup>
const {loaded, profiles} = storeToRefs(useDataStore())
</script>
<template>
<slot v-if="loaded"/>
<div
v-else
>
<UProgress animation="carousel" />
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
<slot/>
</template>
<style scoped>
</style>

View File

@@ -1,18 +0,0 @@
<script setup>
const route = useRoute()
const supabase = useSupabaseClient()
console.log(route.params.account)
const accounts = (await supabase.from("bankAccounts").select()).data
</script>
<template>
<div>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,103 +1,88 @@
<script setup>
import Papa from 'papaparse'
const supabase = useSupabaseClient()
const accounts = (supabase.from("bankAccounts").select()).data
let items = ref([])
async function readSingleFile(evt) {
var f = document.getElementById('fileInput').files[0];
if (f) {
let results = []
let text = await f.text()
results = Papa.parse(text).data
console.log(results[0])
results = results.slice(1)
results = results.map(temp => {
return {
iban: temp[1],
bank: temp[3],
date: temp[4],
partnerName: temp[6],
partnerIban: temp[7],
type: temp[9],
text: temp[10],
value: temp[11],
currency: temp[12]
}
})
console.log(results)
items.value = results
parseStatements()
} else {
alert("Failed to load file");
}
}
const newStatements = ref([])
const parseStatements = async () => {
items.value.forEach(item => {
if(item.date) {
let returnObj = {
date: item.date,
partnerIban: item.partnerIban,
partnerName: item.partnerName,
text: item.text,
value: Number(item.value.replace(",",".")),
}
newStatements.value.push(returnObj)
}
})
const {data,error} = await supabase
.from("bankStatements")
.insert(newStatements.value)
.select()
console.log(data)
console.log(error)
}
</script>
<template>
<div>
<UInput
type="file"
id="fileInput"
/>
<UButton
@click="readSingleFile"
>Test</UButton>
<div id="main">
<!-- TODO: Kontakte erstellen und dem Kunden zuweisen -->
<table>
<tr
v-for="temp in items"
<!--<InputGroup>
<UButton @click="router.push(`/banking/accounts/create/`)">+ Kontakt</UButton>
<UInput
v-model="searchString"
placeholder="Suche..."
/>
</InputGroup>-->
<UTable
:rows="filteredRows"
:columns="itemColumns"
@select="selectItem"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }"
>
<td>{{temp.date}}</td>
<td>{{temp.partnerName}}</td>
<td>{{temp.text}}</td>
<td>{{temp.value}}</td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</UTable>
</div>
</div>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
const router = useRouter()
const {bankAccounts} = storeToRefs(useDataStore())
const mode = ref("show")
const itemColumns = [
{
key: "name",
label: "Name",
sortable: true
},
{
key: "iban",
label: "IBAN",
sortable: true
},
{
key: "bankId",
label: "Bank",
sortable: true
},
{
key: "ownerName",
label: "Besitzer",
sortable: true
}
]
const selectItem = (item) => {
router.push(`/banking/statements/${item.id} `)
}
const searchString = ref('')
const filteredRows = computed(() => {
bankAccounts.value = bankAccounts.value.filter(account => account.used)
if(!searchString.value) {
return bankAccounts.value
}
return bankAccounts.value.filter(item => {
return Object.values(item).some((value) => {
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
})
})
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,142 @@
<script setup>
import * as dayjs from "dayjs";
definePageMeta({
middleware: "auth"
})
const route = useRoute()
const supabase = useSupabaseClient()
const {bankStatements,bankAccounts} = storeToRefs(useDataStore())
const searchString = ref("")
const showAssigned = ref(false)
const selectedAccount = ref(0)
const filteredRows = computed(() => {
let statements = bankStatements.value
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) {
return statements.value.filter(item => {
return Object.values(item).some((value) => {
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
})
})
} else {
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="bankAccounts.filter(account => account.used)"
v-model="selectedAccount"
option-attribute="iban"
value-attribute="id"
>
<template #label>
{{bankAccounts.find(account => account.id === selectedAccount) ? bankAccounts.find(account => account.id === selectedAccount).iban : "Kontoauswählen"}}
</template>
</USelectMenu>
<UCheckbox
v-model="showAssigned"
label="Zugeordnete Anzeigen"
/>
</InputGroup>
<UTable
:rows="bankStatements"
: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>

View File

@@ -0,0 +1,291 @@
<script setup>
definePageMeta({
middleware: "auth"
})
//
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
//Store
const {customers, vendors, contacts } = storeToRefs(useDataStore())
const {fetchContacts, getContactById} = useDataStore()
let currentContact = null
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
active: true
})
//Functions
const setupPage = () => {
if(mode.value === "show" || mode.value === "edit"){
currentContact = getContactById(Number(useRoute().params.id))
}
if(mode.value === "edit") itemInfo.value = currentContact
}
const createItem = async () => {
let fullName = itemInfo.value.firstName + ' ' + itemInfo.value.lastName
const {data,error} = await supabase
.from("contacts")
.insert([{...itemInfo.value, fullName}])
.select()
if(error) {
console.log(error)
} else {
mode.value = "show"
itemInfo.value = {
id: 0,
}
toast.add({title: "Kontakt erfolgreich erstellt"})
await fetchContacts()
router.push(`/contacts/show/${data[0].id}`)
setupPage()
}
}
const editCustomer = async () => {
router.push(`/contacts/edit/${currentContact.id}`)
setupPage()
}
const cancelEditorCreate = () => {
mode.value = "show"
itemInfo.value = {
id: 0,
name: "",
}
}
const updateCustomer = async () => {
const {error} = await supabase
.from("contacts")
.update(itemInfo.value)
.eq('id',itemInfo.value.id)
console.log(error)
mode.value = "show"
itemInfo.value = {
id: 0,
name: "",
}
toast.add({title: "Kontakt erfolgreich gespeichert"})
fetchContacts()
}
setupPage()
</script>
<template>
<div>
<UCard v-if="currentContact && mode == 'show'" >
<template #header>
<UBadge
v-if="currentContact.active"
>
Kontakt aktiv
</UBadge>
<UBadge
v-else
color="red"
>
Kontakt inaktiv
</UBadge>
{{currentContact.fullName}}
</template>
<InputGroup class="mb-3">
<UButton
v-if="currentContact.customer"
:to="`/customers/show/${currentContact.customer}`"
>
Zum Kunden
</UButton>
<UButton
v-if="currentContact.vendor"
:to="`/vendors/show/${currentContact.vendor}`"
>
Zum Lieferanten
</UButton>
</InputGroup>
<span v-if="currentContact.customer">Kunde: {{customers.find(customer => customer.id === currentContact.customer) ? customers.find(customer => customer.id === currentContact.customer).name : "" }}</span><br>
<span v-if="currentContact.vendor">Lieferant: {{vendors.find(vendor => vendor.id === currentContact.vendor) ? vendors.find(vendor => vendor.id === currentContact.vendor).name : ""}}</span><br>
<span>E-Mail: {{currentContact.email}}</span><br>
<span>Mobil: {{currentContact.phoneMobile}}</span><br>
<span>Festnetz: {{currentContact.phoneHome}}</span><br>
<span>Rolle: {{currentContact.role}}</span>
<DevOnly>
<UDivider
class="my-3"
/>
{{currentContact}}
</DevOnly>
<template #footer>
<UButton
v-if="mode == 'show' && currentContact.id"
@click="editCustomer"
>
Bearbeiten
</UButton>
<UButton
color="red"
class="ml-2"
disabled
>
Archivieren
</UButton>
</template>
</UCard>
<UCard v-else-if="mode == 'edit' || mode == 'create'" >
<template #header>
{{itemInfo.fullName}}
</template>
<UFormGroup
label="Anrede:"
>
<UInput
v-model="itemInfo.salutation"
/>
</UFormGroup>
<UFormGroup
label="Vorname:"
>
<UInput
v-model="itemInfo.firstName"
/>
</UFormGroup>
<UFormGroup
label="Nachname:"
>
<UInput
v-model="itemInfo.lastName"
/>
</UFormGroup>
<UFormGroup
label="Kunde:"
>
<USelectMenu
v-model="itemInfo.customer"
option-attribute="name"
value-attribute="id"
:options="customers"
searchable
:search-attributes="['name']"
>
<template #label>
{{customers.find(customer => customer.id === itemInfo.customer) ? customers.find(customer => customer.id === itemInfo.customer).name : "Kunde auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Lieferant:"
>
<USelectMenu
v-model="itemInfo.vendor"
option-attribute="name"
value-attribute="id"
:options="vendors"
searchable
:search-attributes="['name']"
>
<template #label>
{{vendors.find(vendor => vendor.id === itemInfo.vendor) ? vendors.find(vendor => vendor.id === itemInfo.vendor).name : "Lieferant auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Kontakt aktiv:"
>
<UCheckbox
v-model="itemInfo.active"
/>
</UFormGroup>
<UFormGroup
label="E-Mail:"
>
<UInput
v-model="itemInfo.email"
/>
</UFormGroup>
<UFormGroup
label="Mobil:"
>
<UInput
v-model="itemInfo.phoneMobile"
/>
</UFormGroup>
<UFormGroup
label="Festnetz:"
>
<UInput
v-model="itemInfo.phoneHome"
/>
</UFormGroup>
<UFormGroup
label="Rolle:"
>
<UInput
v-model="itemInfo.role"
/>
</UFormGroup>
<template #footer>
<UButton
v-if="mode == 'edit'"
@click="updateCustomer"
>
Speichern
</UButton>
<UButton
v-else-if="mode == 'create'"
@click="createItem"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
>
Abbrechen
</UButton>
</template>
</UCard>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,90 @@
<template>
<div id="main">
<!-- TODO: Kontakte erstellen und dem Kunden zuweisen -->
<InputGroup>
<UButton @click="router.push(`/contacts/create/`)">+ Kontakt</UButton>
<UInput
v-model="searchString"
placeholder="Suche..."
/>
</InputGroup>
<UTable
:rows="filteredRows"
:columns="itemColumns"
@select="selectItem"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }"
>
<template #customer-data="{row}">
{{customers.find(customer => customer.id === row.customer) ? customers.find(customer => customer.id === row.customer).name : ''}}
</template>
<template #vendor-data="{row}">
{{vendors.find(vendor => vendor.id === row.vendor) ? vendors.find(vendor => vendor.id === row.vendor).name : ''}}
</template>
</UTable>
</div>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
const router = useRouter()
const {contacts,customers,vendors} = storeToRefs(useDataStore())
const mode = ref("show")
const itemColumns = [
{
key: "fullName",
label: "Name",
sortable: true
},
{
key: "customer",
label: "Kunde",
sortable: true
},
{
key: "vendor",
label: "Lieferant",
sortable: true
},
{
key: "role",
label: "Rolle",
}
]
const selectItem = (item) => {
router.push(`/contacts/show/${item.id} `)
}
const searchString = ref('')
const filteredRows = computed(() => {
if(!searchString.value) {
return contacts.value
}
return contacts.value.filter(item => {
return Object.values(item).some((value) => {
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
})
})
})
</script>
<style scoped>
</style>

View File

@@ -119,7 +119,9 @@ setupPage()
Beschreibung:<br>
{{currentContract.description}}<br>
<DevOnly>
{{currentContract}}
</DevOnly>
<template #footer>

View File

@@ -11,8 +11,8 @@ const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
//Store
const {customers } = storeToRefs(useDataStore())
const {fetchCustomers, getCustomerById} = useDataStore()
const {customers, contacts } = storeToRefs(useDataStore())
const {fetchCustomers, getCustomerById, getContactsByCustomerId} = useDataStore()
let currentCustomer = null
@@ -130,6 +130,24 @@ setupPage()
Notizen:<br>
{{currentCustomer.notes}}<br>
<UDivider
class="my-2"
/>
Kontakte: <br>
<table>
<tr>
<th>Anrede</th>
<th>Name</th>
<th>Rolle</th>
</tr>
<tr v-for="contact in getContactsByCustomerId(currentCustomer.id)">
<td>{{contact.salutation}}</td>
<td>{{contact.fullName}}</td>
<td>{{contact.role}}</td>
</tr>
</table>
<!-- Kontakte:<br>
&lt;!&ndash; <ul>
<li v-for="contact in currentCustomer.contacts.data">{{contact.lastName}}, {{contact.firstName}}</li>
@@ -279,5 +297,7 @@ setupPage()
</template>
<style scoped>
td {
padding: 0.2em;
}
</style>

View File

@@ -174,7 +174,11 @@ setupPage()
value-attribute="id"
searchable
:search-attributes="['name']"
/>
>
<template #label>
{{customers.find(customer => customer.id === itemInfo.customer) ? customers.find(customer => customer.id === itemInfo.customer).name : "Kunde auswählen"}}
</template>
</USelectMenu>
</UFormGroup>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
definePageMeta({
middleware: "auth"
})
const {profiles} = storeToRefs(useDataStore())
</script>
<template>
{{profiles}}
</template>
<style scoped>
</style>

View File

@@ -12,7 +12,7 @@ const supabase = useSupabaseClient()
const user = useSupabaseUser()
const toast = useToast()
const {times, projects, profiles} = storeToRefs(useDataStore())
const {times, projects, profiles, jobs} = storeToRefs(useDataStore())
const {fetchTimes, getTimeTypes} = useDataStore()
const timeTypes = getTimeTypes
const timeInfo = ref({
@@ -71,6 +71,10 @@ const columns = [
key: "projectId",
label: "Projekt"
},
{
key: "job",
label: "Job"
},
{
key: "notes",
label: "Notizen"
@@ -218,6 +222,21 @@ const selectStartedTime = () => {
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Job:"
>
<USelectMenu
:options="jobs"
option-attribute="title"
value-attribute="id"
v-model="runningTimeInfo.job"
>
<template #label>
{{ jobs.find(job => job.id === runningTimeInfo.job) ? jobs.find(job => job.id === runningTimeInfo.job).title : "Job auswählen" }}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Kategorie:"
>
@@ -307,6 +326,20 @@ const selectStartedTime = () => {
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Job:"
>
<USelectMenu
:options="jobs"
option-attribute="title"
value-attribute="id"
v-model="createTimeInfo.job"
>
<template #label>
{{ jobs.find(job => job.id === runningTimeInfo.job) ? jobs.find(job => job.id === runningTimeInfo.job).title : "Job auswählen" }}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Typ:"
>
@@ -361,6 +394,9 @@ const selectStartedTime = () => {
<template #projectId-data="{row}">
{{projects.find(project => project.id === row.projectId) ? projects.find(project => project.id === row.projectId).name : ""}}
</template>
<template #job-data="{row}">
{{jobs.find(job => job.id === row.job) ? jobs.find(job => job.id === row.job).title : ""}}
</template>
</UTable>
</template>

View File

@@ -0,0 +1,223 @@
<script setup>
definePageMeta({
middleware: "auth"
})
//
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
//Store
const {vehicles, profiles } = storeToRefs(useDataStore())
const {fetchVehicles, getVehicleById} = useDataStore()
let currentItem = null
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
id: 0,
name: "",
licensePlate: "",
type: "",
driver: ""
})
//Functions
const setupPage = () => {
if(mode.value === "show" || mode.value === "edit"){
currentItem = getVehicleById(Number(useRoute().params.id))
}
if(mode.value === "edit") itemInfo.value = currentItem
}
const createItem = async () => {
const {data,error} = await supabase
.from("vehicles")
.insert([itemInfo.value])
.select()
if(error) {
console.log(error)
} else {
mode.value = "show"
itemInfo.value = {
id: 0,
name: ""
}
toast.add({title: "Fahrzeug erfolgreich erstellt"})
await fetchVehicles()
router.push(`/vehicles/show/${data[0].id}`)
setupPage()
}
}
const editCustomer = async () => {
router.push(`/vehicles/edit/${currentItem.id}`)
setupPage()
}
const cancelEditorCreate = () => {
mode.value = "show"
itemInfo.value = {
id: 0,
name: "",
licensePlate: "",
type: ""
}
}
const updateCustomer = async () => {
const {error} = await supabase
.from("vehicles")
.update(itemInfo.value)
.eq('id',itemInfo.value.id)
if(error) {
console.log(error)
} else {
mode.value = "show"
itemInfo.value = {
id: 0,
name: "",
licensePlate: "",
type: ""
}
toast.add({title: "Fahrzeug erfolgreich gespeichert"})
fetchVehicles()
}
}
setupPage()
</script>
<template>
<div>
<UCard v-if="currentItem && mode == 'show'" >
<template #header>
<UBadge
v-if="currentItem.active"
>
Fahrzeug aktiv
</UBadge>
<UBadge
v-else
color="red"
>
Fahrzeug gesperrt
</UBadge>
{{currentItem.licensePlate}}
</template>
Typ: {{currentItem.type}} <br>
Fahrer: {{profiles.find(profile => profile.id === currentItem.driver) ? profiles.find(profile => profile.id === currentItem.driver).fullName : 'Kein Fahrer gewählt'}} <br>
<template #footer>
<UButton
v-if="mode == 'show' && currentItem.id"
@click="editCustomer"
>
Bearbeiten
</UButton>
<UButton
color="red"
class="ml-2"
disabled
>
Archivieren
</UButton>
<!-- TODO: Fahrzeug archivieren -->
</template>
</UCard>
<UCard v-else-if="mode == 'edit' || mode == 'create'" >
<template #header>
{{itemInfo.licensePlate}}
</template>
<UFormGroup
label="Kennzeichen:"
>
<UInput
v-model="itemInfo.licensePlate"
/>
</UFormGroup>
<UFormGroup
label="Fahrzeug aktiv:"
>
<UCheckbox
v-model="itemInfo.active"
/>
</UFormGroup>
<UFormGroup
label="Typ:"
>
<UTextarea
v-model="itemInfo.type"
/>
</UFormGroup>
<UFormGroup
label="Fahrer:"
>
<USelectMenu
v-model="itemInfo.driver"
:options="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>
<template #footer>
<UButton
v-if="mode == 'edit'"
@click="updateCustomer"
>
Speichern
</UButton>
<UButton
v-else-if="mode == 'create'"
@click="createItem"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
>
Abbrechen
</UButton>
</template>
</UCard>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div id="main">
<div class="flex items-center gap-1">
<UButton @click="router.push(`/vehicles/create/`)">+ Fahrzeug</UButton>
<UInput
v-model="searchString"
placeholder="Suche..."
/>
</div>
<UTable
:rows="filteredRows"
:columns="itemColumns"
@select="selectItem"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }"
>
</UTable>
</div>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
const router = useRouter()
const {vehicles } = storeToRefs(useDataStore())
const mode = ref("show")
const itemColumns = [
{
key: 'licensePlate',
label: "Kennzeichen:",
sortable: true
},
{
key: "type",
label: "Typ:",
sortable: true
}
]
const selectItem = (item) => {
router.push(`/vehicles/show/${item.id} `)
}
const searchString = ref('')
const filteredRows = computed(() => {
if(!searchString.value) {
return vehicles.value
}
return vehicles.value.filter(item => {
return Object.values(item).some((value) => {
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
})
})
})
</script>
<style scoped>
</style>

View File

@@ -9,146 +9,166 @@
<div
class="previewDoc"
>
<!-- <embed
:src="fileurl + '#toolbar=0&navpanes=0&scrollbar=0&statusbar=0&messages=0&scrollbar=0'"
width="40vw"
height="50vh"
>-->
<embed
v-if="currentDocument"
:src="currentDocument.url + '#toolbar=0&navpanes=0&scrollbar=0&statusbar=0&messages=0&scrollbar=0'"
>
</div>
<div
class="inputData"
>
<UFormGroup label="Lieferant:" required>
<USelectMenu
v-model="invoice.vendor"
v-model="itemInfo.vendor"
:options="vendors"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name','vendorNumber']"
>
<template #label>
{{vendors.find(vendor => vendor.id === itemInfo.vendor) ? vendors.find(vendor => vendor.id === itemInfo.vendor).name : 'Lieferant auswählen'}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup label="Rechnungsreferenz:" required>
<UFormGroup
class="mt-3"
label="Rechnungsreferenz:"
required
>
<UInput
v-model="invoice.reference"
v-model="itemInfo.reference"
/>
</UFormGroup>
<UFormGroup label="Rechnungsdatum:" required>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton icon="i-heroicons-calendar-days-20-solid" :label="labelDate" />
<InputGroup class="mt-3" gap="2">
<UFormGroup label="Rechnungsdatum:" required>
<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'" />
<template #panel="{ close }">
<LazyDatePicker v-model="invoice.date" @close="close" />
</template>
</UPopover>
</UFormGroup>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.date" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup label="Fälligkeitsdatum:" required>
<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'" />
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.dueDate" @close="close" />
</template>
</UPopover>
</UFormGroup>
</InputGroup>
<UFormGroup label="Fälligkeitsdatum:" required>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton icon="i-heroicons-calendar-days-20-solid" :label="labelDueDate" />
<template #panel="{ close }">
<LazyDatePicker v-model="invoice.dueDate" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup label="Beschreibung:" required>
<UTextarea
v-model="invoice.description"
v-model="itemInfo.description"
/>
</UFormGroup>
<!-- <UButton @click="vendorInvoiceData.lineItems.push({})">+ Reihe</UButton>-->
<UFormGroup label="Betrag:" required>
<UInput
type="number"
step="0.01"
v-model="itemInfo.amount"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</UFormGroup>
<InputGroup class="mt-3">
<UButton
@click="updateItem"
>
Speichern
</UButton>
</InputGroup>
<DevOnly>
{{itemInfo}}<br>
{{currentVendorInvoice}}<br>
{{currentDocument}}
</DevOnly>
<!-- <div v-for="lineItem in vendorInvoiceData.lineItems" class="lineItemRow">
<UFormGroup label="Text:" required>
<UInput v-model="lineItem.text"/>
</UFormGroup>
<UFormGroup label="Produkt:" required>
<UInput v-model="lineItem.productId"/>
</UFormGroup>
<UFormGroup label="Projekt:" required>
<UInput v-model="lineItem.projectId"/>
</UFormGroup>
<UFormGroup label="Anzahl:" required>
<UInput v-model="lineItem.quantity"/>
</UFormGroup>
<UFormGroup label="Einheit:" required>
<UInput v-model="lineItem.unit"/>
</UFormGroup>
<UFormGroup label="Einzelpreis:" required>
<UInput v-model="lineItem.unitPriceNet"/>
</UFormGroup>
<UFormGroup label="USt:" required>
<UInput v-model="lineItem.vat"/>
</UFormGroup>
<UFormGroup label="Rabatt:" required>
<UInput v-model="lineItem.discount"/>
</UFormGroup>
<UFormGroup label="Buchungskonto:" required>
<UInput v-model="lineItem.skrAccountId"/>
</UFormGroup>
<UFormGroup label="Positionspreis:" required>
<UInput disabled/>
</UFormGroup>
</div>-->
{{vendorInvoiceData}}<br>
{{currentVendorInvoice}}<br>
{{getDocumentById(78)}}
</div>
</div>
</template>
<script setup>
import InputGroup from "~/components/InputGroup.vue";
import * as dayjs from "dayjs";
const supabase = useSupabaseClient()
const route = useRoute()
const toast = useToast()
const {vendors} = storeToRefs(useDataStore())
const {getVendorInvoiceById, getDocumentById} = useDataStore()
const {getVendorInvoiceById, getDocumentById, fetchVendorInvoices} = useDataStore()
let currentVendorInvoice = null
let currentDocument = ref(null)
//Working
const mode = ref(route.params.mode || "show")
const invoice = ref({
vendor: 0,
reference: "",
date: new Date(),
dueDate: new Date(),
paymentType: "",
description: "",
//Functions
const setupPage = async () => {
if(mode.value === "show" || mode.value === "edit"){
currentVendorInvoice = await getVendorInvoiceById(Number(useRoute().params.id))
currentDocument.value = await getDocumentById(currentVendorInvoice.document)
}
if(mode.value === "edit") itemInfo.value = currentVendorInvoice
}
const itemInfo = ref({
vendor: 0,
reference: "",
date: null,
dueDate: null,
paymentType: "",
description: "",
state: "Entwurf"
})
const labelDate = computed(() => invoice.value.date.toLocaleDateString('de-de', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric' }))
const labelDueDate = computed(() => invoice.value.dueDate.toLocaleDateString('de-de', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric' }))
const currentVendorInvoice = ref(getVendorInvoiceById(Number(route.params.id)))
console.log(currentVendorInvoice)
const currentDocument = getDocumentById(Number(currentVendorInvoice.document))
console.log(currentDocument)
//console.log(document)
//let fileurl = (await supabase.storage.from('documents').createSignedUrl(document.path,60*60)).data.signedUrl
let vendorInvoiceData = ref({
reference: "",
date: "",
vendorId: 0,
lineItems: []
})
const updateItem = async () => {
const {error} = await supabase
.from("vendorInvoices")
.update(itemInfo.value)
.eq('id',itemInfo.value.id)
if(error) {
console.log(error)
} else {
mode.value = "show"
/*itemInfo.value = {
id: 0,
}*/
toast.add({title: "Eingangsrechnung erfolgreich gespeichert"})
fetchVendorInvoices()
}
}
setupPage()
</script>
<style scoped>
@@ -158,7 +178,7 @@ let vendorInvoiceData = ref({
}
.previewDoc {
width: 50vw;
min-width: 50vw;
min-height: 80vh;
}
@@ -168,7 +188,7 @@ let vendorInvoiceData = ref({
}
.inputData {
max-width: 40vw;
}

View File

@@ -17,7 +17,34 @@
:columns="itemColumns"
@select="selectItem"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }"
/>
>
<template #state-data="{row}">
<span
v-if="row.state === 'Entwurf'"
class="text-cyan-500"
>
{{row.state}}
</span>
<span
v-if="row.state === 'Bezahlt'"
class="text-primary-500"
>
{{row.state}}
</span>
</template>
<template #vendor-data="{row}">
{{vendors.find(vendor => vendor.id === row.vendor) ? vendors.find(vendor => vendor.id === row.vendor).name : ''}}
</template>
<template #date-data="{row}">
{{row.date ? dayjs(row.date).format("DD.MM.YY") : ''}}
</template>
<template #dueDate-data="{row}">
{{row.dueDate ? dayjs(row.dueDate).format("DD.MM.YY") : ''}}
</template>
<template #amount-data="{row}">
{{row.amount ? row.amount.toFixed(2) + ' €' : ''}}
</template>
</UTable>
@@ -25,6 +52,7 @@
</template>
<script setup>
import * as dayjs from "dayjs";
definePageMeta({
middleware: "auth"
@@ -32,15 +60,10 @@ definePageMeta({
const router = useRouter()
const {vendorInvoices } = storeToRefs(useDataStore())
const {vendorInvoices, vendors} = storeToRefs(useDataStore())
const mode = ref("show")
const itemColumns = [
{
key: 'id',
label: "Id",
sortable: true
},
{
key: 'state',
label: "Status.",
@@ -48,7 +71,7 @@ const itemColumns = [
},
{
key: 'vendor',
label: "Lieferantennr.",
label: "Lieferant",
sortable: true
},
{
@@ -61,12 +84,22 @@ const itemColumns = [
label: "Datum",
sortable: true
},
{
key: "dueDate",
label: "Fällig:",
sortable: true
},
{
key: "amount",
label: "Betrag",
sortable: true
},
]
const selectItem = (item) => {
console.log(item)
router.push(`/vendorinvoices/show/${item.id} `)
router.push(`/vendorinvoices/edit/${item.id} `)
}

View File

@@ -38,10 +38,13 @@ export const useDataStore = defineStore('data', {
contacts: [] as any[],
vehicles: [] as any[],
vendors: [] as any[],
vendorInvoices: [] as any[]
vendorInvoices: [] as any[],
bankAccounts: [] as any[],
bankStatements: [] as any[]
}),
actions: {
async fetchData() {
this.fetchDocuments()
await this.fetchOwnTenant()
await this.fetchProfiles()
await this.fetchEvents()
@@ -54,6 +57,7 @@ export const useDataStore = defineStore('data', {
await this.fetchCustomers()
await this.fetchContracts()
await this.fetchContacts()
await this.fetchForms()
await this.fetchFormSubmits()
await this.fetchProducts()
@@ -64,7 +68,36 @@ export const useDataStore = defineStore('data', {
await this.fetchVehicles()
await this.fetchVendors()
await this.fetchVendorInvoices()
await this.fetchBankAccounts()
await this.fetchBankStatements()
},
async clearStore() {
console.log("Clear")
this.loaded = false
this.ownTenant = {}
this.profiles= []
this.events= []
this.customers= []
this.tasks= []
this.projects= []
this.documents= []
this.spaces= []
this.units= []
this.times= []
this.products= []
this.movements= []
this.forms= []
this.contracts= []
this.jobs= []
this.formSubmits= []
this.contacts= []
this.vehicles= []
this.vendors= []
this.vendorInvoices= []
this.bankAccounts= []
this.bankStatements= []
},
async fetchOwnTenant() {
//TODO: Tenant ID Dynamisch machen
@@ -75,6 +108,14 @@ export const useDataStore = defineStore('data', {
// @ts-ignore
this.profiles = (await supabase.from("profiles").select()).data
},
async fetchBankAccounts() {
// @ts-ignore
this.bankAccounts = (await supabase.from("bankAccounts").select()).data
},
async fetchBankStatements() {
// @ts-ignore
this.bankStatements = (await supabase.from("bankStatements").select()).data
},
async fetchEvents() {
// @ts-ignore
this.events = (await supabase.from("events").select()).data
@@ -83,6 +124,10 @@ export const useDataStore = defineStore('data', {
// @ts-ignore
this.contracts = (await supabase.from("contracts").select()).data
},
async fetchContacts() {
// @ts-ignore
this.contacts = (await supabase.from("contacts").select()).data
},
async fetchCustomers() {
// @ts-ignore
this.customers = (await supabase.from("customers").select().order('customerNumber', {ascending: true})).data
@@ -167,6 +212,8 @@ export const useDataStore = defineStore('data', {
getVendorById: (state) => (itemId:number) => state.vendors.find(item => item.id === itemId),
getVendorInvoiceById: (state) => (itemId:number) => state.vendorInvoices.find(item => item.id === itemId),
getContractById: (state) => (itemId:number) => state.contracts.find(item => item.id === itemId),
getContactById: (state) => (itemId:number) => state.contacts.find(item => item.id === itemId),
getVehicleById: (state) => (itemId:number) => state.vehicles.find(item => item.id === itemId),
getDocumentById: (state) => (itemId:number) => state.documents.find(item => item.id === itemId),
getSpaceById: (state) => (itemId:number) => state.spaces.find(item => item.id === itemId),
getProjectById: (state) => (projectId:number) => {
@@ -184,6 +231,7 @@ export const useDataStore = defineStore('data', {
},
getCustomerById: (state) => (customerId:number) => state.customers.find(customer => customer.id === customerId),
getJobById: (state) => (jobId:number) => state.jobs.find(job => job.id === jobId),
getContactsByCustomerId: (state) => (customerId) => state.contacts.filter(contact => contact.customer === customerId),
getTimesByProjectId: (state) => (projectId:number) => {
let times = state.times.filter(time => time.projectId === projectId)
console.log(times.length)

168
test/bankImport/index.mjs Normal file
View File

@@ -0,0 +1,168 @@
import axios from "axios"
import {createClient} from "@supabase/supabase-js";
let key = process.env.SUPABASE_KEY
let url = process.env.SUPABASE_URL
let interval = process.env.INTERVAL
let goCardLessBaseUrl = process.env.GOCARDLESS_URL
let goCardLessSecretId = process.env.GOCARDLESS_SECRET_ID
let goCardLessSecretKey = process.env.GOCARDLESS_SECRET_KEY
const supabase = createClient(url,key)
let accessToken = ""
let refreshToken = ""
console.log(goCardLessBaseUrl)
let currentTenant = 1
let tenantData = (await supabase.from("tenants").select().eq("id", currentTenant)).data[0]
//console.log(tenantData)
let bankConfig = tenantData.bankConfig
//console.log(bankConfig)
//console.log(goCardLessBaseUrl + "token/new/")
let tokenData = (await axios({
url: goCardLessBaseUrl + "token/new/",
method: "POST",
data: {
"secret_id": goCardLessSecretId,
"secret_key": goCardLessSecretKey
}
})).data
accessToken = tokenData.access
refreshToken = tokenData.access
let accounts = []
const getAccounts = async () => {
let existingAccounts = (await supabase.from("bankAccounts").select()).data
let requisitionData = (await axios({
url: goCardLessBaseUrl + "requisitions/" + bankConfig.requisitions[0],
headers: {
Authorization: `Bearer ${accessToken}`
},
method: "GET",
})).data
requisitionData.accounts.forEach(account => {
if (!accounts.includes(account)) {
accounts.push(account)
}
})
let supabaseAccountInserts = []
async function getAccountData () {
await Promise.all(accounts.map(async (account) => {
let accountData = (await axios({
url: goCardLessBaseUrl + "accounts/" + account,
headers: {
Authorization: `Bearer ${accessToken}`
},
method: "GET"
})).data
//console.log(accountData)
if(existingAccounts.filter(account => account.iban === accountData.iban).length === 0){
supabaseAccountInserts.push({
name: "",
iban: accountData.iban,
bankId: accountData.institution_id,
ownerName: accountData.owner_name,
tenant: currentTenant,
accountId: accountData.id
})
}
}));
}
await getAccountData()
//console.log("Start Insert")
const {data:accountInsertData,error:accountInsertError} = await supabase.from("bankAccounts").insert(supabaseAccountInserts).select()
if(accountInsertError) {
console.log(accountInsertError)
} else if(accountInsertData) {
//console.log(accountInsertData)
}
}
const getTransactions = async () => {
let accountsToRequest = (await supabase.from("bankAccounts").select().eq("used", true)).data
let existingTransactions = (await supabase.from("bankStatements").select().eq("tenant", currentTenant)).data
console.log(accountsToRequest)
let supabaseTransactionInserts = []
async function getAccountTransactions () {
await Promise.all(accountsToRequest.map(async (account) => {
let transactions = (await axios({
url: goCardLessBaseUrl + "accounts/" + account.accountId + "/transactions",
headers: {
Authorization: `Bearer ${accessToken}`
},
method: "GET"
})).data.transactions.booked
//console.log(transactions)
transactions.forEach(transaction => {
if(!transaction.creditorAccount) console.log(transaction)
let transactionData = {
date: transaction.bookingDate,
amount: transaction.transactionAmount.amount,
credName: transaction.creditorName ? transaction.creditorName : null,
credIban: transaction.creditorAccount ? transaction.creditorAccount.iban : null,
debName: transaction.debtorName,
debIban: transaction.debtorAccount.iban,
text: transaction.remittanceInformationUnstructured,
gocardlessId: transaction.internalTransactionId,
currency: transaction.transactionAmount.currency,
account: account.id,
tenant: currentTenant
}
if(existingTransactions.filter(item => item.gocardlessId === transaction.internalTransactionId).length === 0){
supabaseTransactionInserts.push(transactionData)
}
})
}));
}
await getAccountTransactions()
console.log(supabaseTransactionInserts)
const {data:transactionInsertData, error:transactionInsertError} = await supabase.from("bankStatements").insert(supabaseTransactionInserts).select()
if(transactionInsertError) {
console.log(transactionInsertError)
} else if(transactionInsertData) {
console.log(transactionInsertData)
}
}
getAccounts()
getTransactions()