implemented first version of invoice printing

This commit is contained in:
2024-07-05 13:10:56 +02:00
parent 79e8cda5e5
commit 91938a0987
4 changed files with 362 additions and 56 deletions

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head><title>Xinon Rechnung</title></head>
<body style="border:0; margin: 0;font-family: sans-serif, Verdana;font-size: 11px;" onload="subst()">
<script>
function subst() {
var vars = {};
var query_strings_from_url = document.location.search.substring(1).split('&');
for (var query_string in query_strings_from_url) {
if (query_strings_from_url.hasOwnProperty(query_string)) {
var temp_var = query_strings_from_url[query_string].split('=', 2);
vars[temp_var[0]] = decodeURI(temp_var[1]);
}
}
var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];
for (var css_class in css_selector_classes) {
if (css_selector_classes.hasOwnProperty(css_class)) {
var element = document.getElementsByClassName(css_selector_classes[css_class]);
for (var j = 0; j < element.length; ++j) {
element[j].textContent = vars[css_selector_classes[css_class]];
}
}
}
}
</script>
<div style="margin-bottom: 16px;height: 1px"></div>
<div style="color:grey;text-align: center;margin-bottom: 0">
<span>XINON GmbH | Fladnitz 150 | 8322 Studenzen</span><br>
<span>Tel.: +43 3115 40800 | E-Mail: office@xinon.at</span><br>
<span>UID: ATU68711968 | FN: 416556h | LG: Feldbach</span><br>
</div>
<div style="text-align: right">Seite <span class="page"></span> von <span class="topage"></span></div>
<div style="margin-top: 16px;height: 1px"></div>
</body>
</html>

View File

