update for warehouse

This commit is contained in:
2024-10-10 08:49:50 +02:00
parent 8a2b8c0b20
commit c57eef6e8d
56 changed files with 2250 additions and 451 deletions
@@ -0,0 +1,56 @@
Vue.component('warehouse-administration', {
//language=Vue
template: `
<tt-card title="Warehouse Administration">
<warehouse-administration-switch/>
<div class="button-group" style="max-width: 300px;display: flex; flex-direction: column; gap: 10px;">
<button
class="btn btn-primary"
:disabled="isLoading"
@click="createLocationsForAllEmployees"
title="Automatische Lagerorterstellung für Mitarbeiter. Mitarbeiter mit Firmenwagen erhalten einen zugehörigen Fahrzeuglagerort.">
Lagerorte für alle Mitarbeiter erstellen
</button>
<button
class="btn btn-primary"
:disabled="isLoading"
@click="updateAllSalesPrices">
Alle Verkaufspreise updaten
</button>
</div>
</tt-card>
`,
data() {
return {
isLoading: false,
window: window,
};
},
methods: {
async createLocationsForAllEmployees() {
this.isLoading = true;
const response = await axios.get(window.TT_CONFIG.BASE_URL + '/createLocations');
if (response.data.success) {
this.window.notify('success', response.data.message || 'Lagerorte wurden erfolgreich erstellt.');
} else {
this.window.notify('error', response.data.message || 'Fehler beim Erstellen der Lagerorte.');
}
this.isLoading = false;
},
async updateAllSalesPrices() {
this.isLoading = true;
const response = await axios.get(window.TT_CONFIG.BASE_PATH + '/WarehouseArticle/updatePrices');
if (response.data.success) {
this.window.notify('success', 'Verkaufspreise wurden erfolgreich aktualisiert.');
} else {
this.window.notify('error', 'Fehler beim Aktualisieren der Verkaufspreise.');
}
this.isLoading = false;
},
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
});
@@ -250,11 +250,14 @@ Vue.component('warehouse-article', {
//language=Vue
template: `
<tt-card>
<tt-table-crud ref="table"
@openHistory="historyModal = true; historyModalId = $event.id"
@editDistributorEntries="distributorModal = true; distributorModalId = $event.id"
@editPricesEntries="priceModal = true; priceModalId = $event.id"
@editThresholdEntries="thresholdModal = true; thresholdModalId = $event.id">
@editThresholdEntries="thresholdModal = true; thresholdModalId = $event.id"
@addToCart="addShoppingCartModal = true; addShoppingCartModalId = $event.id"
>
<template v-slot:cheapestsellprice="{ row }">
<template v-for="price in JSON.parse(row.cheapestSellPrice)">
@@ -268,6 +271,38 @@ Vue.component('warehouse-article', {
</tt-table-crud>
<tt-expandable-shopping-cart :cart-items="shoppingCart" @submitOrder="prepareOrder"/>
<tt-modal :show.sync="addShoppingCartModal" title="Artikel zur Bestellung hinzufügen" :delete="false" @submit="addToShoppingCart"
@close="addShoppingCartModal = false">
<tt-input v-model="addShoppingCartModalCount" placeholder="Menge" type="number" sm></tt-input>
</tt-modal>
<tt-modal :show.sync="confirmOrderModal" title="Bestellung bestätigen" :delete="false"
save-text="Bestätigen" @submit="createOrder"
@close="confirmOrderModal = false; confirmOrderModalData = null">
<span>
Es werden Bestellungen an folgende Lieferanten gesendet:
</span>
<div v-for="(order, index) in confirmOrderModalData" :key="index">
<h4>{{order.distributor[0].name}} - {{order.orderAmount}} €
</h4>
<table class="table table-bordered">
<tr>
<th>Artikel</th>
<th>Menge</th>
<th>Preis</th>
<th>Summe</th>
</tr>
<tr v-for="(item, index) in order.orders" :key="index">
<td>{{item.title}}</td>
<td>{{item.amount}}</td>
<td>{{item.purchasePrice}} €</td>
<td>{{item.sum.toFixed(2)}} €</td>
</tr>
</table>
</div>
</tt-modal>
<warehouse-distributor-modal :show.sync="distributorModal" :id="distributorModalId" @doUpdate="refreshTable"/>
<warehouse-threshold-modal :show.sync="thresholdModal" :id="thresholdModalId" @doUpdate="refreshTable"/>
<warehouse-article-price-modal :show.sync="priceModal" :id="priceModalId" @doUpdate="refreshTable"/>
@@ -275,19 +310,56 @@ Vue.component('warehouse-article', {
</tt-card>
`, data() {
return {
window: window,
historyModal: false,
historyModalId: null,
distributorModal: false,
distributorModalId: null,
thresholdModal: false,
thresholdModalId: null,
priceModal: false,
priceModalId: null
window: window,
historyModal: false,
historyModalId: null,
distributorModal: false,
distributorModalId: null,
thresholdModal: false,
thresholdModalId: null,
priceModal: false,
priceModalId: null,
shoppingCart: [],
addShoppingCartModal: false,
addShoppingCartModalId: null,
addShoppingCartModalCount: '',
confirmOrderModal: false,
confirmOrderModalData: null,
}
}, methods: {
refreshTable() {
this.$refs.table.$refs.table.refreshTable();
}, async addToShoppingCart() {
if (this.addShoppingCartModalCount < 1) { // Check if amount is set
window.notify('error', 'Bitte geben Sie eine Menge ein.');
return;
}
if (this.shoppingCart.some(item => item.itemId === this.addShoppingCartModalId)) { // Check if same article is already in cart
window.notify('error', 'Artikel bereits im Warenkorb.');
return;
}
const response = await axios.get(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticle/getById?id=${this.addShoppingCartModalId}`);
this.shoppingCart.push({amount: parseInt(this.addShoppingCartModalCount), itemId: this.addShoppingCartModalId, title: response.data.title});
this.addShoppingCartModal = false;
this.addShoppingCartModalId = null;
this.addShoppingCartModalCount = '';
window.notify('success', 'Artikel erfolgreich hinzugefügt.');
}, async prepareOrder() {
const response = await axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticle/prepareOrder`, this.shoppingCart);
this.confirmOrderModal = true;
this.confirmOrderModalData = response.data;
},
async createOrder() {
const response = await axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseOrder/createOrder`, this.confirmOrderModalData);
this.confirmOrderModal = false;
this.confirmOrderModalData = null;
this.shoppingCart = [];
window.notify(response.data.success ? 'success' : 'error', response.data.message);
setTimeout(() => {
window.location.href = `${window['TT_CONFIG']['BASE_PATH']}/WarehouseOrder`;
}, 2000);
}
}
})
@@ -0,0 +1,14 @@
Vue.component('warehouse-article-price-type', {
//language=Vue
template: `
<tt-card>
<warehouse-administration-switch/>
<tt-table-crud @openHistory="historyModal = true; historyModalId = $event.id"/>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
</tt-card>
`, data() {
return {
window: window, historyModal: false, historyModalId: null,
}
},
})
@@ -2,6 +2,7 @@ Vue.component('warehouse-distributor', {
//language=Vue
template: `
<tt-card>
<warehouse-administration-switch/>
<tt-table-crud @openHistory="historyModal = true; historyModalId = $event.id"/>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
</tt-card>
@@ -1,47 +1,3 @@
Vue.component('tt-expandable-shopping-cart', {
props: {
cartItems: Array,
},
data() {
return {
isExpanded: false,
};
},
methods: {
},
template: `
<div class="tt-expandable-shopping-cart" :class="{ expanded: isExpanded }">
<button class="toggle-button" @click="isExpanded = !isExpanded">
<i class="fas fa-shopping-cart text-primary"></i>
<span v-if="cartItems.length > 0 && isExpanded" class="btn btn-primary" @click.prevent="$emit('submitOrder')">Bestellen</span>
<span class="cart-count" v-if="cartItems.length > 0">{{ cartItems.length }}</span>
<!-- add arrow down icon when cart is expanded additionally with v-if -->
<i v-if="isExpanded" class="fas fa-arrow-down text-danger" style="font-size:21px;grid-column: 4;"></i>
</button>
<div class="cart-content" v-if="isExpanded">
<div v-if="cartItems.length > 0">
<h3>Einkaufswagen</h3>
<ul class="list-group">
<template v-for="item in cartItems">
<li class="list-group-item" style="display:grid;grid-template-columns: 1fr auto;gap: 10px;">
{{ item.title }}
<div style="display:grid;grid-template-columns: 1fr 1fr;gap: 10px;">
<span class="badge badge-primary badge-pill" style="height: 16px">{{ item.amount }}</span>
<i style="cursor: pointer" class="fas fa-trash-alt text-danger" @click="cartItems.splice(cartItems.indexOf(item), 1)"></i>
</div>
</li>
</template>
</ul>
</div>
<p v-else>Der Einkaufswagen ist leer.</p>
</div>
</div>
`
});
Vue.component('warehouse-e-shop', {
//language=Vue
template: `
@@ -82,7 +82,6 @@ Vue.component('warehouse-e-shop-order', {
}
}, async mounted() {
const response = await axios.get(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseEShopOrder/getAllItemsPerOrder`);
console.log(response.data);
this.articleItems = response.data;
}, methods: {
async sendSingleOrderEmail() {
@@ -43,3 +43,97 @@ Vue.component('warehouse-history-modal', {
}
}
})
Vue.component('tt-expandable-shopping-cart', {
props: {
cartItems: Array,
},
data() {
return {
isExpanded: false,
};
},
methods: {
},
template: `
<div class="tt-expandable-shopping-cart" :class="{ expanded: isExpanded }">
<button class="toggle-button" @click="isExpanded = !isExpanded">
<i class="fas fa-shopping-cart text-primary"></i>
<span v-if="cartItems.length > 0 && isExpanded" class="btn btn-primary" @click.prevent="$emit('submitOrder')">Bestellen</span>
<span class="cart-count" v-if="cartItems.length > 0">{{ cartItems.length }}</span>
<!-- add arrow down icon when cart is expanded additionally with v-if -->
<i v-if="isExpanded" class="fas fa-arrow-down text-danger" style="font-size:21px;grid-column: 4;"></i>
</button>
<div class="cart-content" v-if="isExpanded">
<div v-if="cartItems.length > 0">
<h3>Einkaufswagen</h3>
<ul class="list-group">
<template v-for="item in cartItems">
<li class="list-group-item" style="display:grid;grid-template-columns: 1fr auto;gap: 10px;">
{{ item.title }}
<div style="display:grid;grid-template-columns: 1fr 1fr;gap: 10px;">
<span class="badge badge-primary badge-pill" style="height: 16px">{{ item.amount }}</span>
<i style="cursor: pointer" class="fas fa-trash-alt text-danger" @click="cartItems.splice(cartItems.indexOf(item), 1)"></i>
</div>
</li>
</template>
</ul>
</div>
<p v-else>Der Einkaufswagen ist leer.</p>
</div>
</div>
`
});
//TODO: put this in its own file
//TODO: also for tt-crud or vuehelper create a check for utility folder and include all js files in there to allow adding custom components that are not part of the core
//TODO: also add a component for a switch like this as we will need it more often either with value or doing redirect on click
Vue.component('warehouse-administration-switch', {
//language=Vue
template: `
<div class="device-view-switch" style="margin-bottom: 10px">
<div v-if="!isOverflowing" class="button-group" style="display:grid; grid-template-columns: repeat(5, 1fr); gap: 10px; justify-content: center; align-items: center; text-align: center; width: 100%;">
<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'] + '/WarehouseAdministration';" :class="{ 'active': window.location.href.includes('Device') }" class="btn btn-primary">Admin-Tools</button>
</div>
<div v-else>
<div class="dropdown">
<button @click="showDropdown = !showDropdown"
class="btn btn-primary dropdown-toggle">Ansicht</button>
<div v-show="showDropdown" class="dropdown-menu show">
<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'] + '/WarehouseAdministration'; showDropdown = false" class="dropdown-item">Admin-Tools</a>
</div>
</div>
</div>
</div>
`,
props: ['value'],
data() {
return {
isOverflowing: false,
showDropdown: false,
window: window,
};
},
mounted() {
this.checkOverflow();
window.addEventListener('resize', this.checkOverflow);
},
beforeDestroy() {
window.removeEventListener('resize', this.checkOverflow);
},
methods: {
checkOverflow() {
this.isOverflowing = window.innerWidth < 650
},
},
})
@@ -2,7 +2,13 @@ Vue.component('warehouse-item', {
//language=Vue
template: `
<tt-card>
<tt-table-crud @openHistory="historyModal = true; historyModalId = $event.id"/>
<tt-table-crud @openHistory="historyModal = true; historyModalId = $event.id">
<template v-slot:rack="{ row }">
<span v-if="row.rack && row.shelf">{{ row.rack }} | {{ row.shelf }}</span>
<span v-else-if="row.rack">{{ row.rack }}</span>
<span v-else> - </span>
</template>
</tt-table-crud>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
</tt-card>
`, data() {
@@ -2,6 +2,7 @@ Vue.component('warehouse-location', {
//language=Vue
template: `
<tt-card>
<warehouse-administration-switch/>
<tt-table-crud @openHistory="historyModal = true; historyModalId = $event.id"/>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
</tt-card>
@@ -0,0 +1,70 @@
// noinspection JSUnusedLocalSymbols
Vue.component('warehouse-order', {
//language=Vue
template: `
<tt-card>
<tt-table-crud @openHistory="historyModal = true; historyModalId = $event.id"
ref="table">
<template v-slot:create="{ row }">
{{ window.moment(row.create * 1000).format('DD.MM.YYYY HH:mm:ss') }}
</template>
<template v-slot:sum="{ row }">
<div style="text-align: right">{{ row.sum.toFixed(2) }} €</div>
</template>
<template v-slot:expandedRow="{ row }">
<div class="lazy-loading" :data-row-id="row.id">
<tt-loader v-if="orderLazyLoad[row.id] === true"/>
<div v-else>
<ul class="list-group">
<li class="list-group-item" v-for="item in orderLazyLoad[row.id]">
{{ item.quantity }}x {{ item.articleName }} - {{ item.price.toFixed(2) }} €
</li>
</ul>
</div>
</div>
</template>
</tt-table-crud>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
</tt-card>
`, data() {
return {
window: window, historyModal: false, historyModalId: null, observer: null, orderLazyLoad: {},
}
}, mounted() {
this.observer = new MutationObserver((mutations) => {
const lazyLoadingElements = document.querySelectorAll('.lazy-loading');
console.log(lazyLoadingElements);
// check row id and check if it is already defined in orderLazyLoad else alert('loading')
// if it is defined do nothing
for (const element of lazyLoadingElements) {
if (element.dataset.rowId in this.orderLazyLoad) {
continue;
}
this.loadOrder(element.dataset.rowId);
}
})
this.observer.observe(document.querySelector('.tt-table-container'), {childList: true, subtree: true,});
}, methods: {
async loadOrder(rowId) {
this.orderLazyLoad[rowId] = true;
// use BASE_PATH . /WarehouseOrder/getOrderItems?id= + rowId
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getOrderItems?id=${rowId}`);
console.log(response.data);
this.orderLazyLoad[rowId] = response.data;
// force re-render of the table
this.$refs.table.$forceUpdate();
}
}, beforeDestroy() {
this.observer.disconnect();
}
})
@@ -0,0 +1,321 @@
const defaultCrudModalData = {
billingAddressId: '',
deliveryAddressName: '',
deliveryAddressLine: '',
deliveryAddressPLZ: '',
deliveryAddressCity: '',
status: 'new',
positions: [],
textElements: {}
}
window.crudModalStatusOptions =
[{value: 'new', text: 'Neu'}, {value: 'accepted', text: 'Akzeptiert'}, {value: 'invoiced', text: 'In Rechnung gestellt', disabled: true}]
// create a additional vue component for showing positions in the table with lazy loading for article titles and description
Vue.component('warehouse-shipping-note-positions', {
//language=Vue
props: {
positions: Array
}, data() {
return {
articleData: {}, loading: false
}
}, template: `
<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">
<span>{{ position.amount }}x {{ articleData[position.article]?.text }}</span>
</li>
</ul>
</div>
`, async mounted() {
this.loading = true;
for (const position of this.positions) {
const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/WarehouseArticle/autoComplete?searchedID=' + position.article);
this.$set(this.articleData, position.article, response.data[0]);
}
this.loading = false;
}
})
// noinspection JSUnusedLocalSymbols
Vue.component('warehouse-shipping-note', {
//language=Vue
template: `
<tt-card>
<tt-modal :show.sync="crudModal" :id="crudModalId"
:delete="false"
@submit="createOrUpdate()"
:title="crudModalId === 'create' ? 'Lieferschein erstellen' : 'Lieferschein bearbeiten'">
<tt-autocomplete v-model="crudModalData.billingAddressId"
:api-url="window.TT_CONFIG['BASE_PATH'] + '/Address/Api?do=findAddress'"
label="Rechnungsadresse" sm row/>
<tt-select v-model="crudModalSelectDeliveryAddressMode" :options="crudModalSelectDeliveryAddressModeItems" label="Lieferadresse Art" sm
row/>
<template v-if="crudModalSelectDeliveryAddressMode === 'existing'">
<tt-select v-model="crudModalDataDeliveryAddressSelected" :options="crudModalDataDeliveryAddressOptions" label="Lieferadresse" sm row/>
</template>
<template v-else-if="crudModalSelectDeliveryAddressMode === 'new'">
<tt-input v-model="crudModalData.deliveryAddressName" label="Lieferadresse Name" sm row/>
<tt-input v-model="crudModalData.deliveryAddressLine" label="Lieferadresse" sm row/>
<tt-input v-model="crudModalData.deliveryAddressPLZ" label="Lieferadresse PLZ" sm row/>
<tt-input v-model="crudModalData.deliveryAddressCity" label="Lieferadresse Ort" sm row/>
</template>
<tt-select v-if="crudModalVerifyMode === true" v-model="crudModalData.status" :options="window.crudModalStatusOptions" label="Status" sm
row/>
<!-- show a checkbox for each textElement and if selected set it to selected [{"id":1,"title":"Zahlhinweis","content":"Bezahlung in 14 tagen","create":1728456765,"createBy":145}]-->
<template>
<hr>
<h4 class="text-center">Texte</h4>
<div v-for="textElement in textElements" style="display: inline-block; margin-right: 10px;">
<input type="checkbox" v-model="crudModalData.textElements[textElement.id]" :id="'textElement' + textElement.id">
<label :for="'textElement' + textElement.id">{{ textElement.title }}</label>
</div>
</template>
<hr>
<h4 class="text-center">Positionen</h4>
<template v-if="crudModalData.billingAddressId">
<div style="display: flex; justify-content: space-around;padding: 10px;">
<tt-autocomplete v-model="crudModalAddPositionArticle" :api-url="window.TT_CONFIG['BASE_PATH'] + '/WarehouseArticle/autoComplete'"
placeholder="Artikel" sm row/>
<tt-input v-model="crudModalAddPositionAmount" placeholder="Menge" sm row/>
<tt-input v-model="crudModalAddPositionPrice" placeholder="Preis" type="number" sm row/>
<button style="max-height: 29px" class="btn btn-sm btn-primary" @click="addPosition">Hinzufügen</button>
</div>
<table class="table table-sm">
<thead>
<tr>
<th>Position</th>
<th>Artikel</th>
<th>Menge</th>
<th>Preis</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(position, index) in crudModalData.positions">
<td>{{ index + 1 }}</td>
<td>{{ articleNames[position.article] }}</td>
<td>{{ position.amount }}</td>
<td>{{ (position.price?.toFixed(2)) }} €</td>
<td>
<button class="btn btn-sm btn-danger" @click="crudModalData.positions.splice(index, 1)">Löschen</button>
</td>
</tr>
</tbody>
</table>
</template>
<template v-else>
<h5 class="text-center">Rechnungsadresse auswählen um Positionen hinzuzufügen</h5>
</template>
</tt-modal>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
<button @click="openCrudModal('create')" class="btn btn-primary">Lieferschein erstellen</button>
<button @click="openVerifyModal" class="btn btn-primary">Lieferscheine Freigeben</button>
<tt-table-crud emit-edit
@openHistory="historyModal = true; historyModalId = $event.id"
@print="window.open(window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNote/createPDF?id=' + $event.id)"
@printWithPrice="window.open(window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNote/createPDF?id=' + $event.id + '&price=true')"
@edit="openCrudModal($event)"
ref="table">
<template v-slot:expandedRow="{ row }">
<warehouse-shipping-note-positions :positions="JSON.parse(row.positions)"/>
</template>
</tt-table-crud>
</tt-card>
`, data() {
return {
window: window,
historyModal: false,
historyModalId: null,
crudModal: false,
crudModalSelectDeliveryAddressModeItems: [{text: 'Wie Rechnungsadresse', value: 'billing'},
{text: 'Bestehende Lieferadresse', value: 'existing'},
{text: 'Neue Lieferadresse', value: 'new'}],
crudModalSelectDeliveryAddressMode: 'billing',
crudModalDataDeliveryAddressOptions: [],
crudModalDataDeliveryAddressSelected: '',
crudModalVerifyMode: false,
crudModalId: null,
crudModalData: defaultCrudModalData,
crudModalAddPositionArticle: '',
crudModalAddPositionAmount: '',
crudModalAddPositionPrice: '',
articleNames: {},
textElements: [],
}
}, async mounted() {
const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/WarehouseShippingNote/getAllTextElements');
this.textElements = response.data;
},
methods: {
async openVerifyModal() {
const unverifiedShippingNotes = await axios.post(window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNote/get', {
"pagination": {"page": 1, "per_page": 1}, "filters": {
"status": "new"
}, "order": {"key": null, "order": "asc"}
});
if (unverifiedShippingNotes.data.rows.length === 0) {
this.window.notify('warning', 'Keine Lieferscheine zum Freigeben gefunden');
return;
}
await this.openCrudModal(unverifiedShippingNotes.data.rows[0]);
this.crudModalVerifyMode = true;
}, resetCrudModalData() {
this.crudModalData.billingAddressId = '';
this.crudModalData.deliveryAddressName = '';
this.crudModalData.deliveryAddressLine = '';
this.crudModalData.deliveryAddressPLZ = '';
this.crudModalData.deliveryAddressCity = '';
this.crudModalAddPositionArticle = '';
this.crudModalAddPositionAmount = '';
this.crudModalAddPositionPrice = '';
this.crudModalSelectDeliveryAddressMode = 'billing';
this.crudModalDataDeliveryAddressSelected = '';
this.crudModal = false;
}, async openCrudModal(data) {
this.resetCrudModalData();
this.crudModalVerifyMode = false;
if (data === 'create') {
this.crudModalId = 'create'
this.crudModalData = defaultCrudModalData
this.crudModal = true
} else {
const disconnectedData = JSON.parse(JSON.stringify(data));
if (disconnectedData.status !== 'new') {
this.window.notify('warning', 'Lieferschein kann nicht bearbeitet werden, da er bereits genehmigt wurde');
return;
}
disconnectedData.textElements = JSON.parse(disconnectedData.textElements);
disconnectedData.positions = JSON.parse(disconnectedData.positions);
for (const position of disconnectedData.positions) {
await this.fetchArticleNames(position.article);
}
this.crudModalId = 'update'
this.crudModalData = disconnectedData
this.crudModal = true
}
}, async addPosition() {
const missingFields = [];
// ---------- Check Required Fields ----------
if (!this.crudModalAddPositionArticle) missingFields.push('Artikel');
if (!this.crudModalAddPositionAmount) missingFields.push('Menge');
if (!this.crudModalAddPositionPrice) missingFields.push('Preis-Überschreibung');
if (missingFields.length > 0) {
window.notify('error', 'Bitte füllen Sie die folgenden Felder aus: ' + missingFields.join(', '));
return;
}
// ---------- Check if same article is already in positions ----------
const articleAlreadyInPositions = this.crudModalData.positions.find(position => position.article === this.crudModalAddPositionArticle);
if (articleAlreadyInPositions) {
window.notify('error', 'Artikel ist bereits in den Positionen enthalten');
return;
}
await this.fetchArticleNames(this.crudModalAddPositionArticle);
this.crudModalData.positions.push({
article: this.crudModalAddPositionArticle, amount: this.crudModalAddPositionAmount, price: parseFloat(this.crudModalAddPositionPrice)
});
//TODO: post to server
}, async fetchArticleNames(articleId) {
const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/WarehouseArticle/autoComplete?searchedID=' + articleId);
this.$set(this.articleNames, articleId, response.data[0].text);
}, async createOrUpdate() {
const response = await axios.post(this.crudModalId === 'create' ? window['TT_CONFIG']['CREATE_URL'] : window['TT_CONFIG']['UPDATE_URL'],
this.crudModalData);
if (response.data.success) {
this.$refs.table.$refs.table.refreshTable();
this.resetCrudModalData();
this.window.notify('success', response.data.message || 'Erfolgreich gespeichert');
} else {
this.window.notify('error',
response.data.errors ? Object.values(response.data.errors).join('<br>') : response.data.message || 'Ein Fehler ist aufgetreten');
}
}, async fetchDeliveryAddresses() {
if (!this.crudModalData.billingAddressId || this.crudModalSelectDeliveryAddressMode !== 'existing') return;
if (this.crudModalSelectDeliveryAddressMode === 'billing') {
const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/Address/api?do=getAddress&id=' + this.crudModalData.billingAddressId);
if (response.data.status !== 'OK' || !response.data.result.address) {
window.notify('error', 'Rechnungsadresse konnte nicht gefunden werden');
return;
}
this.crudModalData.deliveryAddressName =
response.data.result.address.company || response.data.result.address.firstname + ' ' + response.data.result.address.lastname;
this.crudModalData.deliveryAddressLine = response.data.result.address.street;
this.crudModalData.deliveryAddressPLZ = response.data.result.address.zip;
this.crudModalData.deliveryAddressCity = response.data.result.address.city;
}
if (!this.crudModalData.billingAddressId || this.crudModalSelectDeliveryAddressMode !== 'existing') return;
const response = await axios.get(window.TT_CONFIG["BASE_PATH"] +
'/WarehouseShippingNote/getDeliveryAddresses?billingAddressId=' +
this.crudModalData.billingAddressId);
this.crudModalDataDeliveryAddressOptions = response.data.map(address => {
address.value = address.id;
address.text = `${address.deliveryAddressName} - ${address.deliveryAddressLine}, ${address.deliveryAddressPLZ} ${address.deliveryAddressCity}`;
return address;
});
}
}, watch: {
crudModalAddPositionArticle: async function (newValue) {
if (!newValue) return;
const url = `${window.TT_CONFIG["BASE_PATH"]}/WarehouseShippingNote/getArticleAddressPrice?articleId=${newValue}&addressId=${this.crudModalData.billingAddressId}`;
const response = await axios.get(url);
this.crudModalAddPositionPrice = response.data.price;
},
crudModalData: {handler: 'fetchDeliveryAddresses', deep: true},
crudModalSelectDeliveryAddressMode: {handler: 'fetchDeliveryAddresses', deep: true},
crudModalDataDeliveryAddressSelected: function (newValue) {
if (!newValue) return;
const selectedAddress = this.crudModalDataDeliveryAddressOptions.find(address => address.id === parseInt(newValue));
if (!selectedAddress) {
window.notify('error', 'Lieferadresse konnte nicht gefunden werden');
return;
}
this.crudModalData.deliveryAddressName = selectedAddress.deliveryAddressName;
this.crudModalData.deliveryAddressLine = selectedAddress.deliveryAddressLine;
this.crudModalData.deliveryAddressPLZ = selectedAddress.deliveryAddressPLZ;
this.crudModalData.deliveryAddressCity = selectedAddress.deliveryAddressCity;
}
}
})