new update

This commit is contained in:
Luca Haid
2025-04-24 13:39:26 +02:00
parent adabe9a7a2
commit 4108eb99c9
22 changed files with 992 additions and 337 deletions
@@ -1,5 +1,38 @@
.warehouse-article-prices > div {
/* style.css */
.warehouse-article-prices > div,
.warehouse-article-distributor > div {
display: grid;
grid-template-columns: repeat(4, minmax(120px, 1fr)) 72px;
gap: 10px;
grid-template-columns: repeat(3, minmax(150px, 1fr)) 120px; /* Defines 4 columns */
gap: 12px; /* Use gap for spacing */
margin-bottom: 8px; /* Add small gap between rows */
}
.warehouse-article-prices .form-group,
.warehouse-article-distributor .form-group {
margin-bottom: 0; /* Keep inputs tight in grid cells */
}
@media (max-width: 992px) {
.warehouse-article-prices > div,
.warehouse-article-distributor > div {
display: block; /* Stack items vertically */
border: 1px solid #eee; /* Lighter border */
padding: 10px;
margin-bottom: 10px;
}
.warehouse-article-prices > div > *,
.warehouse-article-distributor > div > * {
display: block; /* Ensure children are block */
margin-bottom: 10px !important; /* Add space between stacked items */
}
.warehouse-article-prices > div > div:last-child, /* Target button container */
.warehouse-article-distributor > div > div:last-child {
text-align: right; /* Keep buttons aligned right */
margin-bottom: 0 !important; /* Remove extra margin below buttons */
}
.warehouse-article-prices > div > div:last-child > *, /* Target buttons inside */
.warehouse-article-distributor > div > div:last-child > * {
margin-left: 5px; /* Add small space between buttons */
}
}
@@ -1,97 +1,175 @@
async function handleApiResponse(responsePromise) {
const res = await responsePromise;
if (res.data.success === false) return window.notify('error', `Fehler: ${res.data.errors.join(', ')}`);
window.notify('success', res.data.message || 'Erfolgreich');
}
Vue.component('warehouse-article-prices', {
props: {id: {type: Number, required: true}},
template: `
<div>
<tt-card>
<h4>Artikelpreise</h4>
<div class="warehouse-article-prices">
<div v-for="(price, index) in articlePrices" :key="index" class="article-price">
<div class="article-price-type">
<span v-if="price.isRobot" class="robot-icon"><i class="fa-solid fa-robot"></i></span>
{{ index }}
</div>
<tt-input sm v-model="price.priceMultiplier" label="Preisfaktor"/>
<tt-input sm v-model="price.priceOverride" label="Preisoverride"/>
<div>
<tt-button sm icon="fa-solid fa-trash" class="remove-price" additional-class="btn-danger"></tt-button>
<tt-button sm icon="fa-solid fa-save" class="save-price" additional-class="btn-primary"></tt-button>
</div>
<tt-card>
<h4 style="text-align: center">Artikelpreise überschreiben</h4>
<div class="warehouse-article-prices">
<div v-for="(price, typeTitle) in articlePrices" :key="typeTitle">
<div style="align-self: center;">
<i v-if="price.isRobot" class="fa-solid fa-robot"></i> {{ typeTitle }}
</div>
<tt-input sm v-model="price.priceMultiplier" label="Preisfaktor" @input="price.priceOverride = null"/>
<tt-input sm v-model="price.priceOverride" label="Preis" @input="price.priceMultiplier = null"/>
<div style="align-self: end;">
<tt-button sm icon="fa-solid fa-save" additional-class="btn-primary" @click="savePrice(price)"/>
<tt-button sm icon="fa-solid fa-trash" additional-class="btn-danger" v-if="!price.isRobot"
@click="deletePrice(price)"/>
</div>
</div>
</tt-card>
</div>`,
data: () => ({
window,
articlePrices: [],
}),
</div>
</tt-card>
`,
data: () => ({window, articlePrices: {}}),
async mounted() {
const [res1, res2] = await Promise.all([
axios.post(window['TT_CONFIG']['BASE_PATH'] + '/WarehouseArticlePrice/get', {filters: {articleId: this.id}}),
axios.post(window['TT_CONFIG']['BASE_PATH'] + '/WarehouseArticlePriceType/get')
]);
const prices = {};
res2.data.rows.forEach(t => prices[t.title] = {
isRobot: true,
articlePriceTypeId: t.id,
priceMultiplier: t.defaultPriceFactor,
priceOverride: null
});
res1.data.rows.forEach(p => {
const t = res2.data.rows.find(t => t.id === p.articlePriceTypeId);
if (t) prices[t.title] = {
isRobot: false,
articlePriceTypeId: p.articlePriceTypeId,
priceMultiplier: p.priceMultiplier || t.defaultPriceFactor,
priceOverride: p.priceOverride
await this.fetchArticlePrices();
},
methods: {
async fetchArticlePrices() {
const [pricesRes, typesRes] = await Promise.all([
axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/get`, {filters: {articleId: this.id}}),
axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePriceType/get`)
]);
const prices = {};
typesRes.data.rows.forEach(type => prices[type.title] = {
isRobot: true,
articlePriceTypeId: type.id,
priceMultiplier: type.defaultPriceFactor,
priceOverride: null
});
pricesRes.data.rows.forEach(pData => {
const type = typesRes.data.rows.find(t => t.id === pData.articlePriceTypeId);
if (type) prices[type.title] = {
id: pData.id,
isRobot: false,
articlePriceTypeId: pData.articlePriceTypeId,
priceMultiplier: pData.priceMultiplier,
priceOverride: pData.priceOverride
};
});
this.articlePrices = prices;
},
async savePrice(price) {
const payload = {
articleId: this.id,
articlePriceTypeId: price.articlePriceTypeId,
priceMultiplier: price.priceMultiplier,
priceOverride: price.priceOverride
};
});
this.articlePrices = prices;
if (price.isRobot) {
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/create`, payload));
await this.fetchArticlePrices();
} else {
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/update`, {id: price.id, ...payload}));
await this.fetchArticlePrices();
}
},
async deletePrice(price) {
const payload = {id: price.id, articleId: this.id, articlePriceTypeId: price.articlePriceTypeId}
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/delete`, payload));
await this.fetchArticlePrices();
}
}
})
});
Vue.component('warehouse-article', {
//language=Vue
// warehouse-article-distributor.vue.js
Vue.component('warehouse-article-distributor', {
props: {id: {type: Number, required: true}},
template: `
<tt-card>
<h4 style="text-align: center">Lieferanten für diesen Artikel</h4>
<tt-autocomplete :api-url="window['TT_CONFIG']['BASE_PATH'] + '/WarehouseDistributor/autocomplete'"
v-model="newDistributorId" label="Neuen Lieferant hinzufügen"/>
<div class="warehouse-article-distributor">
<div v-for="(distributor, index) in articleDistributors" :key="distributor.id || ('new-' + index)">
<tt-resolver style="align-self: center;" reference="WarehouseDistributor"
:value="distributor.distributorId"></tt-resolver>
<tt-input sm v-model="distributor.externalArticleNumber" label="Externe Artikelnummer"/>
<tt-input sm v-model="distributor.purchasePrice" label="Einkaufspreis"/>
<div style="align-self: end;">
<tt-button sm icon="fa-solid fa-save" additional-class="btn-primary"
@click="saveDistributor(distributor)"/>
<tt-button sm icon="fa-solid fa-trash" additional-class="btn-danger" v-if="distributor.id"
@click="deleteDistributor(distributor)"/>
</div>
</div>
</div>
</tt-card>
`,
data: () => ({window, articleDistributors: [], newDistributorId: null}),
async mounted() {
await this.fetchArticleDistributors();
},
methods: {
async fetchArticleDistributors() {
const res = await axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/get`, {filters: {articleId: this.id}});
this.articleDistributors = res.data.rows;
},
async saveDistributor(distributor) {
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/${distributor.id ? 'update' : 'create'}`, distributor));
await this.fetchArticleDistributors();
},
async deleteDistributor(distributor) {
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/delete`, distributor));
await this.fetchArticleDistributors();
}
},
watch: {
newDistributorId(newId) {
if (newId) {
if (!this.articleDistributors.some(d => d.distributorId === newId)) {
this.articleDistributors.push({
articleId: this.id,
distributorId: newId,
externalArticleNumber: null,
purchasePrice: null
});
}
this.$nextTick(() => {
this.newDistributorId = null;
});
}
}
}
});
<tt-table-crud ref="table" @openHistory="historyModal = true; historyModalId = $event.id">
// warehouse-article.vue.js
Vue.component('warehouse-article', {
template: `
<tt-card>
<tt-table-crud ref="table" @openHistory="historyModalId = $event.id; historyModal = true">
<template v-slot:cheapestsellprice="{ row }">
<template v-for="price in JSON.parse(row.cheapestSellPrice)">
<span v-if="price && window.TT_CONFIG['WAREHOUSE_ADMIN'] == true">{{price.title}}: <span
style="white-space:nowrap;"> {{(price.price)}} €</span><br></span>
<span v-else-if="price && price.title === 'Verkauf'">{{(price.price)}} €</span>
<template v-for="price in JSON.parse(row.cheapestSellPrice || '[]')">
<span v-if="price && window.TT_CONFIG['WAREHOUSE_ADMIN']">{{ price.title }}: <span
style="white-space:nowrap;">{{ price.price }} €</span><br></span>
<span v-else-if="price && price.title === 'Verkauf'">{{ price.price }} €</span>
</template>
</template>
<template v-slot:modal-prepend="{ crudModalData }">
<warehouse-article-prices v-if="crudModalData.id" :id="crudModalData.id"/>
<warehouse-article-distributor v-if="crudModalData.id" :id="crudModalData.id"/>
</template>
</tt-table-crud>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
</tt-card>
`, data: () => ({
window,
historyModal: false,
historyModalId: null,
}),
`,
data: () => ({window, historyModal: false, historyModalId: null}),
mounted() {
const table = this.$refs.table?.$refs?.table;
if (!table) return;
const showId = new URLSearchParams(window.location.search).get('showId');
const currentFilterId = table.filters?.id;
if ((showId && currentFilterId !== showId) || (!showId && currentFilterId)) {
table.filters = showId ? {id: showId} : {};
if (showId && (!table.filters || table.filters.id !== showId)) {
table.filters = {...table.filters, id: showId};
table.refreshTable();
} else if (!showId && table.filters?.id) {
delete table.filters.id;
if (Object.keys(table.filters).length === 0) table.filters = {};
table.refreshTable();
}
}
})
});
@@ -99,7 +99,7 @@ Vue.component('warehouse-administration-switch', {
<button @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseDistributor';" :class="{ 'active': window.location.href.includes('WarehouseDistributor') }" class="btn btn-primary">Lieferanten</button>
<button @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseLocation';" :class="{ 'active': window.location.href.includes('WarehouseLocation') }" class="btn btn-primary">Lagerorte</button>
<button @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseArticlePriceType';" :class="{ 'active': window.location.href.includes('WarehouseArticlePriceType') }" class="btn btn-primary">Preistypen</button>
<button @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNoteTextElement';" :class="{ 'active': window.location.href.includes('Device') }" class="btn btn-primary">LS Texte</button>
<button @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseCategory';" :class="{ 'active': window.location.href.includes('Device') }" class="btn btn-primary">Kategorie</button>
<button @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseAdministration';" :class="{ 'active': window.location.href.includes('Device') }" class="btn btn-primary">Admin-Tools</button>
</div>
<div v-else>
@@ -110,8 +110,9 @@ Vue.component('warehouse-administration-switch', {
<a href="#" @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseDistributor'; showDropdown = false" class="dropdown-item">Lieferanten</a>
<a href="#" @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseArticlePriceType'; showDropdown = false" class="dropdown-item">Lagerorte</a>
<a href="#" @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseLocation'; showDropdown = false" class="dropdown-item">Preistypen</a>
<a href="#" @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNoteTextElement'; showDropdown = false" class="dropdown-item">LS Texte</a>
<a href="#" @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseCategory'; showDropdown = false" class="dropdown-item">Kategorie</a>
<a href="#" @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/WarehouseAdministration'; showDropdown = false" class="dropdown-item">Admin-Tools</a>
</div>
</div>
</div>
@@ -44,7 +44,7 @@ Vue.component('warehouse-offer-modal', {
</div>
<template v-slot:footer-prepend>
<template v-slot:footer-prepend v-if="window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1'">
<tt-input placeholder="Vorlagenname" no-form-group v-model="templateName"/>
<tt-button text="Als Vorlage speichern" @click="saveTemplate" icon="fas fa-save" additional-class="btn-success"/>
</template>
@@ -217,12 +217,15 @@ Vue.component('warehouse-offer', {
<div style="display: flex; gap: 8px">
<button @click="offerModalId = 'create'" class="btn btn-primary">Angebot erstellen</button>
<div class="dropdown">
<button class="btn btn-outline-primary dropdown-toggle" @click="offerTemplatesDropdown = !offerTemplatesDropdown">
<button class="btn btn-outline-primary dropdown-toggle" @click.stop="offerTemplatesDropdown = !offerTemplatesDropdown" >
Angebot aus Vorlage erstellen <i class="fas fa-caret-down"></i>
</button>
<ul class="dropdown-menu" :class="{'show': offerTemplatesDropdown}">
<li v-for="template in offerTemplates" @click="createOfferFromTemplate(template)">
<ul class="dropdown-menu" :class="{'show': offerTemplatesDropdown}" >
<li v-for="template in offerTemplates" @click="createOfferFromTemplate(template)" style="display: flex; gap: 2px;cursor: pointer;margin-bottom: 4px;margin-right: 4px">
<a class="dropdown-item">{{ template.templateName }}</a>
<tt-button
v-if="window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1'"
icon="fas fa-trash" sm additional-class="btn-danger" @click.stop="deleteTemplate(template.id)"/>
</li>
</ul>
</div>
@@ -259,6 +262,15 @@ Vue.component('warehouse-offer', {
this.$refs.modal.offer.notes = template.notes;
this.window.notify('success', 'Angebot aus Vorlage erstellt');
},
async deleteTemplate(id) {
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/deleteTemplate?id=${id}`);
if (response.data.success) {
this.offerTemplates = this.offerTemplates.filter(template => template.id !== id);
this.window.notify('success', 'Vorlage erfolgreich gelöscht');
} else {
this.window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten');
}
}
}
});
@@ -282,7 +282,6 @@ Vue.component('warehouse-order-modal', {
@updateField-distributorId="fetchDistributorData"
>
<template #form-actions-append>
<!-- v-if $refs.positionsManager.formData.article parse is int and not NaN we show a <tt-button> with a @click to BASE_PATH /WarehouseArticle?showId-->
<tt-button
v-if="!isNaN(parseInt($refs.positionsManager.formData.article))"
text="Zum Artikel"
@@ -1,36 +1,36 @@
window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [
{
"key": "status_to_progress",
"key": "status_to_progress",
"title": "In Bearbeitung",
"class": "fas fa-cog text-warning",
"condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['new', 'on_hold'].includes(row.status),
},
{
"key": "status_to_accepted",
"key": "status_to_accepted",
"title": "Akzeptieren",
"class": "fas fa-check text-success",
"condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['new', 'in_progress', 'on_hold'].includes(row.status),
},
{
"key": "status_to_invoiced",
"key": "status_to_invoiced",
"title": "Verrechnet",
"class": "fas fa-file-invoice text-info",
"condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['in_progress', 'accepted', 'on_hold'].includes(row.status),
},
{
"key": "status_to_on_hold",
"key": "status_to_on_hold",
"title": "On Hold",
"class": "fas fa-pause text-warning",
"condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['accepted', 'new', 'in_progress'].includes(row.status),
},
{
"key": "status_to_cancelled",
"key": "status_to_cancelled",
"title": "Storniert",
"class": "fas fa-ban text-danger",
"condition": (row) => (window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['new', 'in_progress', 'accepted'].includes(row.status)) || (row.status === 'new' && row.signature === null),
},
{
"key": "status_to_new",
"key": "status_to_new",
"title": "Lieferschein wiedereröffnen",
"class": "fas fa-redo-alt text-success",
"condition": (row) => (window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.status === 'cancelled'),
@@ -53,32 +53,32 @@ window.TT_CONFIG["CRUD_CONFIG"]["editCondition"] = (row) => row.status === 'new'
Vue.component('warehouse-shipping-note-positions', {
//language=Vue
props: {
positions: Array,
props: {
positions: Array,
hoursEntries: Array
}, data() {
return {
articleData: {}, loading: false, articlePacketData: {}, userData: {}
}
}, template: `
<div>
<div v-if="loading" class="text-center">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div>
<div v-if="loading" class="text-center">
<i class="fa fa-spinner fa-spin"></i>
</div>
<ul v-if="!loading">
<li v-for="position in positions">
<ul v-if="!loading">
<li v-for="position in positions">
<span>{{ position.amount }}x
{{ position.article ? articleData[position.article]?.text : position.articlePacket ? articlePacketData[position.articlePacket]?.text : position.articleText ? position.articleText : position.article_text }}</span>
</li>
<template v-for="entry in hoursEntries">
<li><span>{{ entry.hourCount }}h Arbeitszeit</span></li>
<li v-if="entry.carId">{{ entry.kilometerCount }}km Anfahrt</li>
</template>
</ul>
</div>
`, async mounted() {
</li>
<template v-for="entry in hoursEntries">
<li><span>{{ entry.hourCount }}h Arbeitszeit</span></li>
<li v-if="entry.carId">{{ entry.kilometerCount }}km Anfahrt</li>
</template>
</ul>
</div>
`, async mounted() {
this.loading = true;
for (const position of this.positions) {
if (position.article) {
@@ -105,14 +105,14 @@ Vue.component('add-log-modal-sn', {
},
data() {
return {
window: window,
note: '',
file: null,
uploadedFiles: [],
submitLoading: false
window: window,
note: '',
file: null,
uploadedFiles: [],
submitLoading: false
};
},
methods: {
methods: {
async handleFileUpload(event) {
const files = event.target.files;
if (!files.length) return;
@@ -131,7 +131,7 @@ Vue.component('add-log-modal-sn', {
if (response.data.success) {
this.uploadedFiles.push({
id: response.data.fileId,
id: response.data.fileId,
name: file.name
});
window.notify('success', `File "${file.name}" uploaded successfully`);
@@ -153,7 +153,7 @@ Vue.component('add-log-modal-sn', {
const fileIds = this.uploadedFiles.map(file => file.id);
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseShippingNote/createNewLogAction`, {
shippingNoteId: this.shippingNoteId,
note: this.note,
note: this.note,
fileIds: JSON.stringify(fileIds),
});
@@ -168,53 +168,55 @@ Vue.component('add-log-modal-sn', {
},
},
template: `
<tt-modal :show="true" @submit="submit" :delete="false" @update:show="$emit('close')" title="Logeintrag hinzufügen" :save-loading="submitLoading">
<template>
<tt-modal :show="true" @submit="submit" :delete="false" @update:show="$emit('close')"
title="Logeintrag hinzufügen" :save-loading="submitLoading">
<template>
<div class="form-group" style="margin: 10px 0">
<label>Dateiupload (Mehrere)</label>
<input type="file" class="form-control" @change="handleFileUpload" multiple/>
</div>
<div class="form-group" style="margin: 10px 0">
<label>Dateiupload (Mehrere)</label>
<input type="file" class="form-control" @change="handleFileUpload" multiple/>
</div>
<div v-if="uploadedFiles.length" class="upload-success-alert">
<div class="alert-header">
<i class="fa fa-check-circle" aria-hidden="true"></i>
<span v-if="uploadedFiles.length === 1">Datei erfolgreich hochgeladen</span>
<span v-else>Dateien erfolgreich hochgeladen</span>
</div>
<ul class="file-list">
<li v-for="(file, index) in uploadedFiles" :key="file.id" class="file-item">
<i class="fa fa-file" aria-hidden="true"></i>
<span class="file-name">{{ file.name }}</span>
<button type="button" class="remove-btn" @click="removeFile(index)">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</li>
</ul>
</div>
<div v-if="uploadedFiles.length" class="upload-success-alert">
<div class="alert-header">
<i class="fa fa-check-circle" aria-hidden="true"></i>
<span v-if="uploadedFiles.length === 1">Datei erfolgreich hochgeladen</span>
<span v-else>Dateien erfolgreich hochgeladen</span>
</div>
<ul class="file-list">
<li v-for="(file, index) in uploadedFiles" :key="file.id" class="file-item">
<i class="fa fa-file" aria-hidden="true"></i>
<span class="file-name">{{ file.name }}</span>
<button type="button" class="remove-btn" @click="removeFile(index)">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</li>
</ul>
</div>
<tt-textarea label="Bemerkung" v-model="note" sm/>
</template>
</tt-modal>
`
<tt-textarea label="Bemerkung" v-model="note" sm/>
</template>
</tt-modal>
`
})
Vue.component('tt-file', {
props: ['id'],
data: () => ({file: null}),
data: () => ({file: null}),
async mounted() {
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/File/getById`, {params: {id: this.id}});
this.file = response.data;
},
template: `
<div>
<a :href="'/File/download?id=' + id" target="_blank" v-if="file">{{ file.filename }}</a>
<template v-else>
<div class="spinner-border spinner-border-sm text-primary" role="status"><span class="sr-only">Loading...</span></div>
</template>
</div>
`
<div>
<a :href="'/File/download?id=' + id" target="_blank" v-if="file">{{ file.filename }}</a>
<template v-else>
<div class="spinner-border spinner-border-sm text-primary" role="status"><span
class="sr-only">Loading...</span></div>
</template>
</div>
`
})
@@ -229,23 +231,23 @@ Vue.component('warehouse-shipping-note-logs', {
},
//language=Vue
template: `
<div>
<div v-if="loading" class="text-center">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div>
<div v-if="loading" class="text-center">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div v-if="!loading">
<div v-for="log in logs">
{{ formatDate(log.create) }} ({{ getUserName(log.createBy) }}) | {{ log.message }}
<template v-if="log.fileIds">
<div v-for="file in JSON.parse(log.fileIds)">
<tt-file :id="file"/>
</div>
</template>
</div>
</div>
</div>
`,
<div v-if="!loading">
<div v-for="log in logs">
{{ formatDate(log.create) }} ({{ getUserName(log.createBy) }}) | {{ log.message }}
<template v-if="log.fileIds">
<div v-for="file in JSON.parse(log.fileIds)">
<tt-file :id="file"/>
</div>
</template>
</div>
</div>
</div>
`,
async mounted() {
this.loading = true;
const response = await axios.get(window.TT_CONFIG.BASE_PATH + '/WarehouseShippingNote/getLog', {params: {shippingNoteId: this.shippingNoteId}});
@@ -253,7 +255,7 @@ Vue.component('warehouse-shipping-note-logs', {
this.loading = false;
},
methods: {
formatDate: date => window.moment(date * 1000).format('DD.MM.YYYY HH:mm'),
formatDate: date => window.moment(date * 1000).format('DD.MM.YYYY HH:mm'),
getUserName: id => window.TT_CONFIG.CRUD_CONFIG.columns.find(col => col.key === 'createBy')?.modal.items.find(u => u.value === id)?.text
}
})
@@ -262,52 +264,65 @@ Vue.component('warehouse-shipping-note-see-through', {
props: ['wantedState'],
//language=Vue
template: `
<div class="modal" style="position: fixed; z-index: 9999; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center;">
<div class="modal-content" style="background-color: #fff; border-radius: 8px; width: 95%; height: 95%; max-width: none; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column;">
<div class="modal-top-bar" style="background: linear-gradient(135deg, #f7c423, #005384); color: white; padding: 15px 20px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
<h2 style="margin: 0; font-family: Arial, sans-serif; font-size: 20px; flex: 1;">Lieferschein Durchschau Modus</h2>
<div class="modal"
style="position: fixed; z-index: 9999; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center;">
<div class="modal-content"
style="background-color: #fff; border-radius: 8px; width: 95%; height: 95%; max-width: none; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column;">
<div class="modal-top-bar"
style="background: linear-gradient(135deg, #f7c423, #005384); color: white; padding: 15px 20px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
<h2 style="margin: 0; font-family: Arial, sans-serif; font-size: 20px; flex: 1;">Lieferschein Durchschau
Modus</h2>
<div style="display: flex; gap: 10px; align-items: center;">
<button @click="previousNote" style="background: none; border: none; color: white; font-family: Arial, sans-serif; font-size: 14px; cursor: pointer; display: flex; align-items: center; gap: 5px;">
<button @click="previousNote"
style="background: none; border: none; color: white; font-family: Arial, sans-serif; font-size: 14px; cursor: pointer; display: flex; align-items: center; gap: 5px;">
<i class="fas fa-chevron-left" style="font-size: 12px;"></i> Vorheriger
</button>
<button @click="nextNote" style="background: none; border: none; color: white; font-family: Arial, sans-serif; font-size: 14px; cursor: pointer; display: flex; align-items: center; gap: 5px;">
<button @click="nextNote"
style="background: none; border: none; color: white; font-family: Arial, sans-serif; font-size: 14px; cursor: pointer; display: flex; align-items: center; gap: 5px;">
Nächster <i class="fas fa-chevron-right" style="font-size: 12px;"></i>
</button>
</div>
<i @click="$emit('close')" class="fas fa-times" style="font-size: 20px; cursor: pointer; margin-left: 15px;"></i>
<i @click="$emit('close')" class="fas fa-times"
style="font-size: 20px; cursor: pointer; margin-left: 15px;"></i>
</div>
<div class="modal-body" style="padding: 20px; display: flex; flex: 1; overflow: hidden;">
<template>
<div style="width: 50%; height: 100%; overflow: auto;">
<iframe ref="iframe" v-if="currentRow" :src="'/WarehouseShippingNote/createPDF?id=' + currentRow.id" style="width: 100%; height: 100%; border: none;"></iframe>
</div>
<div style="width: 50%; height: 100%; padding-left: 20px; display: flex; flex-direction: column;">
<div style="margin-bottom: 20px;">
<button @click="activeTab = 'Editieren'" :style="tabStyle('Editieren')">Editieren</button>
<button @click="activeTab = 'Logs'" :style="tabStyle('Logs')">Logs</button>
<div style="margin-top: 20px;" v-if="currentRow">
<template v-for="action in window.TT_CONFIG.CRUD_CONFIG.additionalActions">
<template v-if="typeof action.condition === 'function' ? action.condition(currentRow) : true && action.key !== 'add_log' && action.key !== 'print'">
<i @click="changeStatus(action.key, currentRow)" :class="action.class" style="font-size: 20px; cursor: pointer; margin-right: 10px;"></i>
<div style="width: 50%; height: 100%; overflow: auto;">
<iframe ref="iframe" v-if="currentRow" :src="'/WarehouseShippingNote/createPDF?id=' + currentRow.id"
style="width: 100%; height: 100%; border: none;"></iframe>
</div>
<div style="width: 50%; height: 100%; padding-left: 20px; display: flex; flex-direction: column;">
<div style="margin-bottom: 20px;">
<button @click="activeTab = 'Editieren'" :style="tabStyle('Editieren')">Editieren</button>
<button @click="activeTab = 'Logs'" :style="tabStyle('Logs')">Logs</button>
<div style="margin-top: 20px;" v-if="currentRow">
<template v-for="action in window.TT_CONFIG.CRUD_CONFIG.additionalActions">
<template
v-if="typeof action.condition === 'function' ? action.condition(currentRow) : true && action.key !== 'add_log' && action.key !== 'print'">
<i @click="changeStatus(action.key, currentRow)" :class="action.class"
style="font-size: 20px; cursor: pointer; margin-right: 10px;"></i>
</template>
</template>
</template>
</div>
</div>
<div v-if="activeTab === 'Editieren'" style="flex: 1; overflow: auto;" class="see-through-test-modal">
<warehouse-shipping-note-modal v-if="currentRow" :id="currentRow.id" @close="fetchData" ref="modal">
<template v-slot:footer-prepend v-if="wantedState === 'new'">
<button class="btn btn-warning" @click="saveAndSetToProgress">Speichern und in Bearbeitung
setzen
</button>
</template>
</warehouse-shipping-note-modal>
</div>
<div v-if="activeTab === 'Logs'" style="flex: 1; overflow: auto;" class="see-through-test-modal">
<warehouse-shipping-note-logs :shipping-note-id="currentRow.id" :key="'logs' + logModalKey"/>
<add-log-modal-sn v-if="currentRow" :shipping-note-id="currentRow.id" @close="logModalKey++"
:key="'modal'+logModalKey"/>
</div>
</div>
<div v-if="activeTab === 'Editieren'" style="flex: 1; overflow: auto;" class="see-through-test-modal">
<warehouse-shipping-note-modal v-if="currentRow" :id="currentRow.id" @close="fetchData" ref="modal">
<template v-slot:footer-prepend v-if="wantedState === 'new'">
<button class="btn btn-warning" @click="saveAndSetToProgress">Speichern und in Bearbeitung setzen</button>
</template>
</warehouse-shipping-note-modal>
</div>
<div v-if="activeTab === 'Logs'" style="flex: 1; overflow: auto;" class="see-through-test-modal">
<warehouse-shipping-note-logs :shipping-note-id="currentRow.id" :key="'logs' + logModalKey"/>
<add-log-modal-sn v-if="currentRow" :shipping-note-id="currentRow.id" @close="logModalKey++" :key="'modal'+logModalKey"/>
</div>
</div>
</template>
</div>
</div>
@@ -384,71 +399,339 @@ Vue.component('warehouse-shipping-note-see-through', {
}
});
// noinspection JSUnusedLocalSymbols
Vue.component('warehouse-article-modal', {
template: `
<tt-modal :show="true" :save="false" :delete="false" @update:show="$emit('close')"
title="Artikel Suchen">
<tt-card>
<div v-if="!isLoadingCategories" :style="{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))',
gap: '10px',
padding: '10px',
marginBottom: '15px',
borderBottom: '1px solid #eee'
}">
<div v-for="category in categories"
:key="category.id"
@click="selectCategory(category)"
:style="getCategoryStyle(category)"
style="
padding: '10px 15px';
border: 1px solid #ccc;
border-radius: 4px;
text-align: center;
cursor: pointer;
transition: background-color 0.2s ease, box-shadow 0.2s ease;
background-color: #f9f9f9;
font-size: 0.9em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
"
:title="category.name"
@mouseover="hoverCategory($event, true)"
@mouseleave="hoverCategory($event, false)">
{{ category.name }}
</div>
</div>
<div v-else style="text-align: center; padding: 20px; color: #777;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Lade Kategorien...
</div>
<div v-if="selectedCategory" style="padding: 0 10px 15px 10px;">
<tt-input
label="Artikel suchen"
placeholder="Titel, Artikelnummer, Beschreibung..."
v-model="searchTerm"
:sm="true"
:row="false"
type="search"
hint="Sucht in Titel, Artikelnummer und Beschreibung."
/>
</div>
<div v-if="selectedCategory" style="padding: 0 10px 10px 10px; min-height: 150px; position: relative;">
<div v-if="isLoadingArticles" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255,255,255,0.7); display: flex; justify-content: center; align-items: center; z-index: 10;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span style="margin-left: 10px;">Lade Artikel...</span>
</div>
<div v-if="!isLoadingArticles && filteredArticles.length > 0" :style="{ display: 'grid', gridTemplateColumns: '1fr', gap: '8px' }">
<div v-for="article in filteredArticles"
:key="article.id"
@click="selectArticle(article)"
style="
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
background-color: #fff;
transition: background-color 0.15s ease;
"
@mouseover="event => event.currentTarget.style.backgroundColor='#f5f5f5'"
@mouseleave="event => event.currentTarget.style.backgroundColor='#fff'">
<div>
<strong style="font-size: 0.95em;">{{ article.title }}</strong>
<span style="font-size: 0.85em; color: #666; margin-left: 10px;">({{ article.articleNumber }})</span>
</div>
<div v-if="article.description" style="font-size: 0.85em; color: #555; margin-top: 3px;">
{{ article.description }}
</div>
</div>
</div>
<div v-if="!isLoadingArticles && articles.length > 0 && filteredArticles.length === 0" style="color: #777; text-align: center; padding: 20px;">
Keine Artikel entsprechen Ihrer Suche nach "{{ searchTerm }}".
</div>
<div v-if="!isLoadingArticles && articles.length === 0 && !isLoadingArticles" style="color: #777; text-align: center; padding: 20px;">
Keine Artikel in der ausgewählten Kategorie gefunden.
</div>
</div>
</tt-card>
</tt-modal>
`,
data() {
return {
window: window,
isLoadingCategories: false, // Added loading state for categories
categories: [],
selectedCategory: null,
articles: [],
searchTerm: '',
isLoadingArticles: false,
}
},
computed: {
filteredArticles() {
if (!this.searchTerm) {
return this.articles;
}
const lowerSearchTerm = this.searchTerm.toLowerCase();
// Ensure articles is an array before filtering
if (!Array.isArray(this.articles)) {
console.warn('Attempted to filter non-array articles:', this.articles);
return [];
}
return this.articles.filter(article => {
const titleMatch = article.title?.toLowerCase().includes(lowerSearchTerm);
const articleNumberMatch = article.articleNumber?.toLowerCase().includes(lowerSearchTerm);
const descriptionMatch = article.description?.toLowerCase().includes(lowerSearchTerm);
return titleMatch || articleNumberMatch || descriptionMatch;
});
}
},
methods: {
async selectCategory(category) {
if (this.selectedCategory && this.selectedCategory.id === category.id) {
return;
}
this.selectedCategory = category;
this.searchTerm = '';
this.articles = [];
console.log('Selected Category:', this.selectedCategory);
await this.fetchArticles(category.id);
},
async fetchArticles(categoryId) {
if (!categoryId) return;
this.isLoadingArticles = true;
try {
const response = await axios.post(window.TT_CONFIG.BASE_PATH + '/WarehouseArticle/getAll', {
filters: {
category_id: categoryId
}
});
// Robust check: Ensure response.data is an array
if (Array.isArray(response.data)) {
this.articles = response.data;
} else {
console.warn('Fetched articles data is not an array:', response.data);
this.articles = []; // Set to empty array if not valid
}
console.log('Fetched Articles:', this.articles);
} catch (error) {
console.error("Error fetching articles:", error);
this.articles = [];
} finally {
this.isLoadingArticles = false;
}
},
selectArticle(article) {
console.log('Selected Article:', article);
this.$emit('article-selected', article);
this.$emit('close');
},
getCategoryStyle(category) {
const style = {};
if (this.selectedCategory && this.selectedCategory.id === category.id) {
style.backgroundColor = '#d0e0ff';
style.borderColor = '#a0c0ff';
style.fontWeight = 'bold';
style.boxShadow = '0 0 5px rgba(0, 100, 255, 0.3)';
}
return style;
},
hoverCategory(event, isHovering) {
// Call findCategoryId safely
const categoryId = this.findCategoryId(event.target.innerText);
// Guard: Only proceed if categoryId was found
if (categoryId === null) {
// console.warn('Could not find category ID for:', event.target.innerText);
return; // Stop if ID couldn't be found (e.g., categories not loaded yet)
}
// Rest of the hover logic...
if (!event.target.dataset.categoryId) {
event.target.dataset.categoryId = categoryId;
}
if (!this.selectedCategory || this.selectedCategory.id != categoryId) {
if (isHovering) {
event.target.style.backgroundColor = '#e9e9e9';
event.target.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
} else {
event.target.style.backgroundColor = '#f9f9f9';
event.target.style.boxShadow = 'none';
}
} else {
if (!isHovering) {
event.target.style.boxShadow = this.getCategoryStyle(this.selectedCategory).boxShadow || 'none';
event.target.style.backgroundColor = this.getCategoryStyle(this.selectedCategory).backgroundColor ||'#d0e0ff';
}
}
},
findCategoryId(name) {
// **** GUARD ADDED HERE ****
// Check if categories is an array and has items before using .find()
if (!Array.isArray(this.categories) || this.categories.length === 0) {
// console.warn('findCategoryId called before categories array is ready or populated.');
return null; // Return null if categories are not ready
}
// Now it's safe to use .find()
const found = this.categories.find(cat => cat && cat.name === name); // Added check for cat existence
return found ? found.id : null;
}
},
async mounted() {
this.isLoadingCategories = true; // Set loading true before fetch
try {
const response = await axios.get(window.TT_CONFIG.BASE_PATH + '/WarehouseCategory/getAll');
console.log('Raw category response.data:', response.data); // Log the raw response
console.log('Is response.data an array?', Array.isArray(response.data)); // Check if it's an array
// **** ROBUST ASSIGNMENT ****
// Ensure we assign an array, even if the response isn't one.
if (Array.isArray(response.data)) {
this.categories = response.data;
} else if (response.data && Array.isArray(response.data.data)) {
// Example: Handle common case where data is nested like { data: [...] }
console.log('Assigning categories from response.data.data');
this.categories = response.data.data;
}
else {
console.warn('Categories response.data is not an array and not handled structure:', response.data);
this.categories = []; // Default to empty array if response is unexpected
}
console.log('Assigned this.categories:', this.categories);
} catch (error) {
console.error("Error fetching categories:", error);
this.categories = []; // Ensure it's an array on error
} finally {
this.isLoadingCategories = false; // Set loading false after fetch/error
}
}
})
Vue.component('warehouse-shipping-note', {
//language=Vue
template: `
<tt-card>
<warehouse-shipping-note-see-through
@close="shippingNoteSeeThrough = false;$refs.table.$refs.table.refreshTable()"
v-if="shippingNoteSeeThrough !== false"
:wanted-state="shippingNoteSeeThrough"
@status_to_progress="changeStatus($event.id, 'in_progress')"
@status_to_new="changeStatus($event.id, 'new')"
@status_to_on_hold="changeStatus($event.id, 'on_hold')"
@status_to_cancelled="changeStatus($event.id, 'cancelled')"
@status_to_invoiced="changeStatus($event.id, 'invoiced')"
@status_to_accepted="changeStatus($event.id, 'accepted')"/>
<warehouse-shipping-note-modal v-if="shippingNoteModalId" :id="shippingNoteModalId"
@close="shippingNoteModalId = null;$refs.table.$refs.table.refreshTable()"
@open-signing-modal="signingShippingNoteId = $event"/>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
<warehouse-shipping-note-signature-pad v-if="signingShippingNoteId" :shipping-note-id="signingShippingNoteId"
@close="signingShippingNoteId = null;shippingNoteModalId = null;$refs.table.$refs.table.refreshTable()"/>
<add-log-modal-sn v-if="addLogModalId" :shipping-note-id="addLogModalId" @close="addLogModalId = null"/>
<tt-card>
<warehouse-shipping-note-see-through
@close="shippingNoteSeeThrough = false;$refs.table.$refs.table.refreshTable()"
v-if="shippingNoteSeeThrough !== false"
:wanted-state="shippingNoteSeeThrough"
@status_to_progress="changeStatus($event.id, 'in_progress')"
@status_to_new="changeStatus($event.id, 'new')"
@status_to_on_hold="changeStatus($event.id, 'on_hold')"
@status_to_cancelled="changeStatus($event.id, 'cancelled')"
@status_to_invoiced="changeStatus($event.id, 'invoiced')"
@status_to_accepted="changeStatus($event.id, 'accepted')"/>
<warehouse-shipping-note-modal v-if="shippingNoteModalId" :id="shippingNoteModalId"
ref="modal"
@close="shippingNoteModalId = null;$refs.table.$refs.table.refreshTable()"
@open-article-modal="articleModalId = true;window.console.log($event)"
@open-signing-modal="signingShippingNoteId = $event"/>
<warehouse-article-modal
v-if="articleModalId"
:id="articleModalId"
@close="articleModalId = null"
@article-selected="$refs.modal.$refs.positionsManager.updateField('article', $event.id);"/>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
<warehouse-shipping-note-signature-pad v-if="signingShippingNoteId" :shipping-note-id="signingShippingNoteId"
@close="signingShippingNoteId = null;shippingNoteModalId = null;$refs.table.$refs.table.refreshTable()"/>
<add-log-modal-sn v-if="addLogModalId" :shipping-note-id="addLogModalId" @close="addLogModalId = null"/>
<button @click="shippingNoteModalId = 'create'" class="btn btn-primary">Lieferschein erstellen</button>
<div class="dropdown" style="display: inline-block; margin-left: 10px;" v-if="window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1'">-
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Durchschau-Modus
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" @click="shippingNoteSeeThrough = 'new'">Neue</a>
<a class="dropdown-item" @click="shippingNoteSeeThrough = 'in_progress'">In Bearbeitung</a>
<a class="dropdown-item" @click="shippingNoteSeeThrough = 'accepted'">Akzeptierte</a>
<a class="dropdown-item" @click="shippingNoteSeeThrough = 'on_hold'">On Hold</a>
</div>
</div>
<button @click="shippingNoteModalId = 'create'" class="btn btn-primary">Lieferschein erstellen</button>
<div class="dropdown" style="display: inline-block; margin-left: 10px;"
v-if="window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1'">
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
Durchschau-Modus
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" style="cursor: pointer;">
<a class="dropdown-item" @click="shippingNoteSeeThrough = 'new'">Neue</a>
<a class="dropdown-item" @click="shippingNoteSeeThrough = 'in_progress'">In Bearbeitung</a>
<a class="dropdown-item" @click="shippingNoteSeeThrough = 'accepted'">Akzeptierte</a>
<a class="dropdown-item" @click="shippingNoteSeeThrough = 'on_hold'">On Hold</a>
</div>
</div>
<tt-table-crud emit-edit
@openHistory="historyModal = true; historyModalId = $event.id"
@print="window.open(window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNote/createPDF?id=' + $event.id)"
@status_to_progress="changeStatus($event.id, 'in_progress')"
@status_to_accepted="changeStatus($event.id, 'accepted')"
@status_to_invoiced="changeStatus($event.id, 'invoiced')"
@status_to_on_hold="changeStatus($event.id, 'on_hold')"
@status_to_cancelled="changeStatus($event.id, 'cancelled')"
@status_to_new="changeStatus($event.id, 'new')"
@add_log="addLogModalId = $event.id"
@edit="shippingNoteModalId = $event.id"
ref="table">
<tt-table-crud emit-edit
@openHistory="historyModal = true; historyModalId = $event.id"
@print="window.open(window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNote/createPDF?id=' + $event.id)"
@status_to_progress="changeStatus($event.id, 'in_progress')"
@status_to_accepted="changeStatus($event.id, 'accepted')"
@status_to_invoiced="changeStatus($event.id, 'invoiced')"
@status_to_on_hold="changeStatus($event.id, 'on_hold')"
@status_to_cancelled="changeStatus($event.id, 'cancelled')"
@status_to_new="changeStatus($event.id, 'new')"
@add_log="addLogModalId = $event.id"
@edit="shippingNoteModalId = $event.id"
ref="table">
<template v-slot:expandedRow="{ row }">
<warehouse-shipping-note-positions :positions="JSON.parse(row.positions)" :hours-entries="JSON.parse(row.hoursEntries)"/>
<warehouse-shipping-note-logs :shipping-note-id="row.id"/>
</template>
<template v-slot:expandedRow="{ row }">
<warehouse-shipping-note-positions :positions="JSON.parse(row.positions)"
:hours-entries="JSON.parse(row.hoursEntries)"/>
<warehouse-shipping-note-logs :shipping-note-id="row.id"/>
</template>
</tt-table-crud>
</tt-card>
`, data() {
</tt-table-crud>
</tt-card>
`, data() {
return {
window: window,
historyModal: false,
historyModalId: null,
shippingNoteModalId: null,
window: window,
historyModal: false,
historyModalId: null,
shippingNoteModalId: null,
signingShippingNoteId: null,
addLogModalId: null,
shippingNoteSeeThrough: false
addLogModalId: null,
shippingNoteSeeThrough: false,
articleModalId: null,
}
},
mounted() {
@@ -459,7 +742,10 @@ Vue.component('warehouse-shipping-note', {
},
methods: {
async changeStatus(id, status) {
const response = await axios.post(window.TT_CONFIG.BASE_PATH + '/WarehouseShippingNote/changeStatus', {id, status});
const response = await axios.post(window.TT_CONFIG.BASE_PATH + '/WarehouseShippingNote/changeStatus', {
id,
status
});
if (response.data.success) {
this.window.notify('success', response.data.message || 'Erfolgreich aktualisiert');
this.$refs.table.$refs.table.refreshTable();
@@ -472,7 +758,7 @@ Vue.component('warehouse-shipping-note', {
window.addEventListener('DOMContentLoaded', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/WarehouseShippingNote/sw', { scope: '/' })
navigator.serviceWorker.register('/WarehouseShippingNote/sw', {scope: '/'})
.then(registration => {
console.log('Patching PWA Service Worker registered with scope:', registration.scope);
})
@@ -35,6 +35,7 @@ Vue.component('warehouse-shipping-note-modal', {
customOrdering: 'article',
fields: {
article: {
style: 'display: grid; grid-template-columns: 4fr 1fr; grid-gap: 10px;',
type: 'autocomplete',
label: 'Artikel',
apiUrl: '/WarehouseArticle/autoComplete',
@@ -133,7 +134,13 @@ Vue.component('warehouse-shipping-note-modal', {
<hr>
<h4 class="text-center">Positionen</h4>
<tt-positions-manager :config="positionsConfig" v-model="shippingNote.positions" sm row/>
<tt-positions-manager :config="positionsConfig" v-model="shippingNote.positions" sm row ref="positionsManager">
<template #article-prepend>
<tt-button
style="max-width: 32px" sm
text="" @click="$emit('open-article-modal')" additional-class="btn-outline-primary" icon="fa fa-search"/>
</template>
</tt-positions-manager>
</template>
<div v-else class="text-danger text-center font-weight-bolder font-16">Bitte Lieferadresse eingeben oder nach Kunden suchen</div>