@@ -0,0 +1,241 @@
<?php
/**
* @var string $ressourcePathPrefix
* @var Invoice $invoice
* @var array $vat
*/
$net_total = $invoice->total + $invoice->total_setup;
$gross_total = $invoice->total_gross + $invoice->total_setup_gross;
$is_credit = $net_total < 0;
$this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
//die(json_encode($invoice->positions));
?>
<!DOCTYPE html>
<html>
<head>
<title>Rechnung</title>
<meta charset="utf-8"/>
<!-- <link href="-->
<?php //= self::getResourcePath() ?><!--assets/css/bootstrap.min.css" rel="stylesheet" type="text/css" />-->
<style>
body {
margin-top: 0;
/*padding-top: 20pt;*/
font-family: sans-serif, Verdana;
font-size: 12px;
}
tr {
page-break-inside: avoid;
}
.uneven {
background-color: #e1e1e1;
}
table tr td:last-child {
text-align: right;
}
.additionalRow td:first-child {
text-align: left;
padding-left: 20pt;
}
th {
height: 28px;
}
#invoiceTable tr *:nth-child(5),
#invoiceTable tr *:nth-child(4),
#invoiceTable tr *:nth-child(3) {
text-align: right;
}
#invoiceTable tr *:not(:first-child) {
padding: 4px 0;
}
#invoiceTable tr td {
font-size: 11px;
}
#invoiceTable tr td:first-child {
max-width: 200pt;
}
</style>
</head>
<body>
<div>
<h2 style="text-align: center;color: #005384">Ihre Xinon Rechnung vom <?=date("d.m.Y",$invoice->invoice_date)?></h2>
<table style="border-collapse: collapse; width: 100%;" id="invoiceTable">
<tr style="font-weight: bold; border-bottom: 1px solid black;" class="uneven">
<th style="text-align: center">Dienstleistung</th>
<th style="text-align: center">Zeitraum</th>
<th style="text-align: center">Preis</th>
<th style="text-align: center;">Menge</th>
<th style="text-align: center">Netto €</th>
<th style="text-align: center">Ust. %</th>
<th style="text-align: center;">Brutto €</th>
</tr>
<?php
$i = 0;
foreach($invoice->positions as $p):
$start_date = new DateTime($p->start_date);
$end_date = new DateTime($p->end_date);
$amount = (float) number_format($p->amount, 3, ",", ".");
$price = number_format($p->price, 2, ",",".");
$price_total = number_format($p->price_total, 2, ",",".");
$price_gross = number_format($p->price_gross, 2, ",",".");
$vatrate = number_format($p->vatrate, 2, ",",".");
?>
<tr class="<?=($i%2 == 0) ? "even" : "uneven" ?>">
<td><?=$p->product_name?></td>
<td style="text-align: center;">
<?= $p->billing_period > 1 ?
$start_date->format("m.Y") . " - " . $end_date->format("m.Y") :
$start_date->format("d.m.Y") . " - " . $end_date->format("d.m.Y")
?>
</td>
<td><?=$price?> €</td>
<td style="text-align: center"><?=$amount?></td>
<td><?=$price_total?> €</td>
<td style="text-align: right;"><?=$vatrate?>%</td>
<td><?=$price_gross?> €</td>
</tr>
<?php if($p->matchcode): ?>
<tr class="additionalRow <?=($i%2 == 0) ? "even" : "uneven" ?>">
<td colspan="7"><?=$p->matchcode?></td>
</tr>
<?php endif;
$i++;
endforeach;
?>
<tr style="font-weight: bold; background-color: #e1e1e1; border-bottom: 1px solid black;border-top: 1px solid black">
<td colspan="5">Gesamt Netto:</td>
<td colspan="2" style="text-align: right;"><?=number_format($net_total, 2, ",","."). " €"?></td>
</tr>
<?php foreach ($vat as $rate => $vat_total): ?>
<?php if($vat_total > 0): ?>
<tr style="font-size: 11px;border-bottom: 1px solid black;">
<td colspan="5">USt. <?=$rate?>%:</td>
<td colspan="2" style="text-align: right"><?=number_format($vat_total, 2, ",","."). " €"?></td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
<!-- double underline border on bottom -->
<tr style="font-weight: bold; border-bottom: 3px double black; background-color: #e1e1e1;">
<td colspan="5">Gesamt Brutto:</td>
<td colspan="2" style="text-align: right"><?=number_format($gross_total, 2, ",","."). " €"?></td>
</tr>
</table>
<!-- TODO: ADD CALL DETAIL SECTION-->
<!--
<div style="margin-top: 20pt;">
<h3 style="color: #005384;margin-bottom: 0">Zusammenfassung für 0800 / 498748</h3>
<div style="margin-bottom: 16px">(01.01.2021 - 31.01.2021)</div>
<table style="border-collapse: collapse; width: 100%;">
<tr style="text-align: left;" class="uneven">
<th>Ziel</th>
<th>Preis pro Minute</th>
<th>Dauer</th>
<th style="text-align: right">Netto €</th>
<th style="text-align: right">Brutto €</th>
</tr>
<tr>
<td>Österreich Festnetz</td>
<td>0,05 €</td>
<td>01:40:00</td>
<td style="text-align: right">5,00 €</td>
<td>6,00 €</td>
</tr>
<tr class="uneven">
<td>Österreich Festnetz</td>
<td>0,05 €</td>
<td>01:40:00</td>
<td style="text-align: right">5,00 €</td>
<td>6,00 €</td>
</tr>
<tr>
<td>Österreich Festnetz</td>
<td>0,05 €</td>
<td>01:40:00</td>
<td style="text-align: right">5,00 €</td>
<td>6,00 €</td>
</tr>
<tr class="uneven">
<td>Österreich Festnetz</td>
<td>0,05 €</td>
<td>01:40:00</td>
<td style="text-align: right">5,00 €</td>
<td>6,00 €</td>
</tr>
</table>
</div>
<div style="margin-top: 20pt;">
<h3 style="color: #005384;margin-bottom: 0">Zusammenfassung für 0800 / 498748</h3>
<div style="margin-bottom: 16px">(01.01.2021 - 31.01.2021)</div>
<table style="border-collapse: collapse; width: 100%;">
<tr style="text-align: left;" class="uneven">
<th>Ziel</th>
<th>Preis pro Minute</th>
<th>Dauer</th>
<th style="text-align: right">Netto €</th>
<th style="text-align: right">Brutto €</th>
</tr>
<tr>
<td>Österreich Festnetz</td>
<td>0,05 €</td>
<td>01:40:00</td>
<td style="text-align: right">5,00 €</td>
<td>6,00 €</td>
</tr>
<tr class="uneven">
<td>Österreich Festnetz</td>
<td>0,05 €</td>
<td>01:40:00</td>
<td style="text-align: right">5,00 €</td>
<td>6,00 €</td>
</tr>
<tr>
<td>Österreich Festnetz</td>
<td>0,05 €</td>
<td>01:40:00</td>
<td style="text-align: right">5,00 €</td>
<td>6,00 €</td>
</tr>
<tr class="uneven">
<td>Österreich Festnetz</td>
<td>0,05 €</td>
<td>01:40:00</td>
<td style="text-align: right">5,00 €</td>
<td>6,00 €</td>
</tr>
</table>
</div>
-->
</body>
</html>

