fxied vat calculation
This commit is contained in:
@@ -52,27 +52,21 @@ Vue.component('manual-invoice', {
|
||||
},
|
||||
async handleSave(invoiceData) {
|
||||
try {
|
||||
const positions = invoiceData.positions.map(p => {
|
||||
const amount = parseFloat(p.amount) || 0;
|
||||
const price = parseFloat(p.price) || 0;
|
||||
const discount = parseFloat(p.discount) || 0;
|
||||
const vatrate = parseFloat(p.vatrate) || 0;
|
||||
const priceAfterDiscount = amount * price * (1 - discount / 100);
|
||||
return {
|
||||
...p, amount, price, discount, vatrate,
|
||||
unit: p.unit || 'Stk.',
|
||||
price_total: priceAfterDiscount,
|
||||
price_gross: priceAfterDiscount * (1 + vatrate / 100),
|
||||
product_id: p.product_id || 0,
|
||||
contract_id: p.contract_id || 0,
|
||||
billing_id: p.billing_id || null,
|
||||
matchcode: p.matchcode || null,
|
||||
fibu_cost_account: p.fibu_cost_account || null,
|
||||
fibu_cost_account_legacy: p.fibu_cost_account_legacy || null,
|
||||
fibu_taxcode: p.fibu_taxcode || null,
|
||||
options: p.options || null
|
||||
};
|
||||
});
|
||||
const positions = invoiceData.positions.map(p => ({
|
||||
...p,
|
||||
amount: parseFloat(p.amount) || 0,
|
||||
price: parseFloat(p.price) || 0,
|
||||
discount: parseFloat(p.discount) || 0,
|
||||
vatrate: parseFloat(p.vatrate) || 0,
|
||||
unit: p.unit || 'Stk.',
|
||||
warehousearticle_id: p.warehousearticle_id || p.product_id || 0,
|
||||
warehousearticle_name: p.warehousearticle_name || p.product_name || '',
|
||||
matchcode: p.matchcode || null,
|
||||
fibu_cost_account: p.fibu_cost_account || null,
|
||||
fibu_cost_account_legacy: p.fibu_cost_account_legacy || null,
|
||||
fibu_taxcode: p.fibu_taxcode || null,
|
||||
options: p.options || null
|
||||
}));
|
||||
|
||||
const payload = {
|
||||
...invoiceData,
|
||||
@@ -82,11 +76,13 @@ Vue.component('manual-invoice', {
|
||||
customer_number: invoiceData.customer_number || 0,
|
||||
country: invoiceData.country || 'Österreich',
|
||||
billing_type: invoiceData.billing_type || 'invoice',
|
||||
billing_delivery: 'email',
|
||||
fibu_payment_due: 14,
|
||||
fibu_account_number: invoiceData.fibu_account_number || 0,
|
||||
vatgroup_id: 1,
|
||||
gesamtrabatt: parseFloat(invoiceData.gesamtrabatt) || 0
|
||||
vatgroup_id: invoiceData.vatgroup_id || 1,
|
||||
performance_period: invoiceData.performance_period || invoiceData.leistungszeitraum || '',
|
||||
introductory_text: invoiceData.introductory_text || invoiceData.einleitender_text || '',
|
||||
external_reference: invoiceData.external_reference || invoiceData.externe_referenz || '',
|
||||
total_discount: parseFloat(invoiceData.total_discount || invoiceData.gesamtrabatt) || 0
|
||||
};
|
||||
|
||||
const url = invoiceData.id ? window.TT_CONFIG.UPDATE_URL : window.TT_CONFIG.CREATE_URL;
|
||||
@@ -174,15 +170,15 @@ Vue.component('manual-invoice-modal', {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tt-input label="Leistungszeitraum" v-model="invoiceData.leistungszeitraum" sm row placeholder="z.B. 01.01.2025 - 31.01.2025"/>
|
||||
<tt-input label="Externe Referenz" v-model="invoiceData.externe_referenz" sm row placeholder="z.B. Auftragsnummer, Bestellnummer"/>
|
||||
<tt-textarea label="Einleitender Text" v-model="invoiceData.einleitender_text" rows="3" sm row/>
|
||||
<tt-input label="Leistungszeitraum" v-model="invoiceData.performance_period" sm row placeholder="z.B. 01.01.2025 - 31.01.2025"/>
|
||||
<tt-input label="Externe Referenz" v-model="invoiceData.external_reference" sm row placeholder="z.B. Auftragsnummer, Bestellnummer"/>
|
||||
<tt-textarea label="Einleitender Text" v-model="invoiceData.introductory_text" rows="3" sm row/>
|
||||
</tt-card>
|
||||
<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" @updateField-price_type="onPriceTypeChanged" />
|
||||
</tt-card>
|
||||
<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.total_discount" sm row type="number" placeholder="0"/>
|
||||
</tt-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,8 +211,8 @@ Vue.component('manual-invoice-modal', {
|
||||
id: null, invoice_number: null, invoice_date: moment().format('YYYY-MM-DD'),
|
||||
billingaddress_id: null, owner_id: null, customer_number: 0, fibu_account_number: 0,
|
||||
company: '', firstname: '', lastname: '', street: '', zip: '', city: '', country: 'Österreich',
|
||||
uid: '', email: '', billing_type: 'invoice', tax_text: '',
|
||||
leistungszeitraum: '', einleitender_text: '', externe_referenz: '', gesamtrabatt: 0,
|
||||
uid: '', email: '', billing_type: 'invoice', tax_text: '', vatgroup_id: 1,
|
||||
performance_period: '', introductory_text: '', external_reference: '', total_discount: 0,
|
||||
positions: [], total: 0, total_gross: 0
|
||||
},
|
||||
positionsConfig: {
|
||||
@@ -228,7 +224,7 @@ Vue.component('manual-invoice-modal', {
|
||||
customFieldReference: 'WarehouseArticle',
|
||||
emitDisplayValue: true
|
||||
},
|
||||
product_name: { type: 'input', label: 'Bezeichnung' },
|
||||
warehousearticle_name: { type: 'input', label: 'Bezeichnung' },
|
||||
product_info: { type: 'input', label: 'Zusatzinfo' },
|
||||
amount: { type: 'input', label: 'Menge', inputType: 'number' },
|
||||
price_type: {
|
||||
@@ -240,7 +236,7 @@ Vue.component('manual-invoice-modal', {
|
||||
discount: { type: 'input', label: 'Rabatt (%)', inputType: 'number' },
|
||||
},
|
||||
validateForm: (d) => {
|
||||
if (!d.product_name) { window.notify('error', 'Bezeichnung ist erforderlich.'); return false; }
|
||||
if (!d.warehousearticle_name) { window.notify('error', 'Bezeichnung 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; }
|
||||
return true;
|
||||
@@ -261,18 +257,15 @@ Vue.component('manual-invoice-modal', {
|
||||
subtotal += lineTotal;
|
||||
});
|
||||
|
||||
// Apply gesamtrabatt
|
||||
const gesamtrabatt = parseFloat(this.invoiceData.gesamtrabatt) || 0;
|
||||
const net = subtotal * (1 - gesamtrabatt / 100);
|
||||
|
||||
// Calculate VAT
|
||||
const totalDiscount = parseFloat(this.invoiceData.total_discount) || 0;
|
||||
const net = subtotal * (1 - totalDiscount / 100);
|
||||
let vat = {}, gross = 0;
|
||||
(this.invoiceData.positions || []).forEach(p => {
|
||||
const amount = parseFloat(p.amount) || 0;
|
||||
const price = parseFloat(p.price) || 0;
|
||||
const discount = parseFloat(p.discount) || 0;
|
||||
const r = parseInt(p.vatrate) || 0;
|
||||
const lineNet = amount * price * (1 - discount / 100) * (1 - gesamtrabatt / 100);
|
||||
const lineNet = amount * price * (1 - discount / 100) * (1 - totalDiscount / 100);
|
||||
const lineVat = lineNet * (r / 100);
|
||||
vat[r] = (vat[r] || 0) + lineVat;
|
||||
gross += lineNet + lineVat;
|
||||
@@ -400,7 +393,7 @@ Vue.component('manual-invoice-modal', {
|
||||
if (data.success && this.$refs.positionsManager) {
|
||||
const pm = this.$refs.positionsManager;
|
||||
if (data.article) {
|
||||
pm.$set(pm.formData, 'product_name', data.article.articleNumber + ' | ' + data.article.title);
|
||||
pm.$set(pm.formData, 'warehousearticle_name', data.article.articleNumber + ' | ' + data.article.title);
|
||||
pm.$set(pm.formData, 'product_info', data.article.description || '');
|
||||
pm.$set(pm.formData, 'unit', data.article.unit || 'Stk.');
|
||||
}
|
||||
@@ -455,7 +448,7 @@ Vue.component('manual-invoice-modal', {
|
||||
this.pdfLoading = true;
|
||||
try {
|
||||
const positions = this.invoiceData.positions
|
||||
.filter(p => p.product_name && (parseFloat(p.amount) || 0) !== 0) // Filter out empty positions (allow negative for Gutschrift)
|
||||
.filter(p => p.warehousearticle_name && (parseFloat(p.amount) || 0) !== 0)
|
||||
.map(p => {
|
||||
const amount = parseFloat(p.amount) || 0;
|
||||
const price = parseFloat(p.price) || 0;
|
||||
@@ -524,17 +517,17 @@ Vue.component('manual-invoice-modal', {
|
||||
}
|
||||
|
||||
// Pre-fill external reference with shipping note reference
|
||||
this.invoiceData.externe_referenz = `Lieferschein #${shippingNoteData.shippingNoteId}`;
|
||||
this.invoiceData.external_reference = `Lieferschein #${shippingNoteData.shippingNoteId}`;
|
||||
|
||||
// Add introductory text if shipping note has notes
|
||||
if (shippingNoteData.note) {
|
||||
this.invoiceData.einleitender_text = shippingNoteData.note;
|
||||
this.invoiceData.introductory_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 || '',
|
||||
warehousearticle_name: position.warehousearticle_name || position.product_name || '',
|
||||
product_info: position.product_info || '',
|
||||
amount: parseFloat(position.amount) || 0,
|
||||
unit: position.unit || 'Stk.',
|
||||
@@ -583,7 +576,7 @@ Vue.component('gutschrift-modal', {
|
||||
<tbody>
|
||||
<tr v-for="(pos, index) in invoice.positions" :key="index">
|
||||
<td class="text-center"><input type="checkbox" v-model="selectedPositions[index]"></td>
|
||||
<td><strong>{{ pos.product_name }}</strong><div v-if="pos.product_info" class="text-muted small">{{ pos.product_info }}</div></td>
|
||||
<td><strong>{{ pos.warehousearticle_name }}</strong><div v-if="pos.product_info" class="text-muted small">{{ pos.product_info }}</div></td>
|
||||
<td class="text-right">{{ pos.original_amount }}</td><td class="text-right">{{ pos.credited_amount }}</td>
|
||||
<td class="text-right">{{ pos.available_amount }}</td>
|
||||
<td><input type="number" class="form-control form-control-sm" v-model.number="creditAmounts[index]" :max="pos.available_amount" :disabled="!selectedPositions[index]" step="0.001" min="0.001"></td>
|
||||
@@ -624,7 +617,7 @@ Vue.component('gutschrift-modal', {
|
||||
.map((p, i) => ({ p, i })).filter(({ i }) => this.selectedPositions[i])
|
||||
.map(({ p, i }) => {
|
||||
const amt = this.creditAmounts[i];
|
||||
if (amt > p.available_amount) throw new Error(`Menge zu hoch: ${p.product_name}`);
|
||||
if (amt > p.available_amount) throw new Error(`Menge zu hoch: ${p.warehousearticle_name}`);
|
||||
return amt > 0 ? { ...p, amount: amt } : null;
|
||||
}).filter(Boolean);
|
||||
|
||||
|
||||
@@ -437,8 +437,8 @@ Vue.component('warehouse-article-modal', {
|
||||
<div class="col-md-2" :class="{ 'wa-field-disabled': !isEditMode && !formData.category_id }">
|
||||
<tt-select
|
||||
label="Erlöskonto"
|
||||
v-model="formData.revenueAccount"
|
||||
:options="revenueAccountOptions"
|
||||
v-model="formData.vatgroup_id"
|
||||
:options="vatgroupOptions"
|
||||
required
|
||||
sm/>
|
||||
</div>
|
||||
@@ -538,7 +538,7 @@ Vue.component('warehouse-article-modal', {
|
||||
category_id: null,
|
||||
articleNumber: '',
|
||||
unit: 'Stk.',
|
||||
revenueAccount: 0,
|
||||
vatgroup_id: 2,
|
||||
warningAmount: 0,
|
||||
criticalAmount: 0,
|
||||
isSerialDocumentation: false,
|
||||
@@ -566,10 +566,10 @@ Vue.component('warehouse-article-modal', {
|
||||
{ value: 'km', text: 'km' }
|
||||
];
|
||||
},
|
||||
revenueAccountOptions() {
|
||||
vatgroupOptions() {
|
||||
return [
|
||||
{ value: 0, text: 'Dienstleistungen' },
|
||||
{ value: 1, text: 'Handelswaren' }
|
||||
{ value: 2, text: 'Dienstleistungen' },
|
||||
{ value: 3, text: 'Handelswaren' }
|
||||
];
|
||||
},
|
||||
isValid() {
|
||||
@@ -607,7 +607,7 @@ Vue.component('warehouse-article-modal', {
|
||||
category_id: data.category_id,
|
||||
articleNumber: data.articleNumber || '',
|
||||
unit: data.unit || 'Stk.',
|
||||
revenueAccount: data.revenueAccount || 0,
|
||||
vatgroup_id: data.vatgroup_id || 2,
|
||||
warningAmount: data.warningAmount || 0,
|
||||
criticalAmount: data.criticalAmount || 0,
|
||||
isSerialDocumentation: !!data.isSerialDocumentation,
|
||||
@@ -637,7 +637,7 @@ Vue.component('warehouse-article-modal', {
|
||||
category_id: null,
|
||||
articleNumber: '',
|
||||
unit: 'Stk.',
|
||||
revenueAccount: 0,
|
||||
vatgroup_id: 2,
|
||||
warningAmount: 0,
|
||||
criticalAmount: 0,
|
||||
isSerialDocumentation: false,
|
||||
|
||||
Reference in New Issue
Block a user