improved new warehouse stuff

This commit is contained in:
Luca Haid
2025-07-14 10:27:53 +02:00
parent 0ef289c828
commit 73ff3003fd
6 changed files with 698 additions and 504 deletions

View File

@@ -14,20 +14,34 @@ Vue.component('change-status-modal', {
sendEmail: false,
sendEmailViewedPDF: false,
sendEmailMail: '',
submitLoading: false
submitLoading: false,
deliveredPositions: {} // To track delivery details for each position
};
},
async mounted() {
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById`, {params: {id: this.orderId}});
// if order.status is canceled emit close event and window.notify('error', 'Bestellung wurde storniert')
if (response.data.status === 'cancelled') {
this.$emit('close');
window.notify('error', 'Bestellung wurde storniert');
}
this.order = response.data;
// Initialize deliveredPositions after fetching the order
if (this.order && this.order.positions) {
this.order.positions.forEach((pos, index) => {
this.$set(this.deliveredPositions, index, {
amount: pos.amount, // Default delivered amount to the ordered amount
reason: '',
cancelRest: false,
articleName: pos.articleName, // Store for easy access in the submit method
orderedAmount: pos.amount
});
});
}
},
computed: {
availableStatuses() {
// This computed property remains unchanged
switch (this.order.status) {
case 'new':
return [
@@ -73,6 +87,7 @@ Vue.component('change-status-modal', {
},
methods: {
async handleFileUpload(event) {
// This method remains unchanged
const files = event.target.files;
if (!files.length) return;
@@ -101,11 +116,11 @@ Vue.component('change-status-modal', {
window.notify('error', `Error uploading file "${file.name}"`);
}
}
// Clear the file input
event.target.value = '';
},
removeFile: index => this.uploadedFiles.splice(index, 1),
removeFile(index) {
this.uploadedFiles.splice(index, 1)
},
async submit() {
this.submitLoading = true;
if (this.newStatus === 'accepted' && this.sendEmail && !this.sendEmailMail) {
@@ -124,11 +139,19 @@ Vue.component('change-status-modal', {
}
const fileIds = this.uploadedFiles.map(file => file.id);
// Prepare delivery data if the status is related to delivery
let deliveryData = null;
if (this.newStatus === 'partiallyDelivered' || this.newStatus === 'fullyDelivered') {
deliveryData = this.deliveredPositions;
}
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/createNewLogAction`, {
orderId: this.order.id,
status: this.newStatus,
note: this.note,
fileIds: JSON.stringify(fileIds),
deliveryData: deliveryData // Send the new delivery data to the backend
});
if (response.data.success) {
@@ -141,6 +164,7 @@ Vue.component('change-status-modal', {
this.submitLoading = false;
},
async generateShippingNote() {
// This method remains unchanged
const positions = this.order.positions.map(position => ({
article: position.article,
amount: position.amount,
@@ -165,6 +189,7 @@ Vue.component('change-status-modal', {
return response.data.id;
},
async submitEmail(shippingNote = null) {
// This method remains unchanged
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/sendEmail?id=${this.order.id}&email=${this.sendEmailMail}${shippingNote ? '&shippingNote=' + shippingNote : ''}`);
if (response.data.success) {
@@ -175,82 +200,111 @@ Vue.component('change-status-modal', {
}
},
template: `
<tt-modal :show="true" :delete="false" @submit="submit" @update:show="$emit('close')" title="Status ändern" :save-loading="submitLoading">
<tt-loader :absolute="false" v-if="!order"/>
<template v-else>
<tt-select label="Neuer Status" v-model="newStatus" :options="availableStatuses" sm row/>
<tt-modal :show="true" :delete="false" @submit="submit" @update:show="$emit('close')" title="Status ändern" :save-loading="submitLoading">
<tt-loader :absolute="false" v-if="!order"/>
<template v-else>
<tt-select label="Neuer Status" v-model="newStatus" :options="availableStatuses" sm row/>
<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/>
<tt-textarea label="Bemerkung" v-model="note" sm/>
<div v-if="newStatus === 'partiallyDelivered' || newStatus === 'fullyDelivered'">
<h4 class="mt-3">Positionen Lieferung erfassen</h4>
<div style="display: grid; grid-template-columns: 2fr 1fr 1fr 2fr 1fr; grid-gap: 10px; font-weight: bold; margin-bottom: 10px; padding-bottom: 5px; border-bottom: 1px solid #ccc;">
<div>Artikel</div>
<div>Bestellt</div>
<div>Geliefert</div>
<div>Grund für Abweichung</div>
<div class="text-center">Rest stornieren</div>
</div>
<template v-for="(position, index) in order.positions">
<div :key="index" style="display: grid; grid-template-columns: 2fr 1fr 1fr 2fr 1fr; grid-gap: 10px; align-items: center; margin-bottom: 15px;">
<div>{{ position.articleName }}</div>
<div class="text-center">{{ position.amount }}</div>
<div>
<tt-input type="number"
:max="position.amount"
min="0"
v-model.number="deliveredPositions[index].amount"
sm
no-form-group
/>
</div>
<div>
<tt-input type="text"
v-if="deliveredPositions[index].amount < position.amount"
v-model="deliveredPositions[index].reason"
placeholder="z.B. Lieferschaden"
sm
no-form-group
/>
</div>
<div class="text-center">
<input type="checkbox"
v-if="deliveredPositions[index].amount < position.amount && deliveredPositions[index].amount !== ''"
v-model="deliveredPositions[index].cancelRest"
class="form-check-input"
style="position: relative; margin-left: auto; margin-right: auto;"
/>
</div>
</div>
</template>
</div>
<div v-if="newStatus === 'partiallyDelivered' || newStatus === 'fullyDelivered'">
<h4>Positionen</h4>
<div style="display: grid; grid-gap: 10px; grid-template-columns: 1fr 1fr 1fr;margin-top: 24px">
<div><strong>Artikel</strong></div>
<div><strong>Menge</strong></div>
<div><strong>Geliefert?</strong></div>
<template v-for="position in order.positions">
<div>{{ position.articleName }}</div>
<div>{{ position.amount }}</div>
<div><input type="checkbox" v-model="position.delivered"/></div>
</template>
</div>
</div>
<div v-if="newStatus === 'accepted'">
<h4>E-Mail verschicken?</h4>
<div v-if="newStatus === 'accepted'">
<h4>E-Mail verschicken?</h4>
<tt-button
additional-class="btn-outline-primary"
icon="fa fa-file-pdf text-danger"
text="PDF anzeigen"
@click="sendEmailViewedPDF = true;window.open(window.TT_CONFIG.BASE_PATH + '/WarehouseOrder/createPDF?id=' + order.id)"
></tt-button>
<tt-button
additional-class="btn-outline-primary"
icon="fa fa-file-pdf text-danger"
text="PDF anzeigen"
@click="sendEmailViewedPDF = true;window.open(window.TT_CONFIG.BASE_PATH + '/WarehouseOrder/createPDF?id=' + order.id)"
></tt-button>
<div class="mt-2 d-flex align-items-center">
<div class="mr-2">E-Mail senden:</div>
<label class="ios-switch-wrapper" :class="{'disabled': !sendEmailViewedPDF}">
<input
type="checkbox"
v-model="sendEmail"
:disabled="!sendEmailViewedPDF"
>
<span class="ios-switch-slider"></span>
</label>
<div v-if="!sendEmailViewedPDF" class="text-muted ml-2 text-danger">
Bitte erst PDF ansehen
</div>
</div>
<tt-input v-if="sendEmail" label="E-Mail-Adresse" v-model="sendEmailMail" sm row/>
</div>
</template>
</tt-modal>
`
<div class="mt-2 d-flex align-items-center">
<div class="mr-2">E-Mail senden:</div>
<label class="ios-switch-wrapper" :class="{'disabled': !sendEmailViewedPDF}">
<input
type="checkbox"
v-model="sendEmail"
:disabled="!sendEmailViewedPDF"
>
<span class="ios-switch-slider"></span>
</label>
<div v-if="!sendEmailViewedPDF" class="text-muted ml-2 text-danger">
Bitte erst PDF ansehen
</div>
</div>
<tt-input v-if="sendEmail" label="E-Mail-Adresse" v-model="sendEmailMail" sm row/>
</div>
</template>
</tt-modal>
`
});
// The other components in WarehouseOrder.js (warehouse-order-modal, tt-file, etc.) remain unchanged.
// I am including them here to provide the full file content.
Vue.component('warehouse-order-modal', {
props: {
id: {type: [String, Number], required: true},
@@ -527,48 +581,46 @@ Vue.component('tt-file', {
Vue.component('warehouse-order-detail', {
template: `
<tt-card>
<template v-slot:header><h4>Bestellungsdetails für #{{ loading ? 'Laden...' : order.orderNumber }}</h4></template>
<template v-if="loading">
<div class="d-flex justify-content-center align-items-center">
<div class="spinner-border spinner-border-sm text-primary" role="status"><span class="sr-only">Loading...</span></div>
</div>
</template>
<template v-else>
<h3>Positionen</h3>
<div class="grid-container header">
<div v-for="header in ['Artikel', 'Menge', 'Preis', 'Lieferant', 'Verwendung', 'Summe']"><strong>{{ header }}</strong></div>
</div>
<div class="grid-container" v-for="p in order.positions">
<div>{{ p.articleName }}</div>
<div>{{ p.amount }}</div>
<div>{{ p.buyPrice }}</div>
<div>{{ p.distributorName }}</div>
<div>{{ p.verwendung }}</div>
<div>{{ p.amount * p.buyPrice }}</div>
</div>
<template v-if="orderLog?.length > 0">
<hr>
<h3>Log</h3>
<div v-for="log in orderLog">
{{ formatDate(log.create) }} ({{ getUserName(log.createBy) }}) | {{ log.message }}
<!-- if log.fileIds exists and it is a array of ids use <tt-file :id=> to show the file-->
<tt-card>
<template v-slot:header><h4>Bestellungsdetails für #{{ loading ? 'Laden...' : order.orderNumber }}</h4></template>
<template v-if="loading">
<div class="d-flex justify-content-center align-items-center">
<div class="spinner-border spinner-border-sm text-primary" role="status"><span class="sr-only">Loading...</span></div>
</div>
</template>
<template v-else>
<h3>Positionen</h3>
<div class="grid-container header">
<div v-for="header in ['Artikel', 'Menge', 'Preis', 'Lieferant', 'Verwendung', 'Summe']"><strong>{{ header }}</strong></div>
</div>
<div class="grid-container" v-for="p in order.positions">
<div>{{ p.articleName }}</div>
<div>{{ p.amount }}</div>
<div>{{ p.buyPrice }}</div>
<div>{{ p.distributorName }}</div>
<div>{{ p.verwendung }}</div>
<div>{{ p.amount * p.buyPrice }}</div>
</div>
<template v-if="orderLog?.length > 0">
<hr>
<h3>Log</h3>
<div v-for="log in orderLog">
{{ 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>
<template v-if="log.fileIds">
<div v-for="file in JSON.parse(log.fileIds)">
<tt-file :id="file"/>
</div>
</template>
</div>
</template>
<hr>
<h3>Lieferadresse</h3>
<div v-for="field in ['delAddrName', 'delAddrEMail', 'delAddrLine']">{{ order[field] }}</div>
<div>{{ order.delAddrPLZ }} {{ order.delAddrCity }}</div>
</template>
</tt-card>
`,
</div>
</template>
<hr>
<h3>Lieferadresse</h3>
<div v-for="field in ['delAddrName', 'delAddrEMail', 'delAddrLine']">{{ order[field] }}</div>
<div>{{ order.delAddrPLZ }} {{ order.delAddrCity }}</div>
</template>
</tt-card>
`,
props: ['id'],
data: () => ({order: {}, orderLog: null, loading: true}),
async mounted() {
@@ -600,7 +652,8 @@ Vue.component('warehouse-order', {
<warehouse-order-detail :id="row.id"/>
</template>
<template v-slot:sum="{ row }">{{ calculateSum(JSON.parse(row["positions"])).toFixed(2) }} €</template>
</tt-table-crud> </tt-card>
</tt-table-crud>
</tt-card>
`,
data: () => ({
orderModalId: null,
@@ -619,4 +672,4 @@ Vue.component('warehouse-order', {
calculateSum: positions => positions.reduce((sum, {amount, buyPrice}) => sum + amount * buyPrice, 0),
openPDF: order => window.open(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/createPDF?id=${order.id}`)
}
});
});