View File

@@ -1,9 +1,13 @@
<?php <?php
//use \chillerlan\QRCode;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Output\QROutputInterface;
class InvoiceController extends mfBaseController { class InvoiceController extends mfBaseController {
protected function init() protected function init() {
{
$this->needlogin = true; $this->needlogin = true;
$me = new User(); $me = new User();
$me->loadMe(); $me->loadMe();
@@ -41,7 +45,7 @@ class InvoiceController extends mfBaseController {
$pagination['count'] = 50; $pagination['count'] = 50;
$pagination['maxItems'] = 0; $pagination['maxItems'] = 0;
if(is_numeric($this->request->s)) { if (is_numeric($this->request->s)) {
$pagination['start'] = intval($this->request->s); $pagination['start'] = intval($this->request->s);
} }
//var_dump($filter);exit; //var_dump($filter);exit;
@@ -52,22 +56,21 @@ class InvoiceController extends mfBaseController {
$this->layout()->set("pagination", $pagination); $this->layout()->set("pagination", $pagination);
} }
private function getPreparedFilter($filter) private function getPreparedFilter($filter) {
{
$new_filter = []; $new_filter = [];
if(array_key_exists("customer", $filter)) { if (array_key_exists("customer", $filter)) {
if(array_key_exists("customer", $filter) && $filter["customer"]) { if (array_key_exists("customer", $filter) && $filter["customer"]) {
$kunde = $this->db()->escape($filter['customer']); $kunde = $this->db()->escape($filter['customer']);
if(!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = ""; if (!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = "";
$new_filter['add-where'] .= " AND (company like '%$kunde%' OR firstname like '%$kunde%' OR lastname like '%$kunde%' OR concat(firstname, ' ', lastname) like '%$kunde%' OR concat(lastname, ' ', firstname) like '%$kunde%')"; $new_filter['add-where'] .= " AND (company like '%$kunde%' OR firstname like '%$kunde%' OR lastname like '%$kunde%' OR concat(firstname, ' ', lastname) like '%$kunde%' OR concat(lastname, ' ', firstname) like '%$kunde%')";
} }
} }
if(array_key_exists("address", $filter)) { if (array_key_exists("address", $filter)) {
if(array_key_exists("address", $filter) && $filter["address"]) { if (array_key_exists("address", $filter) && $filter["address"]) {
$search = $this->db()->escape($filter['address']); $search = $this->db()->escape($filter['address']);
if(!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = ""; if (!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = "";
$new_filter['add-where'] .= " AND (street like '%$search%' OR zip like '%$search%' OR city like '%$search%' OR country like '%$search%')"; $new_filter['add-where'] .= " AND (street like '%$search%' OR zip like '%$search%' OR city like '%$search%' OR country like '%$search%')";
} }
} }
@@ -91,37 +94,62 @@ class InvoiceController extends mfBaseController {
$invoice = new Invoice($id); $invoice = new Invoice($id);
if (!$invoice->id) { if (!$invoice->id) {
$this->layout()->setFlash("Rechnung nicht gefunden", "error"); $this->layout()->setFlash("Rechnung nicht gefunden", "error");
$this->redirect("Rechnung"); $this->redirect("Invoice");
} }
$vat = []; $vat = [];
foreach ($invoice->positions as $p) {
foreach($invoice->positions as $p) { if (!array_key_exists($p->vatrate, $vat)) {
if(!array_key_exists($p->vatrate, $vat)) {
$vat[$p->vatrate] = 0; $vat[$p->vatrate] = 0;
} }
$vat[$p->vatrate] += $p->price_gross - $p->price; $vat[$p->vatrate] += $p->price_gross - $p->price;
} }
$pdf_vars = [ $pdf_vars = ["invoice" => $invoice, "vat" => $vat];
"invoice" => $invoice,
"vat" => $vat
];
/*$this->layout()->setTemplate("Invoice/Print.pdf"); // Replace placeholders in header
$this->layout()->set("invoice", $invoice); $headerHtml = file_get_contents(BASEDIR . "/Layout/default/Invoice/PDF_HEADER.html");
$this->layout()->set("vat", $vat); $headerHtml = str_replace("{{ basedir }}", BASEDIR, $headerHtml);
$this->layout()->set("ressourcePathPrefix", MFFANCYBASEURL."/"); $headerHtml = str_replace("{{ addressLine_1 }}", $invoice->company ? $invoice->company : "", $headerHtml);
return true;*/ $headerHtml = str_replace("{{ addressLine_2 }}", $invoice->firstname . " " . $invoice->lastname, $headerHtml);
$headerHtml = str_replace("{{ addressLine_3 }}", nl2br($invoice->street), $headerHtml);
$headerHtml = str_replace("{{ addressLine_4 }}", $invoice->zip . " " . $invoice->city, $headerHtml);
$headerHtml = str_replace("{{ addressLine_5 }}", $invoice->country != "Österreich" ? $invoice->country : "", $headerHtml);
$headerHtml = str_replace("{{ customerNumber }}", $invoice->customer_number, $headerHtml);
$headerHtml = str_replace("{{ billingAccount }}", "testtest", $headerHtml);
$headerHtml = str_replace("{{ invoiceNumber }}", $invoice->invoice_number, $headerHtml);
$headerHtml = str_replace("{{ invoiceDate }}", date("d.m.Y", $invoice->invoice_date), $headerHtml);
$headerHtml = str_replace("{{ vatHtml }}", $invoice->uid ? "<tr><td>Ihre UID:</td><td>" . $invoice->uid . "</td></tr>" : "", $headerHtml);
$headerHtml = str_replace("{{ qrCodeSrc }}", $this->getBankQRCode($invoice->invoice_number, $invoice->total_gross), $headerHtml);
$pdf = new PdfForm("Invoice/Print.pdf", $pdf_vars); $headerFile = BASEDIR . "/var/temp/" . date("U") . "-" . rand(1000, 9999) . ".html";
//$pdfpath = $pdf->render(); file_put_contents($headerFile, $headerHtml);
$pdf->download($invoice->invoice_number.".pdf");
$pdf = new PdfForm("Invoice/PDF_MAIN", $pdf_vars);
$wkhtmltopdfArgs = "--header-html $headerFile --footer-html " . BASEDIR . "/Layout/default/Invoice/PDF_FOOTER.html";
$pdf->download($invoice->invoice_number . ".pdf", $wkhtmltopdfArgs);
}
public function getBankQRCode($paymentReference, $amount) {
$xinonIBAN = "DE89370400440532013000";
$xinonBIC = "COBADEFFXXX";
$epc = "BCD
001
1
SCT
$xinonBIC
Xinon GmbH
$xinonIBAN
EUR$amount
XINO
$paymentReference
XINON GmbH";
return (new QRCode)->render($epc);
} }
protected function runInvoicingAction() { protected function runInvoicingAction() {
@@ -129,7 +157,7 @@ class InvoiceController extends mfBaseController {
$p = 0; $p = 0;
// get pairs of owner_id and billingaddress_id, so each will be its own invoice // get pairs of owner_id and billingaddress_id, so each will be its own invoice
foreach(BillingModel::getInvoiceBaseData(['invoice_id' => null]) as $base) { foreach (BillingModel::getInvoiceBaseData(['invoice_id' => null]) as $base) {
//var_dump($base); continue; //var_dump($base); continue;
$owner_id = $base["owner_id"]; $owner_id = $base["owner_id"];
$billingaddress_id = $base["billingaddress_id"]; $billingaddress_id = $base["billingaddress_id"];
@@ -139,14 +167,13 @@ class InvoiceController extends mfBaseController {
$bill_positions = []; $bill_positions = [];
$credit_positions = []; $credit_positions = [];
$billing_rows = BillingModel::search([ $billing_rows = BillingModel::search(["owner_id" => $owner_id,
"owner_id" => $owner_id,
"billingaddress_id" => $billingaddress_id, "billingaddress_id" => $billingaddress_id,
"billing_type" => $billing_type, "billing_type" => $billing_type,
"billing_delivery" => $billing_delivery, "billing_delivery" => $billing_delivery,
"invoice_id" => null]); "invoice_id" => null]);
if(!count($billing_rows)) { if (!count($billing_rows)) {
die("Keine nicht verrechneten Billing records für billingaddress_id"); die("Keine nicht verrechneten Billing records für billingaddress_id");
} }
@@ -154,7 +181,7 @@ class InvoiceController extends mfBaseController {
$invoice_data = []; $invoice_data = [];
foreach($billing_rows as $bill) { foreach ($billing_rows as $bill) {
$vatrate = $bill->vatrate; $vatrate = $bill->vatrate;
$price = $bill->price; $price = $bill->price;
$price_total = $bill->price * $bill->amount; $price_total = $bill->price * $bill->amount;
@@ -178,8 +205,8 @@ class InvoiceController extends mfBaseController {
$position_data["billing_period"] = $bill->billing_period; $position_data["billing_period"] = $bill->billing_period;
if($is_setup_only) { if ($is_setup_only) {
$this->log->debug("Contract ID ". $bill->contract_id." is setup only"); $this->log->debug("Contract ID " . $bill->contract_id . " is setup only");
$position_data["price"] = $price_setup; $position_data["price"] = $price_setup;
$position_data["price_total"] = $price_setup_total; $position_data["price_total"] = $price_setup_total;
$position_data["price_gross"] = $price_setup_gross; $position_data["price_gross"] = $price_setup_gross;
@@ -195,10 +222,10 @@ class InvoiceController extends mfBaseController {
$new_position = InvoicepositionModel::create($position_data); $new_position = InvoicepositionModel::create($position_data);
$bill_positions[] = $new_position; $bill_positions[] = $new_position;
if($add_setup_position) { if ($add_setup_position) {
$this->log->debug("Adding Setup Invoiceposition for Contract ID ". $bill->contract_id); $this->log->debug("Adding Setup Invoiceposition for Contract ID " . $bill->contract_id);
$setup_data = $position_data; $setup_data = $position_data;
$setup_data["product_name"] = "Herstellungskosten ".$bill->product_name; $setup_data["product_name"] = "Herstellungskosten " . $bill->product_name;
$setup_data["product_info"] = ""; $setup_data["product_info"] = "";
$setup_data["price"] = $price_setup; $setup_data["price"] = $price_setup;
$setup_data["price_total"] = $price_setup * $bill->amount; $setup_data["price_total"] = $price_setup * $bill->amount;
@@ -239,7 +266,6 @@ class InvoiceController extends mfBaseController {
$invoice_data["total_vat"] = 0; $invoice_data["total_vat"] = 0;
} }
@@ -251,7 +277,7 @@ class InvoiceController extends mfBaseController {
$invoice->startTransaction(); $invoice->startTransaction();
try { try {
if(!$invoice->save()) { if (!$invoice->save()) {
$invoice->rollbackTransaction(); $invoice->rollbackTransaction();
die("Error saving Invoice"); die("Error saving Invoice");
} }
@@ -260,13 +286,13 @@ class InvoiceController extends mfBaseController {
$total_gross = 0; $total_gross = 0;
$total_vat = 0; $total_vat = 0;
foreach($bill_positions as $position) { foreach ($bill_positions as $position) {
// on error: rollback transaction // on error: rollback transaction
// add Invoice::id to Invoiceposition // add Invoice::id to Invoiceposition
$position->invoice_id = $invoice->id; $position->invoice_id = $invoice->id;
if(!$position->vatrate) { if (!$position->vatrate) {
$total_net += $position->price_total; $total_net += $position->price_total;
} else { } else {
$total_vat += ($position->price_total / 100) * $position->vatrate; $total_vat += ($position->price_total / 100) * $position->vatrate;
@@ -275,20 +301,20 @@ class InvoiceController extends mfBaseController {
} }
// save Invoiceposition // save Invoiceposition
if(!$position->save()) { if (!$position->save()) {
$invoice->rollbackTransaction(); $invoice->rollbackTransaction();
die("Error saving Invoiceposition"); die("Error saving Invoiceposition");
} }
// ad Invoice::id to Bill // ad Invoice::id to Bill
$bill = new Billing($position->billing_id); $bill = new Billing($position->billing_id);
if(!$bill->id) { if (!$bill->id) {
$invoice->rollbackTransaction(); $invoice->rollbackTransaction();
die("Bill for Invoiceposition not found"); die("Bill for Invoiceposition not found");
} }
$bill->invoice_id = $invoice->id; $bill->invoice_id = $invoice->id;
if(!$bill->save()) { if (!$bill->save()) {
$invoice->rollbackTransaction(); $invoice->rollbackTransaction();
die("error saving invoice_id to bill"); die("error saving invoice_id to bill");
} }
@@ -300,7 +326,7 @@ class InvoiceController extends mfBaseController {
$invoice->total_gross = $total_gross; $invoice->total_gross = $total_gross;
$invoice->total_vat = $total_vat; $invoice->total_vat = $total_vat;
if(!$invoice->save()) { if (!$invoice->save()) {
$invoice->rollbackTransaction(); $invoice->rollbackTransaction();
die("Error saving totals in Invoice"); die("Error saving totals in Invoice");
} }
@@ -310,7 +336,7 @@ class InvoiceController extends mfBaseController {
$invoice->invoice_number = $new_num; $invoice->invoice_number = $new_num;
$invoice->invoice_date = date("U"); $invoice->invoice_date = date("U");
if(!$invoice->save()) { if (!$invoice->save()) {
$invoice->rollbackTransaction(); $invoice->rollbackTransaction();
die("Error saving Invoice number and date"); die("Error saving Invoice number and date");
} }
@@ -319,7 +345,7 @@ class InvoiceController extends mfBaseController {
$invoice->commitTransaction(); $invoice->commitTransaction();
$i++; $i++;
} catch (Exception $e) { } catch (Exception $e) {
if($invoice) { if ($invoice) {
$invoice->rollbackTransaction(); $invoice->rollbackTransaction();
} }

View File

@@ -16,23 +16,23 @@ class PdfForm {
} }
public function render() { public function render($additionalArgs = false): string {
$this->layout->setTemplate($this->template); $this->layout->setTemplate($this->template);
$this->layout->set("ressourcePathPrefix", BASEDIR."/public/"); $this->layout->set("ressourcePathPrefix", BASEDIR."/public/");
foreach($this->variables as $name => $value) { foreach($this->variables as $name => $value) {
$this->layout->set($name, $value); $this->layout->set($name, $value);
} }
$fullpath = $this->layout->renderPDF(); $fullpath = $this->layout->renderPDF(false, $additionalArgs);
$this->fullpath = $fullpath; $this->fullpath = $fullpath;
$this->returnValues = $this->layout->getReturnedValue(); $this->returnValues = $this->layout->getReturnedValue();
return $fullpath; return $fullpath;
} }
public function download($filename = false) { public function download($filename = false, $additionalArgs = false) {
if(!$this->fullpath) { if(!$this->fullpath) {
$this->render(); $this->render($additionalArgs);
} }
$filepath = $this->fullpath; $filepath = $this->fullpath;