From 7996c746c3c61f79b2b1f1bb2baa0263111e0d53 Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Fri, 27 Mar 2026 21:41:20 +0100 Subject: [PATCH] Add External Link Fix Plantafel --- .../migrations/0027_product_supplier_link.sql | 2 + backend/db/migrations/meta/_journal.json | 7 + backend/db/schema/products.ts | 1 + .../columnRenderings/externalLink.vue | 48 ++++ frontend/pages/createDocument/edit/[[id]].vue | 74 ++++-- frontend/pages/organisation/plantafel.vue | 213 ++++++++++++++++-- frontend/stores/data.js | 7 + 7 files changed, 313 insertions(+), 39 deletions(-) create mode 100644 backend/db/migrations/0027_product_supplier_link.sql create mode 100644 frontend/components/columnRenderings/externalLink.vue diff --git a/backend/db/migrations/0027_product_supplier_link.sql b/backend/db/migrations/0027_product_supplier_link.sql new file mode 100644 index 0000000..064628b --- /dev/null +++ b/backend/db/migrations/0027_product_supplier_link.sql @@ -0,0 +1,2 @@ +ALTER TABLE "products" +ADD COLUMN "supplierLink" text; diff --git a/backend/db/migrations/meta/_journal.json b/backend/db/migrations/meta/_journal.json index 6e7e0a9..1b1c5d9 100644 --- a/backend/db/migrations/meta/_journal.json +++ b/backend/db/migrations/meta/_journal.json @@ -190,6 +190,13 @@ "when": 1774393202000, "tag": "0026_statementallocation_depreciation_method", "breakpoints": true + }, + { + "idx": 27, + "version": "7", + "when": 1774602000000, + "tag": "0027_product_supplier_link", + "breakpoints": true } ] } diff --git a/backend/db/schema/products.ts b/backend/db/schema/products.ts index d070254..ffce4a5 100644 --- a/backend/db/schema/products.ts +++ b/backend/db/schema/products.ts @@ -50,6 +50,7 @@ export const products = pgTable("products", { vendor_allocation: jsonb("vendorAllocation").default([]), article_number: text("articleNumber"), + supplier_link: text("supplierLink"), barcodes: text("barcodes").array().notNull().default([]), diff --git a/frontend/components/columnRenderings/externalLink.vue b/frontend/components/columnRenderings/externalLink.vue new file mode 100644 index 0000000..8d6be39 --- /dev/null +++ b/frontend/components/columnRenderings/externalLink.vue @@ -0,0 +1,48 @@ + + + diff --git a/frontend/pages/createDocument/edit/[[id]].vue b/frontend/pages/createDocument/edit/[[id]].vue index 3f941b1..e12d4dc 100644 --- a/frontend/pages/createDocument/edit/[[id]].vue +++ b/frontend/pages/createDocument/edit/[[id]].vue @@ -51,6 +51,7 @@ const itemInfo = ref({ dateOfPerformance: null, paymentDays: auth.activeTenantData.standardPaymentDays, payment_type: "transfer", + availableInPortal: false, customSurchargePercentage: 0, created_by: auth.user.id, title: null, @@ -111,6 +112,18 @@ const serialIntervalItems = ['wöchentlich', '2 - wöchentlich', 'monatlich', 'v const serialDateDirectionItems = ['Rückwirkend', 'Im Voraus'] const taxPercentItems = [19, 7, 0] const selectedCustomer = computed(() => customers.value.find(i => i.id === itemInfo.value.customer) || null) +const normalizeExternalUrl = (value) => { + if (!value || typeof value !== "string") return null + + const trimmedValue = value.trim() + if (!trimmedValue) return null + + if (/^https?:\/\//i.test(trimmedValue)) return trimmedValue + + return `https://${trimmedValue}` +} + +const getSelectedProduct = (row) => products.value.find((item) => item.id === row.product) || null const formatNumberLikeValue = (value) => { if (value === null || typeof value === 'undefined' || value === '') return '-' if (typeof value === 'string' || typeof value === 'number') return String(value) @@ -1438,7 +1451,8 @@ const saveSerialInvoice = async () => { contactPerson: itemInfo.value.contactPerson, serialConfig: itemInfo.value.serialConfig, letterhead: itemInfo.value.letterhead, - taxType:itemInfo.value.taxType + taxType:itemInfo.value.taxType, + availableInPortal: itemInfo.value.availableInPortal } let data = null @@ -1527,6 +1541,7 @@ const saveDocument = async (state, resetup = false) => { agriculture: itemInfo.value.agriculture, letterhead: itemInfo.value.letterhead, usedAdvanceInvoices: itemInfo.value.usedAdvanceInvoices, + availableInPortal: itemInfo.value.availableInPortal, customSurchargePercentage: itemInfo.value.customSurchargePercentage, report: documentReport.value } @@ -2234,6 +2249,12 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = { class="w-full" /> + + + @@ -2589,24 +2610,39 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = { v-else-if="row.mode === 'normal'" > - - - + { absenceForm[field] = $dayjs().format("YYYY-MM-DD") } +const startDateValue = computed({ + get: () => { + if (!absenceForm.startDate) return null + + try { + return parseDate(absenceForm.startDate) + } catch { + return null + } + }, + set: (value) => { + absenceForm.startDate = value ? value.toString() : "" + } +}) + +const endDateValue = computed({ + get: () => { + if (!absenceForm.endDate) return null + + try { + return parseDate(absenceForm.endDate) + } catch { + return null + } + }, + set: (value) => { + absenceForm.endDate = value ? value.toString() : "" + } +}) + const resourceTypeOptions = [ { label: "Alle Ressourcen", value: "all" }, { label: "Profile", value: "Profile" }, { label: "Inventarartikel", value: "Inventarartikel" } ] +const calendarViewOptions = [ + { label: "Tag", value: "resourceTimelineDay" }, + { label: "Woche", value: "resourceTimelineWeek" }, + { label: "Monat", value: "resourceTimelineMonth" } +] + const absenceTypeOptions = [ { label: "Urlaub", value: "vacation" }, { label: "Krank", value: "sick" } ] +const calendarPickerValue = computed({ + get: () => { + if (!calendarCurrentDate.value) return null + + try { + return parseDate(calendarCurrentDate.value) + } catch { + return null + } + }, + set: (value) => { + calendarCurrentDate.value = value ? value.toString() : "" + if (value) { + const api = calendarRef.value?.getApi?.() + api?.gotoDate(value.toString()) + } + } +}) + const profileOptions = computed(() => profiles.value .filter((profile) => !profile.archived && profile.user_id) @@ -83,12 +143,9 @@ const calendarOptions = computed(() => ({ schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives", locale: deLocale, plugins: [resourceTimelinePlugin, interactionPlugin], - initialView: "resourceTimelineWeek", - headerToolbar: { - left: "prev,next today", - center: "title", - right: "resourceTimelineDay,resourceTimelineWeek,resourceTimelineMonth" - }, + initialView: calendarView.value, + initialDate: calendarCurrentDate.value, + headerToolbar: false, resourceAreaWidth: "280px", resourceGroupField: "type", resourceOrder: "type,title", @@ -149,6 +206,10 @@ const calendarOptions = computed(() => ({ const nextTo = $dayjs(info.end).subtract(1, "day").format("YYYY-MM-DD") const nextKey = `${nextFrom}:${nextTo}` + calendarView.value = info.view.type + calendarCurrentDate.value = $dayjs(info.view.currentStart).format("YYYY-MM-DD") + calendarTitle.value = info.view.title + if (nextKey === lastRangeKey.value) return lastRangeKey.value = nextKey @@ -264,6 +325,31 @@ function resetAbsenceForm() { absenceForm.description = "" } +function getCalendarApi() { + return calendarRef.value?.getApi?.() +} + +function changeCalendarView(view) { + const api = getCalendarApi() + if (!api || !view) return + calendarView.value = view + api.changeView(view) +} + +function moveCalendar(direction) { + const api = getCalendarApi() + if (!api) return + + if (direction === "prev") api.prev() + if (direction === "next") api.next() +} + +function moveCalendarToday() { + const api = getCalendarApi() + if (!api) return + api.today() +} + function openAbsenceModal(type = "vacation", preset = {}) { absenceForm.mode = preset.entry ? "edit" : "create" absenceForm.entry = preset.entry || null @@ -404,12 +490,49 @@ onMounted(() => {
+ + + + {{ calendarCurrentDate ? $dayjs(calendarCurrentDate).format("DD.MM.YYYY") : "Datum wählen" }} + + + + +
+ + +
+ + {{ calendarTitle }} +