Add shipping note import to manual invoice

This commit is contained in:
2025-12-09 12:23:53 +01:00
parent 7035f01fad
commit 2690451f0b
3 changed files with 322 additions and 3 deletions
+112 -3
View File
@@ -16,12 +16,29 @@ Vue.component('manual-invoice', {
<button class="btn btn-sm btn-primary" @click="downloadPdf(row.id)" title="PDF herunterladen"><i class="fas fa-file-pdf"></i></button>
</template>
</tt-table-crud>
<manual-invoice-modal v-if="isModalOpen" :initial-data="editingInvoiceData" @close="closeModal" @save="handleSave"/>
<manual-invoice-modal v-if="isModalOpen" :initial-data="editingInvoiceData" :shipping-note-import="shippingNoteImportData" @close="closeModal" @save="handleSave"/>
<gutschrift-modal v-if="isGutschriftModalOpen" :invoice-id="gutschriftInvoiceId" @close="closeGutschriftModal" @created="handleGutschriftCreated"/>
<send-invoice-modal v-if="isSendModalOpen" :invoice-id="sendInvoiceId" @close="closeSendModal" @sent="handleInvoiceSent"/>
</tt-card>
`,
data: () => ({ isModalOpen: false, editingInvoiceData: null, isGutschriftModalOpen: false, gutschriftInvoiceId: null, isSendModalOpen: false, sendInvoiceId: null }),
data: () => ({ isModalOpen: false, editingInvoiceData: null, shippingNoteImportData: null, isGutschriftModalOpen: false, gutschriftInvoiceId: null, isSendModalOpen: false, sendInvoiceId: null }),
mounted() {
// Check for shipping note import data
const shippingNoteData = localStorage.getItem('ManualInvoice_create');
if (shippingNoteData) {
try {
// Parse and store the data
this.shippingNoteImportData = JSON.parse(shippingNoteData);
// Delete from localStorage immediately so it doesn't auto-open again on reload
localStorage.removeItem('ManualInvoice_create');
// Auto-open modal for import
this.openModal();
} catch (e) {
console.error('Error parsing shipping note data:', e);
localStorage.removeItem('ManualInvoice_create');
}
}
},
methods: {
openModal(invoice = null) {
this.editingInvoiceData = invoice ? JSON.parse(JSON.stringify(invoice)) : null;
@@ -30,6 +47,7 @@ Vue.component('manual-invoice', {
closeModal() {
this.isModalOpen = false;
this.editingInvoiceData = null;
this.shippingNoteImportData = null;
this.$refs.table.$refs.table.refreshTable();
},
async handleSave(invoiceData) {
@@ -126,7 +144,7 @@ Vue.component('manual-invoice', {
});
Vue.component('manual-invoice-modal', {
props: ['initialData'],
props: ['initialData', 'shippingNoteImport'],
template: `
<div class="manual-invoice-overlay" :class="overlayClasses" tabindex="-1" ref="overlay">
<div class="info-bar" v-if="!isLargeScreen"><i class="fas fa-info-circle mr-2"></i> Drücke <strong>STRG + Q</strong> um die Vorschau umzuschalten.</div>
@@ -278,6 +296,16 @@ Vue.component('manual-invoice-modal', {
}
if (!Array.isArray(this.invoiceData.positions)) this.invoiceData.positions = [];
}
// Check for shipping note import data from prop
if (this.shippingNoteImport && Array.isArray(this.shippingNoteImport) && this.shippingNoteImport.length > 0) {
try {
this.processShippingNoteImport(this.shippingNoteImport);
} catch (e) {
console.error('Error processing shipping note import:', e);
window.notify('error', 'Fehler beim Importieren des Lieferscheins');
}
}
},
mounted() {
window.addEventListener('resize', this.handleResize);
@@ -334,6 +362,87 @@ Vue.component('manual-invoice-modal', {
} finally {
this.pdfLoading = false;
}
},
processShippingNoteImport(shippingNoteDataArray) {
// Temporarily disable the preview update during import to prevent memory leak
clearTimeout(this.previewDebounceTimer);
const originalWatcher = this.$options.watch['invoiceData'];
delete this.$options.watch['invoiceData'];
try {
for (const shippingNoteData of shippingNoteDataArray) {
// Pre-fill billing address fields
if (shippingNoteData.billingAddress) {
const addr = shippingNoteData.billingAddress;
Object.assign(this.invoiceData, {
billingaddress_id: addr.id,
customer_number: addr.customer_number || 0,
company: addr.company || '',
firstname: addr.firstname || '',
lastname: addr.lastname || '',
street: addr.street || '',
zip: addr.zip || '',
city: addr.city || '',
email: addr.email || '',
uid: addr.uid || '',
fibu_account_number: addr.fibu_account_number || 0,
fibu_payment_due: addr.fibu_payment_due || 14,
fibu_payment_skonto: addr.fibu_payment_skonto || 0,
fibu_payment_skonto_rate: addr.fibu_payment_skonto_rate || 0,
billing_type: addr.billing_type || 'invoice',
owner_id: addr.id
});
// Banking info (if SEPA)
if (addr.billing_type === 'sepa') {
Object.assign(this.invoiceData, {
bank_account_bank: addr.bank_account_bank || '',
bank_account_owner: addr.bank_account_owner || '',
bank_account_iban: addr.bank_account_iban || '',
bank_account_bic: addr.bank_account_bic || '',
sepa_date: addr.sepa_date || ''
});
}
}
// Pre-fill external reference with shipping note reference
this.invoiceData.externe_referenz = `Lieferschein #${shippingNoteData.shippingNoteId}`;
// Add introductory text if shipping note has notes
if (shippingNoteData.note) {
this.invoiceData.einleitender_text = shippingNoteData.note;
}
// Add all positions (batch operation to avoid triggering watcher for each item)
if (shippingNoteData.positions && Array.isArray(shippingNoteData.positions)) {
const newPositions = shippingNoteData.positions.map(position => ({
product_name: position.product_name || '',
product_info: position.product_info || '',
amount: parseFloat(position.amount) || 0,
unit: position.unit || 'Stk.',
price: parseFloat(position.price) || 0,
discount: parseFloat(position.discount) || 0,
vatrate: parseFloat(position.vatrate) || 20
}));
// Add all positions at once instead of one by one
this.invoiceData.positions.push(...newPositions);
}
}
// Notify user
const positionCount = shippingNoteDataArray.reduce((sum, sn) => sum + (sn.positions?.length || 0), 0);
window.notify('success', `Lieferschein erfolgreich importiert (${positionCount} Position(en))`);
} finally {
// Re-enable the watcher
this.$options.watch['invoiceData'] = originalWatcher;
// Trigger one preview update after import is complete
this.$nextTick(() => {
this.debouncedPreviewUpdate();
});
}
}
}
});
@@ -44,6 +44,12 @@ window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [
"key": "print",
"title": "Drucken",
"class": "fas fa-print text-primary",
},
{
"key": "createManualInvoice",
"title": "Rechnung erstellen",
"class": "fas fa-file-invoice text-primary",
"condition": (row) => row.status === 'accepted',
}
]
@@ -547,6 +553,7 @@ Vue.component('warehouse-shipping-note', {
@status_to_cancelled="changeStatus($event.id, 'cancelled')"
@status_to_new="changeStatus($event.id, 'new')"
@add_log="addLogModalId = $event.id"
@createManualInvoice="createManualInvoice($event)"
@edit="shippingNoteModalId = $event.id"
ref="table">
@@ -678,6 +685,30 @@ Vue.component('warehouse-shipping-note', {
this.window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten');
}
},
async createManualInvoice(row) {
try {
// Fetch shipping note with enriched article data
const res = await axios.get(
`${window.TT_CONFIG.BASE_PATH}/WarehouseShippingNote/getShippingNoteForInvoice`,
{ params: { id: row.id } }
);
if (!res.data.success) {
window.notify('error', res.data.message || 'Fehler beim Laden der Lieferscheindaten');
return;
}
// Store in localStorage as array (to match WarehouseOrder pattern)
localStorage.setItem('ManualInvoice_create', JSON.stringify([res.data.data]));
// Navigate to ManualInvoice module
window.location.href = `${window.TT_CONFIG.BASE_PATH}/ManualInvoice`;
} catch (error) {
console.error('Error creating manual invoice:', error);
window.notify('error', 'Fehler beim Erstellen der Rechnung');
}
},
}
})