fxied vat calculation

This commit is contained in:
Luca Haid
2026-01-26 15:44:35 +01:00
parent 6f01440bc9
commit e8002d184f
10 changed files with 224 additions and 124 deletions

View File

@@ -17,7 +17,7 @@ foreach($invoice->positions as $p) {
} }
} }
$gesamtrabatt = $invoice->gesamtrabatt ?? 0; $total_discount = $invoice->total_discount ?? 0;
$subtotal = 0; $subtotal = 0;
foreach($invoice->positions as $p) { foreach($invoice->positions as $p) {
$subtotal += $p->price_total ?? 0; $subtotal += $p->price_total ?? 0;
@@ -127,8 +127,8 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
<h2 style="text-align: center;color: #005384">Ihre Xinon <?=($is_credit) ? "Gutschrift" : "Rechnung"?> vom <?=date("d.m.Y",$invoice->invoice_date)?></h2> <h2 style="text-align: center;color: #005384">Ihre Xinon <?=($is_credit) ? "Gutschrift" : "Rechnung"?> vom <?=date("d.m.Y",$invoice->invoice_date)?></h2>
<?php if($invoice->einleitender_text ?? ''): ?> <?php if($invoice->introductory_text ?? ''): ?>
<p style="margin-top: 10pt; margin-bottom: 20pt; text-align: center; font-weight: bold;"><?=nl2br(htmlspecialchars($invoice->einleitender_text))?></p> <p style="margin-top: 10pt; margin-bottom: 20pt; text-align: center; font-weight: bold;"><?=nl2br(htmlspecialchars($invoice->introductory_text))?></p>
<?php endif; ?> <?php endif; ?>
<table style="border-collapse: collapse; width: 100%;" id="invoiceTable"> <table style="border-collapse: collapse; width: 100%;" id="invoiceTable">
@@ -166,7 +166,7 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
<tr class="position <?=($i%2 == 0) ? "even" : "uneven" ?>"> <tr class="position <?=($i%2 == 0) ? "even" : "uneven" ?>">
<td style="padding-left: 4pt; vertical-align: top;"> <td style="padding-left: 4pt; vertical-align: top;">
<?=htmlspecialchars($p->product_name ?? '')?> <?=htmlspecialchars($p->warehousearticle_name ?? '')?>
<?php if(isset($p->product_info) && $p->product_info): ?> <?php if(isset($p->product_info) && $p->product_info): ?>
<div style="padding-left: 12pt; font-size: 10px; color: #666;"><?=htmlspecialchars($p->product_info)?></div> <div style="padding-left: 12pt; font-size: 10px; color: #666;"><?=htmlspecialchars($p->product_info)?></div>
<?php endif; ?> <?php endif; ?>
@@ -186,17 +186,17 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
endforeach; endforeach;
endforeach; endforeach;
?> ?>
<?php if($gesamtrabatt > 0): ?> <?php if($total_discount > 0): ?>
<tr style="background-color: #ebebeb; border-top: 2px solid black;"> <tr style="background-color: #ebebeb; border-top: 2px solid black;">
<td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Zwischensumme:</td> <td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Zwischensumme:</td>
<td colspan="2" style="text-align: right; padding-right: 4pt;"><?=number_format($subtotal, 2, ",","."). " €"?></td> <td colspan="2" style="text-align: right; padding-right: 4pt;"><?=number_format($subtotal, 2, ",","."). " €"?></td>
</tr> </tr>
<tr style="background-color: #ebebeb; border-bottom: 1px solid #ccc;"> <tr style="background-color: #ebebeb; border-bottom: 1px solid #ccc;">
<td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Gesamtrabatt <?=number_format($gesamtrabatt, 2, ",", ".")?>%:</td> <td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Gesamtrabatt <?=number_format($total_discount, 2, ",", ".")?>%:</td>
<td colspan="2" style="text-align: right; padding-right: 4pt; color: #d32f2f;">-<?=number_format($subtotal * ($gesamtrabatt / 100), 2, ",","."). " €"?></td> <td colspan="2" style="text-align: right; padding-right: 4pt; color: #d32f2f;">-<?=number_format($subtotal * ($total_discount / 100), 2, ",","."). " €"?></td>
</tr> </tr>
<?php endif; ?> <?php endif; ?>
<tr style="font-weight: bold; background-color: #ebebeb; border-bottom: 1px solid black;<?=($gesamtrabatt > 0) ? '' : 'border-top: 2px solid black;'?>"> <tr style="font-weight: bold; background-color: #ebebeb; border-bottom: 1px solid black;<?=($total_discount > 0) ? '' : 'border-top: 2px solid black;'?>">
<td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Gesamt Netto:</td> <td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Gesamt Netto:</td>
<td colspan="2" style="text-align: right; padding-right: 4pt;"><?=number_format($net_total, 2, ",","."). " €"?></td> <td colspan="2" style="text-align: right; padding-right: 4pt;"><?=number_format($net_total, 2, ",","."). " €"?></td>
</tr> </tr>

View File

@@ -105,8 +105,8 @@ class ManualInvoiceController extends TTCrud
"{{ billingAccount }}" => $invoice->fibu_account_number ?? '', "{{ billingAccount }}" => $invoice->fibu_account_number ?? '',
"{{ invoiceNumber }}" => $invoice->invoice_number ?? "VORSCHAU", "{{ invoiceNumber }}" => $invoice->invoice_number ?? "VORSCHAU",
"{{ invoiceDate }}" => date("d.m.Y", $invoice->invoice_date ?? time()), "{{ invoiceDate }}" => date("d.m.Y", $invoice->invoice_date ?? time()),
"{{ leistungszeitraumHtml }}" => ($invoice->leistungszeitraum ?? '') ? "<tr><td>Leistungszeitraum:</td><td>" . htmlspecialchars($invoice->leistungszeitraum) . "</td></tr>" : "", "{{ leistungszeitraumHtml }}" => ($invoice->performance_period ?? '') ? "<tr><td>Leistungszeitraum:</td><td>" . htmlspecialchars($invoice->performance_period) . "</td></tr>" : "",
"{{ externeReferenzHtml }}" => ($invoice->externe_referenz ?? '') ? "<tr><td>Externe Referenz:</td><td>" . htmlspecialchars($invoice->externe_referenz) . "</td></tr>" : "", "{{ externeReferenzHtml }}" => ($invoice->external_reference ?? '') ? "<tr><td>Externe Referenz:</td><td>" . htmlspecialchars($invoice->external_reference) . "</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>" : "",
"{{ qrCodeHtml }}" => ($invoice->total_gross ?? 0) >= 0 "{{ 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>' ? '<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>'
@@ -393,11 +393,32 @@ class ManualInvoiceController extends TTCrud
$data['status'] = 'erstellt'; $data['status'] = 'erstellt';
$data['fibu_payment_skonto'] = $data['fibu_payment_skonto'] ?? 0; $data['fibu_payment_skonto'] = $data['fibu_payment_skonto'] ?? 0;
$data['fibu_payment_skonto_rate'] = $data['fibu_payment_skonto_rate'] ?? 0; $data['fibu_payment_skonto_rate'] = $data['fibu_payment_skonto_rate'] ?? 0;
$data['gesamtrabatt'] = $data['gesamtrabatt'] ?? 0;
$data['total_discount'] = $data['total_discount'] ?? $data['gesamtrabatt'] ?? 0;
$data['performance_period'] = $data['performance_period'] ?? $data['leistungszeitraum'] ?? null;
$data['introductory_text'] = $data['introductory_text'] ?? $data['einleitender_text'] ?? null;
$data['external_reference'] = $data['external_reference'] ?? $data['externe_referenz'] ?? null;
unset($data['gesamtrabatt'], $data['leistungszeitraum'], $data['einleitender_text'], $data['externe_referenz'], $data['billing_delivery']);
$data['total'] = $data['total'] ?? 0; $data['total'] = $data['total'] ?? 0;
$data['total_gross'] = $data['total_gross'] ?? 0; $data['total_gross'] = $data['total_gross'] ?? 0;
$data['lock'] = 0; $data['lock'] = 0;
$data['exported'] = 0; $data['exported'] = 0;
if (($data['billing_type'] ?? '') === 'sepa' && ($data['billingaddress_id'] ?? null)) {
$address = new Address($data['billingaddress_id']);
if ($address->id) {
$data['bank_account_bank'] = $address->bank_account_bank;
$data['bank_account_owner'] = $address->bank_account_owner;
$data['bank_account_iban'] = str_replace(' ', '', $address->bank_account_iban ?? '');
$data['bank_account_bic'] = str_replace(' ', '', $address->bank_account_bic ?? '');
if ($address->sepa_date) {
$data['sepa_date'] = date('Y-m-d', $address->sepa_date);
}
$data['sepa_id'] = 'R' . ($data['fibu_account_number'] ?? '');
}
}
$data['create_by'] = $me->id; $data['create_by'] = $me->id;
$data['edit_by'] = $me->id; $data['edit_by'] = $me->id;
$data['create'] = time(); $data['create'] = time();
@@ -476,31 +497,40 @@ 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 $articleName = $position['warehousearticle_name'] ?? $position['product_name'] ?? '';
if (empty($position['product_name']) || ($position['amount'] ?? 0) == 0) continue; if (empty($articleName) || ($position['amount'] ?? 0) == 0) continue;
// Map _group to position_group $amount = floatval($position['amount']);
$groupName = $position['_group'] ?? null; $price = floatval($position['price']);
$discount = floatval($position['discount'] ?? 0);
$vatrate = floatval($position['vatrate'] ?? 0);
$priceAfterDiscount = $amount * $price * (1 - $discount / 100);
$priceGross = $priceAfterDiscount * (1 + $vatrate / 100);
// Filter to only allowed fields ManualInvoicepositionModel::create([
$filteredPosition = array_intersect_key($position, array_flip($allowedFields));
ManualInvoicepositionModel::create(array_merge([
'manualinvoice_id' => $invoiceId, 'manualinvoice_id' => $invoiceId,
'position_group' => $groupName, 'position_group' => $position['_group'] ?? null,
'unit' => 'Stk.', 'matchcode' => $position['matchcode'] ?? null,
'discount' => 0, 'warehousearticle_id' => $position['warehousearticle_id'] ?? $position['product_id'] ?? 0,
'warehousearticle_name' => $articleName,
'product_info' => $position['product_info'] ?? '',
'amount' => $amount,
'unit' => $position['unit'] ?? 'Stk.',
'price' => $price,
'discount' => $discount,
'price_total' => $priceAfterDiscount,
'price_gross' => $priceGross,
'vatrate' => $vatrate,
'fibu_cost_account' => $position['fibu_cost_account'] ?? null,
'fibu_cost_account_legacy' => $position['fibu_cost_account_legacy'] ?? null,
'fibu_taxcode' => $position['fibu_taxcode'] ?? null,
'options' => $position['options'] ?? null,
'create_by' => $me->id, 'create_by' => $me->id,
'edit_by' => $me->id, 'edit_by' => $me->id,
'create' => time(), 'create' => time(),
'edit' => time() 'edit' => time()
], $filteredPosition)); ]);
} }
$this->tempPositions = []; $this->tempPositions = [];
} }
@@ -510,17 +540,13 @@ class ManualInvoiceController extends TTCrud
$positions = ManualInvoicepositionModel::search(['manualinvoice_id' => $invoiceId]); $positions = ManualInvoicepositionModel::search(['manualinvoice_id' => $invoiceId]);
$subtotal = array_sum(array_column($positions, 'price_total')); $subtotal = array_sum(array_column($positions, 'price_total'));
$totalDiscount = $invoice->total_discount ?? 0;
$netTotal = $subtotal * (1 - $totalDiscount / 100);
// Apply gesamtrabatt (total discount) if exists
$gesamtrabatt = $invoice->gesamtrabatt ?? 0;
$discountAmount = $subtotal * ($gesamtrabatt / 100);
$netTotal = $subtotal - $discountAmount;
// Calculate gross total with VAT applied after discount
$grossTotal = 0; $grossTotal = 0;
foreach ($positions as $pos) { foreach ($positions as $pos) {
$positionNet = $pos->price_total; $positionNet = $pos->price_total;
$positionAfterDiscount = $positionNet * (1 - $gesamtrabatt / 100); $positionAfterDiscount = $positionNet * (1 - $totalDiscount / 100);
$grossTotal += $positionAfterDiscount * (1 + $pos->vatrate / 100); $grossTotal += $positionAfterDiscount * (1 + $pos->vatrate / 100);
} }
@@ -582,11 +608,11 @@ class ManualInvoiceController extends TTCrud
'id' => $pos->id, 'id' => $pos->id,
'manualinvoice_id' => $pos->manualinvoice_id, 'manualinvoice_id' => $pos->manualinvoice_id,
'_group' => $pos->position_group ?? '', '_group' => $pos->position_group ?? '',
'billing_id' => $pos->billing_id,
'contract_id' => $pos->contract_id,
'matchcode' => $pos->matchcode, 'matchcode' => $pos->matchcode,
'product_id' => $pos->product_id, 'warehousearticle_id' => $pos->warehousearticle_id,
'product_name' => $pos->product_name, 'warehousearticle_name' => $pos->warehousearticle_name,
'product_id' => $pos->warehousearticle_id,
'product_name' => $pos->warehousearticle_name,
'product_info' => $pos->product_info, 'product_info' => $pos->product_info,
'amount' => $pos->amount, 'amount' => $pos->amount,
'unit' => $pos->unit ?? 'Stk.', 'unit' => $pos->unit ?? 'Stk.',
@@ -632,19 +658,20 @@ class ManualInvoiceController extends TTCrud
foreach ($existingCredits as $credit) { foreach ($existingCredits as $credit) {
foreach ($credit->getProperty('positions') as $creditPos) { foreach ($credit->getProperty('positions') as $creditPos) {
$key = $creditPos->product_id . '_' . $creditPos->matchcode; $key = $creditPos->warehousearticle_id . '_' . $creditPos->matchcode;
$creditedAmounts[$key] = ($creditedAmounts[$key] ?? 0) + abs($creditPos->amount); $creditedAmounts[$key] = ($creditedAmounts[$key] ?? 0) + abs($creditPos->amount);
} }
} }
$availablePositions = []; $availablePositions = [];
foreach ($positions as $pos) { foreach ($positions as $pos) {
$key = $pos->product_id . '_' . $pos->matchcode; $key = $pos->warehousearticle_id . '_' . $pos->matchcode;
$availableAmount = $pos->amount - ($creditedAmounts[$key] ?? 0); $availableAmount = $pos->amount - ($creditedAmounts[$key] ?? 0);
if ($availableAmount > 0) { if ($availableAmount > 0) {
$availablePositions[] = [ $availablePositions[] = [
'id' => $pos->id, 'id' => $pos->id,
'product_name' => $pos->product_name, 'warehousearticle_name' => $pos->warehousearticle_name,
'product_name' => $pos->warehousearticle_name,
'product_info' => $pos->product_info, 'product_info' => $pos->product_info,
'original_amount' => $pos->amount, 'original_amount' => $pos->amount,
'credited_amount' => $creditedAmounts[$key] ?? 0, 'credited_amount' => $creditedAmounts[$key] ?? 0,
@@ -652,7 +679,8 @@ class ManualInvoiceController extends TTCrud
'unit' => $pos->unit ?? 'Stk.', 'unit' => $pos->unit ?? 'Stk.',
'price' => $pos->price, 'price' => $pos->price,
'vatrate' => $pos->vatrate, 'vatrate' => $pos->vatrate,
'product_id' => $pos->product_id, 'warehousearticle_id' => $pos->warehousearticle_id,
'product_id' => $pos->warehousearticle_id,
'matchcode' => $pos->matchcode, 'matchcode' => $pos->matchcode,
'fibu_cost_account' => $pos->fibu_cost_account, 'fibu_cost_account' => $pos->fibu_cost_account,
'fibu_taxcode' => $pos->fibu_taxcode 'fibu_taxcode' => $pos->fibu_taxcode
@@ -692,10 +720,10 @@ class ManualInvoiceController extends TTCrud
$invoiceData = [ $invoiceData = [
'invoice_number' => ManualInvoiceModel::getNextInvoiceNumber(), 'invoice_number' => ManualInvoiceModel::getNextInvoiceNumber(),
'invoice_date' => time(), 'invoice_date' => time(),
'leistungszeitraum' => $originalInvoice->leistungszeitraum ?? null, 'performance_period' => $originalInvoice->performance_period ?? null,
'einleitender_text' => 'Gutschrift zur Rechnung ' . $originalInvoice->invoice_number, 'introductory_text' => 'Gutschrift zur Rechnung ' . $originalInvoice->invoice_number,
'externe_referenz' => $originalInvoice->externe_referenz ?? null, 'external_reference' => $originalInvoice->external_reference ?? null,
'gesamtrabatt' => 0, 'total_discount' => 0,
'owner_id' => $originalInvoice->owner_id, 'owner_id' => $originalInvoice->owner_id,
'billingaddress_id' => $originalInvoice->billingaddress_id, 'billingaddress_id' => $originalInvoice->billingaddress_id,
'customer_number' => $originalInvoice->customer_number, 'customer_number' => $originalInvoice->customer_number,
@@ -721,7 +749,6 @@ class ManualInvoiceController extends TTCrud
'email' => $originalInvoice->email, 'email' => $originalInvoice->email,
'uid' => $originalInvoice->uid, 'uid' => $originalInvoice->uid,
'billing_type' => $originalInvoice->billing_type, 'billing_type' => $originalInvoice->billing_type,
'billing_delivery' => $originalInvoice->billing_delivery,
'bank_account_bank' => $originalInvoice->bank_account_bank, 'bank_account_bank' => $originalInvoice->bank_account_bank,
'bank_account_owner' => $originalInvoice->bank_account_owner, 'bank_account_owner' => $originalInvoice->bank_account_owner,
'bank_account_iban' => $originalInvoice->bank_account_iban, 'bank_account_iban' => $originalInvoice->bank_account_iban,
@@ -749,8 +776,8 @@ class ManualInvoiceController extends TTCrud
ManualInvoicepositionModel::create([ ManualInvoicepositionModel::create([
'manualinvoice_id' => $creditInvoiceId, 'manualinvoice_id' => $creditInvoiceId,
'position_group' => null, 'position_group' => null,
'product_id' => $pos['product_id'], 'warehousearticle_id' => $pos['warehousearticle_id'] ?? $pos['product_id'] ?? 0,
'product_name' => $pos['product_name'], 'warehousearticle_name' => $pos['warehousearticle_name'] ?? $pos['product_name'] ?? '',
'product_info' => $pos['product_info'] ?? '', 'product_info' => $pos['product_info'] ?? '',
'amount' => -abs($pos['amount']), 'amount' => -abs($pos['amount']),
'unit' => $pos['unit'] ?? 'Stk.', 'unit' => $pos['unit'] ?? 'Stk.',
@@ -762,8 +789,6 @@ class ManualInvoiceController extends TTCrud
'matchcode' => $pos['matchcode'] ?? null, 'matchcode' => $pos['matchcode'] ?? null,
'fibu_cost_account' => $pos['fibu_cost_account'] ?? null, 'fibu_cost_account' => $pos['fibu_cost_account'] ?? null,
'fibu_taxcode' => $pos['fibu_taxcode'] ?? null, 'fibu_taxcode' => $pos['fibu_taxcode'] ?? null,
'contract_id' => 0,
'billing_id' => null,
'create_by' => $me->id, 'create_by' => $me->id,
'edit_by' => $me->id, 'edit_by' => $me->id,
'create' => time(), 'create' => time(),
@@ -813,12 +838,7 @@ class ManualInvoiceController extends TTCrud
return; return;
} }
// Map revenueAccount to vatgroup_id $vatgroupId = $article->vatgroup_id;
// revenueAccount 0 = Dienstleistungen = vatgroup_id 2
// revenueAccount 1 = Handelswaren = vatgroup_id 3
$vatgroupId = $article->revenueAccount == 0 ? 2 : 3;
// Get vatrate for this vatgroup and area
$vatrate = VatrateModel::getFirst(['vatgroup_id' => $vatgroupId, 'area' => $vatarea]); $vatrate = VatrateModel::getFirst(['vatgroup_id' => $vatgroupId, 'area' => $vatarea]);
if (!$vatrate) { if (!$vatrate) {
@@ -826,7 +846,6 @@ class ManualInvoiceController extends TTCrud
return; return;
} }
// Parse prices from cheapestSellPrice JSON
$prices = []; $prices = [];
if (!empty($article->cheapestSellPrice)) { if (!empty($article->cheapestSellPrice)) {
$pricesData = json_decode($article->cheapestSellPrice, true); $pricesData = json_decode($article->cheapestSellPrice, true);
@@ -842,7 +861,7 @@ 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, 'vatgroup_id' => $article->vatgroup_id,
'unit' => $article->unit 'unit' => $article->unit
], ],
'prices' => $prices, 'prices' => $prices,

View File

@@ -4,10 +4,10 @@ class ManualInvoiceModel extends TTCrudBaseModel {
public int $id; public int $id;
public ?string $invoice_number; public ?string $invoice_number;
public int $invoice_date; public int $invoice_date;
public ?string $leistungszeitraum; public ?string $performance_period;
public ?string $einleitender_text; public ?string $introductory_text;
public ?string $externe_referenz; public ?string $external_reference;
public float $gesamtrabatt; public float $total_discount;
public int $owner_id; public int $owner_id;
public int $billingaddress_id; public int $billingaddress_id;
public int $customer_number; public int $customer_number;
@@ -33,7 +33,6 @@ class ManualInvoiceModel extends TTCrudBaseModel {
public ?string $email; public ?string $email;
public ?string $uid; public ?string $uid;
public string $billing_type; public string $billing_type;
public string $billing_delivery;
public ?string $bank_account_bank; public ?string $bank_account_bank;
public ?string $bank_account_owner; public ?string $bank_account_owner;
public ?string $bank_account_iban; public ?string $bank_account_iban;

View File

@@ -4,11 +4,9 @@ class ManualInvoicepositionModel extends TTCrudBaseModel {
public int $id; public int $id;
public ?int $manualinvoice_id; public ?int $manualinvoice_id;
public ?string $position_group; public ?string $position_group;
public ?int $billing_id;
public int $contract_id;
public ?string $matchcode; public ?string $matchcode;
public int $product_id; public int $warehousearticle_id;
public string $product_name; public string $warehousearticle_name;
public ?string $product_info; public ?string $product_info;
public float $amount; public float $amount;
public string $unit; public string $unit;

View File

@@ -13,7 +13,7 @@ class WarehouseArticleController extends TTCrud {
['key' => 'description', 'text' => 'Beschreibung', 'required' => true,'modal' => ['type' => 'textarea'], 'table' => ['sortable' => false]], ['key' => 'description', 'text' => 'Beschreibung', 'required' => true,'modal' => ['type' => 'textarea'], 'table' => ['sortable' => false]],
['key' => 'category_id', 'text' => 'Kategorie', 'required' => true, 'modal' => ['type' => 'select', 'items' => []], 'table' => ['filter' => 'select']], ['key' => 'category_id', 'text' => 'Kategorie', 'required' => true, 'modal' => ['type' => 'select', 'items' => []], 'table' => ['filter' => 'select']],
['key' => 'unit', 'text' => 'Einheit', 'required' => true,'modal' => ['type' => 'select', 'items' => [['value' => 'Stk.', 'text' => 'Stk.'], ['value' => 'Pau.', 'text' => 'Pau.'], ['value' => 'm.', 'text' => 'm.'], ['value' => 'Std.', 'text' => 'Std.'], ['value' => 'km', 'text' => 'km']]], 'table' => ['filter' => 'select', 'filterOptions' => [['value' => 'Stk.', 'text' => 'Stk.'], ['value' => 'Pau.', 'text' => 'Pau.'], ['value' => 'm.', 'text' => 'm.'], ['value' => 'Std.', 'text' => 'Std.'], ['value' => 'km', 'text' => 'km']]]], ['key' => 'unit', 'text' => 'Einheit', 'required' => true,'modal' => ['type' => 'select', 'items' => [['value' => 'Stk.', 'text' => 'Stk.'], ['value' => 'Pau.', 'text' => 'Pau.'], ['value' => 'm.', 'text' => 'm.'], ['value' => 'Std.', 'text' => 'Std.'], ['value' => 'km', 'text' => 'km']]], 'table' => ['filter' => 'select', 'filterOptions' => [['value' => 'Stk.', 'text' => 'Stk.'], ['value' => 'Pau.', 'text' => 'Pau.'], ['value' => 'm.', 'text' => 'm.'], ['value' => 'Std.', 'text' => 'Std.'], ['value' => 'km', 'text' => 'km']]]],
['key' => 'revenueAccount', 'text' => 'Erlöskonto', 'required' => true,'modal' => ['type' => 'select', 'items' => [['value' => 0, 'text' => 'Dienstleistungen'], ['value' => 1, 'text' => 'Handelswaren']]], 'table' => false], ['key' => 'vatgroup_id', 'text' => 'Erlöskonto', 'required' => true,'modal' => ['type' => 'select', 'items' => [['value' => 2, 'text' => 'Dienstleistungen'], ['value' => 3, 'text' => 'Handelswaren']]], 'table' => false],
['key' => 'cheapestPurchasePrice', 'text' => 'Einkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']], ['key' => 'cheapestPurchasePrice', 'text' => 'Einkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']],
['key' => 'cheapestSellPrice', 'text' => 'Verkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']], ['key' => 'cheapestSellPrice', 'text' => 'Verkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']],
['key' => 'warningAmount', 'text' => 'Warnmenge', 'required' => true,'modal' => ['type' => 'number'], 'table' => false], ['key' => 'warningAmount', 'text' => 'Warnmenge', 'required' => true,'modal' => ['type' => 'number'], 'table' => false],

View File

@@ -17,7 +17,7 @@ class WarehouseArticleModel extends TTCrudBaseModel {
public ?int $isEndOfLife; public ?int $isEndOfLife;
public string $unit; public string $unit;
public ?int $isSerialDocumentation; public ?int $isSerialDocumentation;
public int $revenueAccount; public int $vatgroup_id;
public int $create; public int $create;
public int $createBy; public int $createBy;
} }

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class WarehousearticleRenameRevenueaccountToVatgroupid extends AbstractMigration
{
public function up(): void
{
if($this->getEnvironment() == "thetool") {
$this->execute("ALTER TABLE warehousearticle CHANGE revenueAccount vatgroup_id INT(11) NOT NULL DEFAULT 2");
$this->execute("UPDATE warehousearticle SET vatgroup_id = CASE
WHEN vatgroup_id = 0 THEN 2
WHEN vatgroup_id = 1 THEN 3
ELSE vatgroup_id
END");
}
}
public function down(): void
{
if($this->getEnvironment() == "thetool") {
$this->execute("UPDATE warehousearticle SET vatgroup_id = CASE
WHEN vatgroup_id = 2 THEN 0
WHEN vatgroup_id = 3 THEN 1
ELSE vatgroup_id
END");
$this->execute("ALTER TABLE warehousearticle CHANGE vatgroup_id revenueAccount INT(11) NOT NULL DEFAULT 0");
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class ManualinvoiceColumnCleanup extends AbstractMigration
{
public function up(): void
{
if($this->getEnvironment() == "thetool") {
$position = $this->table("ManualInvoiceposition");
if ($position->hasColumn("billing_id")) {
$position->removeColumn("billing_id")->save();
}
if ($position->hasColumn("contract_id")) {
$position->removeColumn("contract_id")->save();
}
// ManualInvoiceposition: Rename columns
$this->execute("ALTER TABLE manualinvoiceposition CHANGE product_id warehousearticle_id INT(11) NOT NULL");
$this->execute("ALTER TABLE manualinvoiceposition CHANGE product_name warehousearticle_name VARCHAR(255) NOT NULL");
$invoice = $this->table("ManualInvoice");
if ($invoice->hasColumn("billing_delivery")) {
$invoice->removeColumn("billing_delivery")->save();
}
$this->execute("ALTER TABLE manualinvoice CHANGE leistungszeitraum performance_period VARCHAR(255) NULL");
$this->execute("ALTER TABLE manualinvoice CHANGE einleitender_text introductory_text TEXT NULL");
$this->execute("ALTER TABLE manualinvoice CHANGE externe_referenz external_reference VARCHAR(255) NULL");
$this->execute("ALTER TABLE manualinvoice CHANGE gesamtrabatt total_discount DECIMAL(6,2) NOT NULL DEFAULT 0.00");
}
}
public function down(): void
{
if($this->getEnvironment() == "thetool") {
$this->execute("ALTER TABLE manualinvoice CHANGE performance_period leistungszeitraum VARCHAR(255) NULL");
$this->execute("ALTER TABLE manualinvoice CHANGE introductory_text einleitender_text TEXT NULL");
$this->execute("ALTER TABLE manualinvoice CHANGE external_reference externe_referenz VARCHAR(255) NULL");
$this->execute("ALTER TABLE manualinvoice CHANGE total_discount gesamtrabatt DECIMAL(6,2) NOT NULL DEFAULT 0.00");
$invoice = $this->table("ManualInvoice");
$invoice->addColumn("billing_delivery", "enum", [
"null" => false,
"values" => ["email", "paper"],
"after" => "billing_type"
])->save();
$this->execute("ALTER TABLE manualinvoiceposition CHANGE warehousearticle_id product_id INT(11) NOT NULL");
$this->execute("ALTER TABLE manualinvoiceposition CHANGE warehousearticle_name product_name VARCHAR(255) NOT NULL");
$position = $this->table("ManualInvoiceposition");
$position->addColumn("billing_id", "integer", ["null" => true, "after" => "position_group"]);
$position->addColumn("contract_id", "integer", ["null" => false, "after" => "billing_id"]);
$position->save();
}
}
}

View File

@@ -52,27 +52,21 @@ Vue.component('manual-invoice', {
}, },
async handleSave(invoiceData) { async handleSave(invoiceData) {
try { try {
const positions = invoiceData.positions.map(p => { const positions = invoiceData.positions.map(p => ({
const amount = parseFloat(p.amount) || 0; ...p,
const price = parseFloat(p.price) || 0; amount: parseFloat(p.amount) || 0,
const discount = parseFloat(p.discount) || 0; price: parseFloat(p.price) || 0,
const vatrate = parseFloat(p.vatrate) || 0; discount: parseFloat(p.discount) || 0,
const priceAfterDiscount = amount * price * (1 - discount / 100); vatrate: parseFloat(p.vatrate) || 0,
return {
...p, amount, price, discount, vatrate,
unit: p.unit || 'Stk.', unit: p.unit || 'Stk.',
price_total: priceAfterDiscount, warehousearticle_id: p.warehousearticle_id || p.product_id || 0,
price_gross: priceAfterDiscount * (1 + vatrate / 100), warehousearticle_name: p.warehousearticle_name || p.product_name || '',
product_id: p.product_id || 0,
contract_id: p.contract_id || 0,
billing_id: p.billing_id || null,
matchcode: p.matchcode || null, matchcode: p.matchcode || null,
fibu_cost_account: p.fibu_cost_account || null, fibu_cost_account: p.fibu_cost_account || null,
fibu_cost_account_legacy: p.fibu_cost_account_legacy || null, fibu_cost_account_legacy: p.fibu_cost_account_legacy || null,
fibu_taxcode: p.fibu_taxcode || null, fibu_taxcode: p.fibu_taxcode || null,
options: p.options || null options: p.options || null
}; }));
});
const payload = { const payload = {
...invoiceData, ...invoiceData,
@@ -82,11 +76,13 @@ Vue.component('manual-invoice', {
customer_number: invoiceData.customer_number || 0, customer_number: invoiceData.customer_number || 0,
country: invoiceData.country || 'Österreich', country: invoiceData.country || 'Österreich',
billing_type: invoiceData.billing_type || 'invoice', billing_type: invoiceData.billing_type || 'invoice',
billing_delivery: 'email',
fibu_payment_due: 14, fibu_payment_due: 14,
fibu_account_number: invoiceData.fibu_account_number || 0, fibu_account_number: invoiceData.fibu_account_number || 0,
vatgroup_id: 1, vatgroup_id: invoiceData.vatgroup_id || 1,
gesamtrabatt: parseFloat(invoiceData.gesamtrabatt) || 0 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; 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> </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="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.externe_referenz" sm row placeholder="z.B. Auftragsnummer, Bestellnummer"/> <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.einleitender_text" rows="3" sm row/> <tt-textarea label="Einleitender Text" v-model="invoiceData.introductory_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" @updateField-price_type="onPriceTypeChanged" /> <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.total_discount" sm row type="number" placeholder="0"/>
</tt-card> </tt-card>
</div> </div>
</div> </div>
@@ -215,8 +211,8 @@ Vue.component('manual-invoice-modal', {
id: null, invoice_number: null, invoice_date: moment().format('YYYY-MM-DD'), 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, billingaddress_id: null, owner_id: null, customer_number: 0, fibu_account_number: 0,
company: '', firstname: '', lastname: '', street: '', zip: '', city: '', country: 'Österreich', company: '', firstname: '', lastname: '', street: '', zip: '', city: '', country: 'Österreich',
uid: '', email: '', billing_type: 'invoice', tax_text: '', uid: '', email: '', billing_type: 'invoice', tax_text: '', vatgroup_id: 1,
leistungszeitraum: '', einleitender_text: '', externe_referenz: '', gesamtrabatt: 0, performance_period: '', introductory_text: '', external_reference: '', total_discount: 0,
positions: [], total: 0, total_gross: 0 positions: [], total: 0, total_gross: 0
}, },
positionsConfig: { positionsConfig: {
@@ -228,7 +224,7 @@ Vue.component('manual-invoice-modal', {
customFieldReference: 'WarehouseArticle', customFieldReference: 'WarehouseArticle',
emitDisplayValue: true emitDisplayValue: true
}, },
product_name: { type: 'input', label: 'Bezeichnung' }, warehousearticle_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' },
price_type: { price_type: {
@@ -240,7 +236,7 @@ Vue.component('manual-invoice-modal', {
discount: { type: 'input', label: 'Rabatt (%)', inputType: 'number' }, discount: { type: 'input', label: 'Rabatt (%)', inputType: 'number' },
}, },
validateForm: (d) => { 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.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;
@@ -261,18 +257,15 @@ Vue.component('manual-invoice-modal', {
subtotal += lineTotal; subtotal += lineTotal;
}); });
// Apply gesamtrabatt const totalDiscount = parseFloat(this.invoiceData.total_discount) || 0;
const gesamtrabatt = parseFloat(this.invoiceData.gesamtrabatt) || 0; const net = subtotal * (1 - totalDiscount / 100);
const net = subtotal * (1 - gesamtrabatt / 100);
// Calculate VAT
let vat = {}, gross = 0; let vat = {}, gross = 0;
(this.invoiceData.positions || []).forEach(p => { (this.invoiceData.positions || []).forEach(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;
const discount = parseFloat(p.discount) || 0; const discount = parseFloat(p.discount) || 0;
const r = parseInt(p.vatrate) || 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); const lineVat = lineNet * (r / 100);
vat[r] = (vat[r] || 0) + lineVat; vat[r] = (vat[r] || 0) + lineVat;
gross += lineNet + lineVat; gross += lineNet + lineVat;
@@ -400,7 +393,7 @@ Vue.component('manual-invoice-modal', {
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.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, 'product_info', data.article.description || '');
pm.$set(pm.formData, 'unit', data.article.unit || 'Stk.'); pm.$set(pm.formData, 'unit', data.article.unit || 'Stk.');
} }
@@ -455,7 +448,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 (allow negative for Gutschrift) .filter(p => p.warehousearticle_name && (parseFloat(p.amount) || 0) !== 0)
.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;
@@ -524,17 +517,17 @@ Vue.component('manual-invoice-modal', {
} }
// Pre-fill external reference with shipping note reference // 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 // Add introductory text if shipping note has notes
if (shippingNoteData.note) { 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) // Add all positions (batch operation to avoid triggering watcher for each item)
if (shippingNoteData.positions && Array.isArray(shippingNoteData.positions)) { if (shippingNoteData.positions && Array.isArray(shippingNoteData.positions)) {
const newPositions = shippingNoteData.positions.map(position => ({ const newPositions = shippingNoteData.positions.map(position => ({
product_name: position.product_name || '', warehousearticle_name: position.warehousearticle_name || position.product_name || '',
product_info: position.product_info || '', product_info: position.product_info || '',
amount: parseFloat(position.amount) || 0, amount: parseFloat(position.amount) || 0,
unit: position.unit || 'Stk.', unit: position.unit || 'Stk.',
@@ -583,7 +576,7 @@ Vue.component('gutschrift-modal', {
<tbody> <tbody>
<tr v-for="(pos, index) in invoice.positions" :key="index"> <tr v-for="(pos, index) in invoice.positions" :key="index">
<td class="text-center"><input type="checkbox" v-model="selectedPositions[index]"></td> <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.original_amount }}</td><td class="text-right">{{ pos.credited_amount }}</td>
<td class="text-right">{{ pos.available_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> <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) => ({ p, i })).filter(({ i }) => this.selectedPositions[i])
.map(({ p, i }) => { .map(({ p, i }) => {
const amt = this.creditAmounts[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; return amt > 0 ? { ...p, amount: amt } : null;
}).filter(Boolean); }).filter(Boolean);

View File

@@ -437,8 +437,8 @@ Vue.component('warehouse-article-modal', {
<div class="col-md-2" :class="{ 'wa-field-disabled': !isEditMode && !formData.category_id }"> <div class="col-md-2" :class="{ 'wa-field-disabled': !isEditMode && !formData.category_id }">
<tt-select <tt-select
label="Erlöskonto" label="Erlöskonto"
v-model="formData.revenueAccount" v-model="formData.vatgroup_id"
:options="revenueAccountOptions" :options="vatgroupOptions"
required required
sm/> sm/>
</div> </div>
@@ -538,7 +538,7 @@ Vue.component('warehouse-article-modal', {
category_id: null, category_id: null,
articleNumber: '', articleNumber: '',
unit: 'Stk.', unit: 'Stk.',
revenueAccount: 0, vatgroup_id: 2,
warningAmount: 0, warningAmount: 0,
criticalAmount: 0, criticalAmount: 0,
isSerialDocumentation: false, isSerialDocumentation: false,
@@ -566,10 +566,10 @@ Vue.component('warehouse-article-modal', {
{ value: 'km', text: 'km' } { value: 'km', text: 'km' }
]; ];
}, },
revenueAccountOptions() { vatgroupOptions() {
return [ return [
{ value: 0, text: 'Dienstleistungen' }, { value: 2, text: 'Dienstleistungen' },
{ value: 1, text: 'Handelswaren' } { value: 3, text: 'Handelswaren' }
]; ];
}, },
isValid() { isValid() {
@@ -607,7 +607,7 @@ Vue.component('warehouse-article-modal', {
category_id: data.category_id, category_id: data.category_id,
articleNumber: data.articleNumber || '', articleNumber: data.articleNumber || '',
unit: data.unit || 'Stk.', unit: data.unit || 'Stk.',
revenueAccount: data.revenueAccount || 0, vatgroup_id: data.vatgroup_id || 2,
warningAmount: data.warningAmount || 0, warningAmount: data.warningAmount || 0,
criticalAmount: data.criticalAmount || 0, criticalAmount: data.criticalAmount || 0,
isSerialDocumentation: !!data.isSerialDocumentation, isSerialDocumentation: !!data.isSerialDocumentation,
@@ -637,7 +637,7 @@ Vue.component('warehouse-article-modal', {
category_id: null, category_id: null,
articleNumber: '', articleNumber: '',
unit: 'Stk.', unit: 'Stk.',
revenueAccount: 0, vatgroup_id: 2,
warningAmount: 0, warningAmount: 0,
criticalAmount: 0, criticalAmount: 0,
isSerialDocumentation: false, isSerialDocumentation: false,