Merge branch 'ManualInvoice/fix-vat' into 'master'

fxied vat calculation

See merge request fronk/thetool!2048
This commit is contained in:
Luca Haid
2026-01-26 14:44:52 +00:00
10 changed files with 224 additions and 124 deletions

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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],

View File

@@ -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;
}