Rechnungen
- Artikelsuche - optional weg - Einheit weg - Einzelpreis wird nicht geholt - einzelpreis andere preistypen irgendwie anzeigen - ust weg - -1 menge erlauben - kleiner Speicher bug - mit Rechnungsadresse LS geht nicht übernehmen - pdf herunterladen Status ändern - per Mail verschicken ohne eingetragene Mail geht ned
This commit is contained in:
@@ -68,9 +68,7 @@
|
|||||||
<td style="vertical-align: top; text-align: right;">
|
<td style="vertical-align: top; text-align: right;">
|
||||||
<table style="display: inline-table; vertical-align: top;">
|
<table style="display: inline-table; vertical-align: top;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="vertical-align: top; padding-right: 10px;">
|
{{ qrCodeHtml }}
|
||||||
<img alt="QR-Code" src="{{ qrCodeSrc }}" style="display: block; height: 100%; max-height: 3.5cm; width: auto;">
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<table class="invoice-details">
|
<table class="invoice-details">
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -108,7 +108,9 @@ class ManualInvoiceController extends TTCrud
|
|||||||
"{{ leistungszeitraumHtml }}" => ($invoice->leistungszeitraum ?? '') ? "<tr><td>Leistungszeitraum:</td><td>" . htmlspecialchars($invoice->leistungszeitraum) . "</td></tr>" : "",
|
"{{ leistungszeitraumHtml }}" => ($invoice->leistungszeitraum ?? '') ? "<tr><td>Leistungszeitraum:</td><td>" . htmlspecialchars($invoice->leistungszeitraum) . "</td></tr>" : "",
|
||||||
"{{ externeReferenzHtml }}" => ($invoice->externe_referenz ?? '') ? "<tr><td>Externe Referenz:</td><td>" . htmlspecialchars($invoice->externe_referenz) . "</td></tr>" : "",
|
"{{ externeReferenzHtml }}" => ($invoice->externe_referenz ?? '') ? "<tr><td>Externe Referenz:</td><td>" . htmlspecialchars($invoice->externe_referenz) . "</td></tr>" : "",
|
||||||
"{{ vatHtml }}" => ($invoice->uid ?? '') ? "<tr><td>Ihre UID:</td><td>" . $invoice->uid . "</td></tr>" : "",
|
"{{ vatHtml }}" => ($invoice->uid ?? '') ? "<tr><td>Ihre UID:</td><td>" . $invoice->uid . "</td></tr>" : "",
|
||||||
"{{ qrCodeSrc }}" => $this->generateSepaQRCode($invoice->invoice_number ?? "VORSCHAU", round($invoice->total_gross ?? 0, 2))
|
"{{ qrCodeHtml }}" => ($invoice->total_gross ?? 0) >= 0
|
||||||
|
? '<td style="vertical-align: top; padding-right: 10px;"><img alt="QR-Code" src="' . $this->generateSepaQRCode($invoice->invoice_number ?? "VORSCHAU", round($invoice->total_gross ?? 0, 2)) . '" style="display: block; height: 100%; max-height: 3.5cm; width: auto;"></td>'
|
||||||
|
: ''
|
||||||
];
|
];
|
||||||
|
|
||||||
$headerHtml = str_replace(array_keys($replacements), array_values($replacements), file_get_contents(BASEDIR . "/Layout/default/ManualInvoice/PDF_HEADER.html"));
|
$headerHtml = str_replace(array_keys($replacements), array_values($replacements), file_get_contents(BASEDIR . "/Layout/default/ManualInvoice/PDF_HEADER.html"));
|
||||||
@@ -349,10 +351,17 @@ class ManualInvoiceController extends TTCrud
|
|||||||
$me = new User();
|
$me = new User();
|
||||||
$me->loadMe();
|
$me->loadMe();
|
||||||
|
|
||||||
// Log download in journal
|
// Update status to 'gesendet' (same as email)
|
||||||
|
if ($invoice->status === 'erstellt') {
|
||||||
|
$invoice->status = 'gesendet';
|
||||||
|
$invoice->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log download in journal with status change
|
||||||
ManualInvoiceJournalModel::create([
|
ManualInvoiceJournalModel::create([
|
||||||
'manualinvoiceId' => $id,
|
'manualinvoiceId' => $id,
|
||||||
'text' => 'Rechnung heruntergeladen',
|
'text' => 'Rechnung heruntergeladen',
|
||||||
|
'statusChange' => 'gesendet',
|
||||||
'createBy' => $me->id,
|
'createBy' => $me->id,
|
||||||
'create' => time()
|
'create' => time()
|
||||||
]);
|
]);
|
||||||
@@ -467,13 +476,20 @@ class ManualInvoiceController extends TTCrud
|
|||||||
$me = new User();
|
$me = new User();
|
||||||
$me->loadMe();
|
$me->loadMe();
|
||||||
|
|
||||||
|
// Fields that exist in ManualInvoicepositionModel
|
||||||
|
$allowedFields = ['billing_id', 'contract_id', 'matchcode', 'product_id', 'product_name', 'product_info',
|
||||||
|
'amount', 'unit', 'price', 'discount', 'price_total', 'price_gross', 'vatrate',
|
||||||
|
'fibu_cost_account', 'fibu_cost_account_legacy', 'fibu_taxcode', 'options'];
|
||||||
|
|
||||||
foreach ($this->tempPositions as $position) {
|
foreach ($this->tempPositions as $position) {
|
||||||
// Skip empty positions
|
// Skip empty positions
|
||||||
if (empty($position['product_name']) || ($position['amount'] ?? 0) == 0) continue;
|
if (empty($position['product_name']) || ($position['amount'] ?? 0) == 0) continue;
|
||||||
|
|
||||||
// Map _group to position_group
|
// Map _group to position_group
|
||||||
$groupName = $position['_group'] ?? null;
|
$groupName = $position['_group'] ?? null;
|
||||||
unset($position['_group']);
|
|
||||||
|
// Filter to only allowed fields
|
||||||
|
$filteredPosition = array_intersect_key($position, array_flip($allowedFields));
|
||||||
|
|
||||||
ManualInvoicepositionModel::create(array_merge([
|
ManualInvoicepositionModel::create(array_merge([
|
||||||
'manualinvoice_id' => $invoiceId,
|
'manualinvoice_id' => $invoiceId,
|
||||||
@@ -484,7 +500,7 @@ class ManualInvoiceController extends TTCrud
|
|||||||
'edit_by' => $me->id,
|
'edit_by' => $me->id,
|
||||||
'create' => time(),
|
'create' => time(),
|
||||||
'edit' => time()
|
'edit' => time()
|
||||||
], $position));
|
], $filteredPosition));
|
||||||
}
|
}
|
||||||
$this->tempPositions = [];
|
$this->tempPositions = [];
|
||||||
}
|
}
|
||||||
@@ -810,6 +826,15 @@ class ManualInvoiceController extends TTCrud
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse prices from cheapestSellPrice JSON
|
||||||
|
$prices = [];
|
||||||
|
if (!empty($article->cheapestSellPrice)) {
|
||||||
|
$pricesData = json_decode($article->cheapestSellPrice, true);
|
||||||
|
if (is_array($pricesData)) {
|
||||||
|
$prices = $pricesData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self::returnJson([
|
self::returnJson([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'article' => [
|
'article' => [
|
||||||
@@ -817,8 +842,10 @@ class ManualInvoiceController extends TTCrud
|
|||||||
'title' => $article->title,
|
'title' => $article->title,
|
||||||
'articleNumber' => $article->articleNumber,
|
'articleNumber' => $article->articleNumber,
|
||||||
'description' => $article->description,
|
'description' => $article->description,
|
||||||
'revenueAccount' => $article->revenueAccount
|
'revenueAccount' => $article->revenueAccount,
|
||||||
|
'unit' => $article->unit
|
||||||
],
|
],
|
||||||
|
'prices' => $prices,
|
||||||
'vatgroup_id' => $vatgroupId,
|
'vatgroup_id' => $vatgroupId,
|
||||||
'fibu_cost_account' => $vatrate->account,
|
'fibu_cost_account' => $vatrate->account,
|
||||||
'fibu_cost_account_legacy' => $vatrate->legacy_account,
|
'fibu_cost_account_legacy' => $vatrate->legacy_account,
|
||||||
|
|||||||
@@ -131,7 +131,10 @@ class WarehouseShippingNoteController extends TTCrud {
|
|||||||
// Get billing address info
|
// Get billing address info
|
||||||
$billingAddress = null;
|
$billingAddress = null;
|
||||||
if ($shippingNote->billingAddressId) {
|
if ($shippingNote->billingAddressId) {
|
||||||
$billingAddress = Address::getOne($shippingNote->billingAddressId);
|
$billingAddress = new Address($shippingNote->billingAddressId);
|
||||||
|
if (!$billingAddress->id) {
|
||||||
|
$billingAddress = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine price type ONCE (not in loop for performance)
|
// Determine price type ONCE (not in loop for performance)
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ Vue.component('manual-invoice-modal', {
|
|||||||
<tt-textarea label="Einleitender Text" v-model="invoiceData.einleitender_text" rows="3" sm row/>
|
<tt-textarea label="Einleitender Text" v-model="invoiceData.einleitender_text" rows="3" sm row/>
|
||||||
</tt-card>
|
</tt-card>
|
||||||
<tt-card><template v-slot:header><h5><i class="fas fa-list-ol mr-2"></i>Positionen</h5></template>
|
<tt-card><template v-slot:header><h5><i class="fas fa-list-ol mr-2"></i>Positionen</h5></template>
|
||||||
<tt-positions-manager group-mode ref="positionsManager" v-model="invoiceData.positions" :config="positionsConfig" @updateField-article_id="onArticleSelected" />
|
<tt-positions-manager group-mode ref="positionsManager" v-model="invoiceData.positions" :config="positionsConfig" @updateField-article_id="onArticleSelected" @updateField-price_type="onPriceTypeChanged" />
|
||||||
</tt-card>
|
</tt-card>
|
||||||
<tt-card><template v-slot:header><h5><i class="fas fa-paragraph mr-2"></i>Rabatt</h5></template>
|
<tt-card><template v-slot:header><h5><i class="fas fa-paragraph mr-2"></i>Rabatt</h5></template>
|
||||||
<tt-input label="Gesamtrabatt (%)" v-model.number="invoiceData.gesamtrabatt" sm row type="number" placeholder="0"/>
|
<tt-input label="Gesamtrabatt (%)" v-model.number="invoiceData.gesamtrabatt" sm row type="number" placeholder="0"/>
|
||||||
@@ -222,35 +222,31 @@ Vue.component('manual-invoice-modal', {
|
|||||||
positionsConfig: {
|
positionsConfig: {
|
||||||
fields: {
|
fields: {
|
||||||
article_id: {
|
article_id: {
|
||||||
type: 'autocomplete',
|
type: 'input-article',
|
||||||
label: 'Artikel (optional)',
|
label: 'Artikel',
|
||||||
apiUrl: '/WarehouseArticle/autocomplete',
|
apiUrl: '/WarehouseArticle/autocomplete',
|
||||||
customFieldReference: 'WarehouseArticle'
|
customFieldReference: 'WarehouseArticle',
|
||||||
|
emitDisplayValue: true
|
||||||
},
|
},
|
||||||
product_name: { type: 'input', label: 'Bezeichnung' },
|
product_name: { type: 'input', label: 'Bezeichnung' },
|
||||||
product_info: { type: 'input', label: 'Zusatzinfo' },
|
product_info: { type: 'input', label: 'Zusatzinfo' },
|
||||||
amount: { type: 'input', label: 'Menge', inputType: 'number' },
|
amount: { type: 'input', label: 'Menge', inputType: 'number' },
|
||||||
unit: {
|
price_type: {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
label: 'Einheit',
|
label: 'Preistyp',
|
||||||
options: [
|
options: []
|
||||||
{ value: 'Pau.', text: 'Pau.' },
|
|
||||||
{ value: 'Stk.', text: 'Stk.' },
|
|
||||||
{ value: 'h', text: 'h' },
|
|
||||||
{ value: 'm', text: 'm' }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
price: { type: 'input', label: 'Einzelpreis (€)', inputType: 'number' },
|
price: { type: 'input', label: 'Einzelpreis (€)', inputType: 'number' },
|
||||||
discount: { type: 'input', label: 'Rabatt (%)', inputType: 'number' },
|
discount: { type: 'input', label: 'Rabatt (%)', inputType: 'number' },
|
||||||
vatrate: { type: 'input', label: 'USt. (%)', inputType: 'number' },
|
|
||||||
},
|
},
|
||||||
validateForm: (d) => {
|
validateForm: (d) => {
|
||||||
if (!d.product_name) { window.notify('error', 'Bezeichnung ist erforderlich.'); return false; }
|
if (!d.product_name) { window.notify('error', 'Bezeichnung ist erforderlich.'); return false; }
|
||||||
if (!d.amount) { window.notify('error', 'Menge ist erforderlich.'); return false; }
|
if (d.amount == null || d.amount === '') { window.notify('error', 'Menge ist erforderlich.'); return false; }
|
||||||
if (d.price == null) { window.notify('error', 'Preis ist erforderlich.'); return false; }
|
if (d.price == null) { window.notify('error', 'Preis ist erforderlich.'); return false; }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
articlePrices: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -392,15 +388,21 @@ Vue.component('manual-invoice-modal', {
|
|||||||
},
|
},
|
||||||
togglePreviewVisibility() { if (!this.isLargeScreen) this.showPreviewOnSmallScreen = !this.showPreviewOnSmallScreen; },
|
togglePreviewVisibility() { if (!this.isLargeScreen) this.showPreviewOnSmallScreen = !this.showPreviewOnSmallScreen; },
|
||||||
async onArticleSelected(articleId) {
|
async onArticleSelected(articleId) {
|
||||||
if (!articleId) return;
|
if (!articleId) {
|
||||||
|
// Reset price type options when no article selected
|
||||||
|
this.articlePrices = [];
|
||||||
|
this.positionsConfig.fields.price_type.options = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const vatarea = this.customerBillingInfo.vatarea || 'domestic';
|
const vatarea = this.customerBillingInfo.vatarea || 'domestic';
|
||||||
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getArticleVatInfo?article_id=${articleId}&vatarea=${vatarea}`);
|
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getArticleVatInfo?article_id=${articleId}&vatarea=${vatarea}`);
|
||||||
if (data.success && this.$refs.positionsManager) {
|
if (data.success && this.$refs.positionsManager) {
|
||||||
const pm = this.$refs.positionsManager;
|
const pm = this.$refs.positionsManager;
|
||||||
if (data.article) {
|
if (data.article) {
|
||||||
pm.$set(pm.formData, 'product_name', data.article.title);
|
pm.$set(pm.formData, 'product_name', data.article.articleNumber + ' | ' + data.article.title);
|
||||||
pm.$set(pm.formData, 'product_info', data.article.description || '');
|
pm.$set(pm.formData, 'product_info', data.article.description || '');
|
||||||
|
pm.$set(pm.formData, 'unit', data.article.unit || 'Stk.');
|
||||||
}
|
}
|
||||||
pm.$set(pm.formData, 'vatrate', parseFloat(data.vatrate) || 20);
|
pm.$set(pm.formData, 'vatrate', parseFloat(data.vatrate) || 20);
|
||||||
pm.$set(pm.formData, 'fibu_cost_account', data.fibu_cost_account);
|
pm.$set(pm.formData, 'fibu_cost_account', data.fibu_cost_account);
|
||||||
@@ -408,11 +410,30 @@ Vue.component('manual-invoice-modal', {
|
|||||||
pm.$set(pm.formData, 'fibu_taxcode', data.fibu_taxcode);
|
pm.$set(pm.formData, 'fibu_taxcode', data.fibu_taxcode);
|
||||||
this.invoiceData.vatgroup_id = data.vatgroup_id;
|
this.invoiceData.vatgroup_id = data.vatgroup_id;
|
||||||
await this.updateTaxText(data.vatgroup_id);
|
await this.updateTaxText(data.vatgroup_id);
|
||||||
|
|
||||||
|
// Handle prices and price type selection
|
||||||
|
this.articlePrices = data.prices || [];
|
||||||
|
if (this.articlePrices.length > 0) {
|
||||||
|
const priceOptions = this.articlePrices.map(p => ({ value: p.title, text: `${p.title} (${this.formatPrice(p.price)})` }));
|
||||||
|
this.positionsConfig.fields.price_type.options = priceOptions;
|
||||||
|
// Set first price as default
|
||||||
|
pm.$set(pm.formData, 'price_type', this.articlePrices[0].title);
|
||||||
|
pm.$set(pm.formData, 'price', this.articlePrices[0].price);
|
||||||
|
} else {
|
||||||
|
this.positionsConfig.fields.price_type.options = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error fetching article VAT info:', e);
|
console.error('Error fetching article VAT info:', e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onPriceTypeChanged(priceType) {
|
||||||
|
if (!priceType || !this.articlePrices.length) return;
|
||||||
|
const selectedPrice = this.articlePrices.find(p => p.title === priceType);
|
||||||
|
if (selectedPrice && this.$refs.positionsManager) {
|
||||||
|
this.$refs.positionsManager.$set(this.$refs.positionsManager.formData, 'price', selectedPrice.price);
|
||||||
|
}
|
||||||
|
},
|
||||||
async updateTaxText(vatgroupId) {
|
async updateTaxText(vatgroupId) {
|
||||||
if (!vatgroupId) return;
|
if (!vatgroupId) return;
|
||||||
try {
|
try {
|
||||||
@@ -434,7 +455,7 @@ Vue.component('manual-invoice-modal', {
|
|||||||
this.pdfLoading = true;
|
this.pdfLoading = true;
|
||||||
try {
|
try {
|
||||||
const positions = this.invoiceData.positions
|
const positions = this.invoiceData.positions
|
||||||
.filter(p => p.product_name && (parseFloat(p.amount) || 0) > 0) // Filter out empty positions
|
.filter(p => p.product_name && (parseFloat(p.amount) || 0) !== 0) // Filter out empty positions (allow negative for Gutschrift)
|
||||||
.map(p => {
|
.map(p => {
|
||||||
const amount = parseFloat(p.amount) || 0;
|
const amount = parseFloat(p.amount) || 0;
|
||||||
const price = parseFloat(p.price) || 0;
|
const price = parseFloat(p.price) || 0;
|
||||||
@@ -546,7 +567,7 @@ Vue.component('manual-invoice-modal', {
|
|||||||
Vue.component('gutschrift-modal', {
|
Vue.component('gutschrift-modal', {
|
||||||
props: ['invoiceId'],
|
props: ['invoiceId'],
|
||||||
template: `
|
template: `
|
||||||
<tt-modal :show="true" @close="close" size="lg" title="Gutschrift erstellen">
|
<tt-modal :show="true" @update:show="close" size="lg" title="Gutschrift erstellen">
|
||||||
<div v-if="loading" class="text-center py-5"><i class="fas fa-spinner fa-spin fa-3x"></i><p class="mt-3">Lade Rechnungsdaten...</p></div>
|
<div v-if="loading" class="text-center py-5"><i class="fas fa-spinner fa-spin fa-3x"></i><p class="mt-3">Lade Rechnungsdaten...</p></div>
|
||||||
<div v-else-if="invoice">
|
<div v-else-if="invoice">
|
||||||
<div class="alert alert-info"><strong>Originalrechnung:</strong> {{ invoice.invoice_number }} - {{ invoice.customer_name }}</div>
|
<div class="alert alert-info"><strong>Originalrechnung:</strong> {{ invoice.invoice_number }} - {{ invoice.customer_name }}</div>
|
||||||
@@ -623,7 +644,7 @@ Vue.component('gutschrift-modal', {
|
|||||||
Vue.component('send-invoice-modal', {
|
Vue.component('send-invoice-modal', {
|
||||||
props: ['invoiceId'],
|
props: ['invoiceId'],
|
||||||
template: `
|
template: `
|
||||||
<tt-modal :show="true" @close="close" @submit="handleAction" :submit-text="actionButtonText" :is-loading="loading" size="md">
|
<tt-modal :show="true" @update:show="close" @submit="handleAction" :submit-text="actionButtonText" :is-loading="loading" size="md">
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h5><i class="fas fa-paper-plane mr-2"></i>Rechnung aussenden</h5>
|
<h5><i class="fas fa-paper-plane mr-2"></i>Rechnung aussenden</h5>
|
||||||
</template>
|
</template>
|
||||||
@@ -639,11 +660,11 @@ Vue.component('send-invoice-modal', {
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Aktion auswählen:</label>
|
<label>Aktion auswählen:</label>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="radio" id="action-email" value="email" v-model="selectedAction" :disabled="!invoice.email">
|
<input class="form-check-input" type="radio" id="action-email" value="email" v-model="selectedAction">
|
||||||
<label class="form-check-label" for="action-email">
|
<label class="form-check-label" for="action-email">
|
||||||
<i class="fas fa-envelope mr-2"></i>Per E-Mail versenden
|
<i class="fas fa-envelope mr-2"></i>Per E-Mail versenden
|
||||||
<span v-if="invoice.email" class="text-muted d-block ml-4">an {{ invoice.email }}</span>
|
<span v-if="invoice.email" class="text-muted d-block ml-4">Kunden-E-Mail: {{ invoice.email }}</span>
|
||||||
<span v-else class="text-danger d-block ml-4">Keine E-Mail-Adresse vorhanden</span>
|
<span v-else class="text-warning d-block ml-4"><i class="fas fa-exclamation-triangle"></i> Keine Kunden-E-Mail - bitte manuell eingeben</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check mt-2">
|
<div class="form-check mt-2">
|
||||||
@@ -653,8 +674,8 @@ Vue.component('send-invoice-modal', {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="selectedAction === 'email' && invoice.email" class="mt-3">
|
<div v-if="selectedAction === 'email'" class="mt-3">
|
||||||
<tt-input label="E-Mail-Adresse" v-model="emailAddress" sm/>
|
<tt-input label="E-Mail-Adresse" v-model="emailAddress" sm :placeholder="invoice.email ? 'Standard: ' + invoice.email : 'E-Mail-Adresse eingeben'"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</tt-modal>
|
</tt-modal>
|
||||||
@@ -678,7 +699,6 @@ Vue.component('send-invoice-modal', {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.invoice = data.invoice;
|
this.invoice = data.invoice;
|
||||||
this.emailAddress = data.invoice.email || '';
|
this.emailAddress = data.invoice.email || '';
|
||||||
this.selectedAction = data.invoice.email ? 'email' : 'download';
|
|
||||||
} else {
|
} else {
|
||||||
window.notify('error', data.message || 'Fehler beim Laden der Rechnung');
|
window.notify('error', data.message || 'Fehler beim Laden der Rechnung');
|
||||||
this.close();
|
this.close();
|
||||||
|
|||||||
Reference in New Issue
Block a user