fxied vat calculation
This commit is contained in:
@@ -17,7 +17,7 @@ foreach($invoice->positions as $p) {
|
||||
}
|
||||
}
|
||||
|
||||
$gesamtrabatt = $invoice->gesamtrabatt ?? 0;
|
||||
$total_discount = $invoice->total_discount ?? 0;
|
||||
$subtotal = 0;
|
||||
foreach($invoice->positions as $p) {
|
||||
$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>
|
||||
|
||||
<?php if($invoice->einleitender_text ?? ''): ?>
|
||||
<p style="margin-top: 10pt; margin-bottom: 20pt; text-align: center; font-weight: bold;"><?=nl2br(htmlspecialchars($invoice->einleitender_text))?></p>
|
||||
<?php if($invoice->introductory_text ?? ''): ?>
|
||||
<p style="margin-top: 10pt; margin-bottom: 20pt; text-align: center; font-weight: bold;"><?=nl2br(htmlspecialchars($invoice->introductory_text))?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<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" ?>">
|
||||
<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): ?>
|
||||
<div style="padding-left: 12pt; font-size: 10px; color: #666;"><?=htmlspecialchars($p->product_info)?></div>
|
||||
<?php endif; ?>
|
||||
@@ -186,17 +186,17 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
|
||||
endforeach;
|
||||
endforeach;
|
||||
?>
|
||||
<?php if($gesamtrabatt > 0): ?>
|
||||
<?php if($total_discount > 0): ?>
|
||||
<tr style="background-color: #ebebeb; border-top: 2px solid black;">
|
||||
<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>
|
||||
</tr>
|
||||
<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="2" style="text-align: right; padding-right: 4pt; color: #d32f2f;">-<?=number_format($subtotal * ($gesamtrabatt / 100), 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 * ($total_discount / 100), 2, ",","."). " €"?></td>
|
||||
</tr>
|
||||
<?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="2" style="text-align: right; padding-right: 4pt;"><?=number_format($net_total, 2, ",","."). " €"?></td>
|
||||
</tr>
|
||||
|
||||
@@ -105,8 +105,8 @@ class ManualInvoiceController extends TTCrud
|
||||
"{{ billingAccount }}" => $invoice->fibu_account_number ?? '',
|
||||
"{{ invoiceNumber }}" => $invoice->invoice_number ?? "VORSCHAU",
|
||||
"{{ invoiceDate }}" => date("d.m.Y", $invoice->invoice_date ?? time()),
|
||||
"{{ 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>" : "",
|
||||
"{{ leistungszeitraumHtml }}" => ($invoice->performance_period ?? '') ? "<tr><td>Leistungszeitraum:</td><td>" . htmlspecialchars($invoice->performance_period) . "</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>" : "",
|
||||
"{{ 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>'
|
||||
@@ -393,11 +393,32 @@ class ManualInvoiceController extends TTCrud
|
||||
$data['status'] = 'erstellt';
|
||||
$data['fibu_payment_skonto'] = $data['fibu_payment_skonto'] ?? 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_gross'] = $data['total_gross'] ?? 0;
|
||||
$data['lock'] = 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['edit_by'] = $me->id;
|
||||
$data['create'] = time();
|
||||
@@ -476,31 +497,40 @@ class ManualInvoiceController extends TTCrud
|
||||
$me = new User();
|
||||
$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) {
|
||||
// Skip empty positions
|
||||
if (empty($position['product_name']) || ($position['amount'] ?? 0) == 0) continue;
|
||||
$articleName = $position['warehousearticle_name'] ?? $position['product_name'] ?? '';
|
||||
if (empty($articleName) || ($position['amount'] ?? 0) == 0) continue;
|
||||
|
||||
// Map _group to position_group
|
||||
$groupName = $position['_group'] ?? null;
|
||||
$amount = floatval($position['amount']);
|
||||
$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
|
||||
$filteredPosition = array_intersect_key($position, array_flip($allowedFields));
|
||||
|
||||
ManualInvoicepositionModel::create(array_merge([
|
||||
ManualInvoicepositionModel::create([
|
||||
'manualinvoice_id' => $invoiceId,
|
||||
'position_group' => $groupName,
|
||||
'unit' => 'Stk.',
|
||||
'discount' => 0,
|
||||
'position_group' => $position['_group'] ?? null,
|
||||
'matchcode' => $position['matchcode'] ?? null,
|
||||
'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,
|
||||
'edit_by' => $me->id,
|
||||
'create' => time(),
|
||||
'edit' => time()
|
||||
], $filteredPosition));
|
||||
]);
|
||||
}
|
||||
$this->tempPositions = [];
|
||||
}
|
||||
@@ -510,17 +540,13 @@ class ManualInvoiceController extends TTCrud
|
||||
|
||||
$positions = ManualInvoicepositionModel::search(['manualinvoice_id' => $invoiceId]);
|
||||
$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;
|
||||
foreach ($positions as $pos) {
|
||||
$positionNet = $pos->price_total;
|
||||
$positionAfterDiscount = $positionNet * (1 - $gesamtrabatt / 100);
|
||||
$positionAfterDiscount = $positionNet * (1 - $totalDiscount / 100);
|
||||
$grossTotal += $positionAfterDiscount * (1 + $pos->vatrate / 100);
|
||||
}
|
||||
|
||||
@@ -582,11 +608,11 @@ class ManualInvoiceController extends TTCrud
|
||||
'id' => $pos->id,
|
||||
'manualinvoice_id' => $pos->manualinvoice_id,
|
||||
'_group' => $pos->position_group ?? '',
|
||||
'billing_id' => $pos->billing_id,
|
||||
'contract_id' => $pos->contract_id,
|
||||
'matchcode' => $pos->matchcode,
|
||||
'product_id' => $pos->product_id,
|
||||
'product_name' => $pos->product_name,
|
||||
'warehousearticle_id' => $pos->warehousearticle_id,
|
||||
'warehousearticle_name' => $pos->warehousearticle_name,
|
||||
'product_id' => $pos->warehousearticle_id,
|
||||
'product_name' => $pos->warehousearticle_name,
|
||||
'product_info' => $pos->product_info,
|
||||
'amount' => $pos->amount,
|
||||
'unit' => $pos->unit ?? 'Stk.',
|
||||
@@ -632,19 +658,20 @@ class ManualInvoiceController extends TTCrud
|
||||
|
||||
foreach ($existingCredits as $credit) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
$availablePositions = [];
|
||||
foreach ($positions as $pos) {
|
||||
$key = $pos->product_id . '_' . $pos->matchcode;
|
||||
$key = $pos->warehousearticle_id . '_' . $pos->matchcode;
|
||||
$availableAmount = $pos->amount - ($creditedAmounts[$key] ?? 0);
|
||||
if ($availableAmount > 0) {
|
||||
$availablePositions[] = [
|
||||
'id' => $pos->id,
|
||||
'product_name' => $pos->product_name,
|
||||
'warehousearticle_name' => $pos->warehousearticle_name,
|
||||
'product_name' => $pos->warehousearticle_name,
|
||||
'product_info' => $pos->product_info,
|
||||
'original_amount' => $pos->amount,
|
||||
'credited_amount' => $creditedAmounts[$key] ?? 0,
|
||||
@@ -652,7 +679,8 @@ class ManualInvoiceController extends TTCrud
|
||||
'unit' => $pos->unit ?? 'Stk.',
|
||||
'price' => $pos->price,
|
||||
'vatrate' => $pos->vatrate,
|
||||
'product_id' => $pos->product_id,
|
||||
'warehousearticle_id' => $pos->warehousearticle_id,
|
||||
'product_id' => $pos->warehousearticle_id,
|
||||
'matchcode' => $pos->matchcode,
|
||||
'fibu_cost_account' => $pos->fibu_cost_account,
|
||||
'fibu_taxcode' => $pos->fibu_taxcode
|
||||
@@ -692,10 +720,10 @@ class ManualInvoiceController extends TTCrud
|
||||
$invoiceData = [
|
||||
'invoice_number' => ManualInvoiceModel::getNextInvoiceNumber(),
|
||||
'invoice_date' => time(),
|
||||
'leistungszeitraum' => $originalInvoice->leistungszeitraum ?? null,
|
||||
'einleitender_text' => 'Gutschrift zur Rechnung ' . $originalInvoice->invoice_number,
|
||||
'externe_referenz' => $originalInvoice->externe_referenz ?? null,
|
||||
'gesamtrabatt' => 0,
|
||||
'performance_period' => $originalInvoice->performance_period ?? null,
|
||||
'introductory_text' => 'Gutschrift zur Rechnung ' . $originalInvoice->invoice_number,
|
||||
'external_reference' => $originalInvoice->external_reference ?? null,
|
||||
'total_discount' => 0,
|
||||
'owner_id' => $originalInvoice->owner_id,
|
||||
'billingaddress_id' => $originalInvoice->billingaddress_id,
|
||||
'customer_number' => $originalInvoice->customer_number,
|
||||
@@ -721,7 +749,6 @@ class ManualInvoiceController extends TTCrud
|
||||
'email' => $originalInvoice->email,
|
||||
'uid' => $originalInvoice->uid,
|
||||
'billing_type' => $originalInvoice->billing_type,
|
||||
'billing_delivery' => $originalInvoice->billing_delivery,
|
||||
'bank_account_bank' => $originalInvoice->bank_account_bank,
|
||||
'bank_account_owner' => $originalInvoice->bank_account_owner,
|
||||
'bank_account_iban' => $originalInvoice->bank_account_iban,
|
||||
@@ -749,8 +776,8 @@ class ManualInvoiceController extends TTCrud
|
||||
ManualInvoicepositionModel::create([
|
||||
'manualinvoice_id' => $creditInvoiceId,
|
||||
'position_group' => null,
|
||||
'product_id' => $pos['product_id'],
|
||||
'product_name' => $pos['product_name'],
|
||||
'warehousearticle_id' => $pos['warehousearticle_id'] ?? $pos['product_id'] ?? 0,
|
||||
'warehousearticle_name' => $pos['warehousearticle_name'] ?? $pos['product_name'] ?? '',
|
||||
'product_info' => $pos['product_info'] ?? '',
|
||||
'amount' => -abs($pos['amount']),
|
||||
'unit' => $pos['unit'] ?? 'Stk.',
|
||||
@@ -762,8 +789,6 @@ class ManualInvoiceController extends TTCrud
|
||||
'matchcode' => $pos['matchcode'] ?? null,
|
||||
'fibu_cost_account' => $pos['fibu_cost_account'] ?? null,
|
||||
'fibu_taxcode' => $pos['fibu_taxcode'] ?? null,
|
||||
'contract_id' => 0,
|
||||
'billing_id' => null,
|
||||
'create_by' => $me->id,
|
||||
'edit_by' => $me->id,
|
||||
'create' => time(),
|
||||
@@ -813,12 +838,7 @@ class ManualInvoiceController extends TTCrud
|
||||
return;
|
||||
}
|
||||
|
||||
// Map revenueAccount to 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
|
||||
$vatgroupId = $article->vatgroup_id;
|
||||
$vatrate = VatrateModel::getFirst(['vatgroup_id' => $vatgroupId, 'area' => $vatarea]);
|
||||
|
||||
if (!$vatrate) {
|
||||
@@ -826,7 +846,6 @@ class ManualInvoiceController extends TTCrud
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse prices from cheapestSellPrice JSON
|
||||
$prices = [];
|
||||
if (!empty($article->cheapestSellPrice)) {
|
||||
$pricesData = json_decode($article->cheapestSellPrice, true);
|
||||
@@ -842,7 +861,7 @@ class ManualInvoiceController extends TTCrud
|
||||
'title' => $article->title,
|
||||
'articleNumber' => $article->articleNumber,
|
||||
'description' => $article->description,
|
||||
'revenueAccount' => $article->revenueAccount,
|
||||
'vatgroup_id' => $article->vatgroup_id,
|
||||
'unit' => $article->unit
|
||||
],
|
||||
'prices' => $prices,
|
||||
|
||||
@@ -4,10 +4,10 @@ class ManualInvoiceModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public ?string $invoice_number;
|
||||
public int $invoice_date;
|
||||
public ?string $leistungszeitraum;
|
||||
public ?string $einleitender_text;
|
||||
public ?string $externe_referenz;
|
||||
public float $gesamtrabatt;
|
||||
public ?string $performance_period;
|
||||
public ?string $introductory_text;
|
||||
public ?string $external_reference;
|
||||
public float $total_discount;
|
||||
public int $owner_id;
|
||||
public int $billingaddress_id;
|
||||
public int $customer_number;
|
||||
@@ -33,7 +33,6 @@ class ManualInvoiceModel extends TTCrudBaseModel {
|
||||
public ?string $email;
|
||||
public ?string $uid;
|
||||
public string $billing_type;
|
||||
public string $billing_delivery;
|
||||
public ?string $bank_account_bank;
|
||||
public ?string $bank_account_owner;
|
||||
public ?string $bank_account_iban;
|
||||
|
||||
@@ -4,11 +4,9 @@ class ManualInvoicepositionModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public ?int $manualinvoice_id;
|
||||
public ?string $position_group;
|
||||
public ?int $billing_id;
|
||||
public int $contract_id;
|
||||
public ?string $matchcode;
|
||||
public int $product_id;
|
||||
public string $product_name;
|
||||
public int $warehousearticle_id;
|
||||
public string $warehousearticle_name;
|
||||
public ?string $product_info;
|
||||
public float $amount;
|
||||
public string $unit;
|
||||
|
||||
@@ -13,7 +13,7 @@ class WarehouseArticleController extends TTCrud {
|
||||
['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' => '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' => 'cheapestSellPrice', 'text' => 'Verkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']],
|
||||
['key' => 'warningAmount', 'text' => 'Warnmenge', 'required' => true,'modal' => ['type' => 'number'], 'table' => false],
|
||||
|
||||
@@ -17,7 +17,7 @@ class WarehouseArticleModel extends TTCrudBaseModel {
|
||||
public ?int $isEndOfLife;
|
||||
public string $unit;
|
||||
public ?int $isSerialDocumentation;
|
||||
public int $revenueAccount;
|
||||
public int $vatgroup_id;
|
||||
public int $create;
|
||||
public int $createBy;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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