Add Gutschrift functionality to ManualInvoice with modal and backend support

This commit is contained in:
2025-12-02 15:46:58 +01:00
parent e2dfa4a50c
commit 5feaadb171
8 changed files with 703 additions and 1253 deletions

View File

@@ -1,233 +0,0 @@
<?php
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Output\QROutputInterface;
class ManualInvoice extends mfBaseModel {
private $positions;
private $pdf;
public function createPdf() {
if($this->id) {
$invoice_number = $this->invoice_number;
$invoice_date = $this->invoice_date;
} else {
$invoice_number = "PROFORMA";
$invoice_date = 1;
}
$filename = "";
$positions = $this->getProperty("positions");
$vat = [];
foreach ($positions as $p) {
if (!array_key_exists($p->vatrate, $vat)) {
$vat[$p->vatrate] = 0;
}
$vat[$p->vatrate] += $p->price_gross - ($p->price * $p->amount);
}
$pdf_vars = [
"invoice" => $this,
"vat" => $vat,
"bank_iban" => TT_INVOICE_BANK_IBAN,
"bank_bic" => TT_INVOICE_BANK_BIC,
"bank_bank"=> TT_INVOICE_BANK_BANK,
"bank_owner" => TT_INVOICE_BANK_OWNER
];
// Replace placeholders in header
$headerHtml = file_get_contents(BASEDIR . "/Layout/default/ManualInvoice/PDF_HEADER.html");
$headerHtml = str_replace("{{ basedir }}", BASEDIR, $headerHtml);
$headerHtml = str_replace("{{ addressLine_1 }}", $this->company ? $this->company : "", $headerHtml);
$headerHtml = str_replace("{{ addressLine_2 }}", $this->firstname . " " . $this->lastname, $headerHtml);
$headerHtml = str_replace("{{ addressLine_3 }}", nl2br($this->street), $headerHtml);
$headerHtml = str_replace("{{ addressLine_4 }}", $this->zip . " " . $this->city, $headerHtml);
$headerHtml = str_replace("{{ addressLine_5 }}", $this->country != "Österreich" ? $this->country : "", $headerHtml);
$headerHtml = str_replace("{{ customerNumber }}", $this->customer_number, $headerHtml);
$headerHtml = str_replace("{{ billingAccount }}", $this->fibu_account_number, $headerHtml);
$headerHtml = str_replace("{{ invoiceNumber }}", $invoice_number, $headerHtml);
$headerHtml = str_replace("{{ invoiceDate }}", date("d.m.Y", $invoice_date), $headerHtml);
$headerHtml = str_replace("{{ vatHtml }}", $this->uid ? "<tr><td>Ihre UID:</td><td>" . $this->uid . "</td></tr>" : "", $headerHtml);
$headerHtml = str_replace("{{ qrCodeSrc }}", $this->getSepaQRCode($invoice_number, round($this->total_gross, 2)), $headerHtml);
$headerFile = BASEDIR . "/var/temp/manualinvoice_header-" . date("U") . "-" . rand(1000, 9999) . ".html";
file_put_contents($headerFile, $headerHtml);
// Replace placeholders in footer
$footerHtml = file_get_contents(BASEDIR . "/Layout/default/ManualInvoice/PDF_FOOTER.html");
$footerHtml = str_replace("{{ bank_iban }}", TT_INVOICE_BANK_IBAN_FORMATTED, $footerHtml);
$footerHtml = str_replace("{{ bank_bic }}", TT_INVOICE_BANK_BIC, $footerHtml);
$footerHtml = str_replace("{{ bank_bank }}", TT_INVOICE_BANK_BANK, $footerHtml);
$footerHtml = str_replace("{{ bank_owner }}", TT_INVOICE_BANK_OWNER, $footerHtml);
$footerFile = BASEDIR . "/var/temp/manualinvoice_footer-" . date("U") . "-" . rand(1000, 9999) . ".html";
file_put_contents($footerFile, $footerHtml);
$pdf = new PdfForm("ManualInvoice/PDF_MAIN", $pdf_vars);
$wkhtmltopdfArgs = "--header-html $headerFile --footer-html $footerFile";
$filename = $pdf->render($wkhtmltopdfArgs);
return $filename;
}
public function getSepaQRCode($paymentReference, $amount) {
$xinonIBAN = TT_INVOICE_BANK_IBAN;
$xinonBIC = TT_INVOICE_BANK_BIC;
$xinonOwner = TT_INVOICE_BANK_OWNER;
$epc = "BCD
001
1
SCT
$xinonBIC
$xinonOwner
$xinonIBAN
EUR$amount
XINO
$paymentReference
XINON GmbH";
return (new QRCode)->render($epc);
}
public function sendByEmail($to_email = false) {
if(!$this->id) return false;
$pdf = $this->getProperty("pdf");
if(!$pdf || !$pdf->name) {
return false;
}
$pdf_filename = false;
try {
$pdf_filename = $pdf->getFullPath();
} catch(\Exception $e) {
$this->log->error("File for ManualInvoice ".$this->id." not found");
}
if(!$pdf_filename || !file_exists($pdf_filename)) {
return false;
}
$tpl = new Layout();
$tpl->setTemplate("Emailtemplates/invoice/invoice-email");
$pdf_vars = [
"invoice" => $this
];
foreach($pdf_vars as $name => $val) {
$tpl->set($name, $val);
}
$body = $tpl->render();
$values = $tpl->getReturnedValue();
$subject = $values['subject'];
$from = $values['from_email'];
$from_name = $values['from_email_name'];
if($to_email) {
$to = $to_email;
} else {
$to = trim($this->email);
}
if(!$to) {
$this->log->error(__METHOD__.": ManualInvoice ".$this->invoice_number." missing email");
}
if(!$subject || !$from || !$from_name || !$to) {
$this->log->warn(__METHOD__.": ManualInvoice ".$this->invoice_number." could not be sent. Values missing. (subject: '$subject', from: '$from_name', from_email: '$from', to: '$to')");
return false;
} else {
$email = new Emailnotification("ManualInvoice", $this->id);
$email->setSubject($subject);
$email->setBody($body);
$email->setFrom($from, $from_name);
$email->setTo($to);
$email->setHeader("X-".MFAPPNAME."-Iid", $this->id);
$email->addAttachment($pdf_filename, null, $pdf->filename, "application/pdf");
$email->send();
$this->log->info(__METHOD__.": Sending ManualInvoice ".$this->invoice_number." to $to");
}
return true;
}
public function getProperty($name) {
if($this->$name == null) {
if(!$this->id) {
return null;
}
if($name == "positions") {
$positions = ManualInvoicepositionModel::search(["manualinvoice_id" => $this->id]);
$this->positions = $positions;
return $this->positions;
}
if($name == "pdf") {
$ifile = ManualInvoiceFileModel::getFirst(["manualinvoice_id" => $this->id]);
if(!$ifile) return null;
$file = $ifile->file;
if(!$file) return null;
$this->pdf = $file;
return $this->pdf;
}
if($name == "creator") {
$this->creator = mfValuecache::singleton()->get("Worker-id-".$this->create_by);
if($this->creator === null) {
$this->creator = new User($this->create_by);
if($this->creator->id) {
mfValuecache::singleton()->set("Worker-id-".$this->create_by, $this->creator);
}
}
return $this->creator;
}
if($name == "editor") {
$this->editor = mfValuecache::singleton()->get("Worker-id-".$this->edit_by);
if($this->editor === null) {
$this->editor = new User($this->edit_by);
if($this->editor->id) {
mfValuecache::singleton()->set("Worker-id-".$this->edit_by, $this->editor);
}
}
return $this->editor;
}
$classname = ucfirst($name);
$idfield = $name."_id";
$this->$name = mfValuecache::singleton()->get("mfObjectmodel-$name-".$this->$idfield);
if(!$this->$name) {
$this->$name = new $classname($this->$idfield);
}
if($this->$name->id) {
mfValuecache::singleton()->set("mfObjectmodel-$name-".$this->$name->id, $this->$name);
return $this->$name;
} else {
return null;
}
}
return $this->$name;
}
}

View File

@@ -4,11 +4,10 @@ class ManualInvoiceController extends TTCrud
{
protected string $headerTitle = 'Manuelle Rechnungen';
protected bool $createText = false;
protected array $additionalJS = ["js/pages/ManualInvoice/ManualInvoice.js"];
protected array $additionalHead = ["<link rel='stylesheet' href='/js/pages/ManualInvoice/ManualInvoice.css'>"];
private array $tempPositions = [];
//@formatter:off
protected array $columns = [
['key' => 'id', 'text' => 'ID', 'table' => ['visible' => false], 'modal' => false],
['key' => 'invoice_number', 'text' => 'Rechnungsnr.', 'table' => ['sortable' => true, 'filter' => 'search']],
@@ -19,79 +18,54 @@ class ManualInvoiceController extends TTCrud
['key' => 'customer_number', 'text' => 'Kundennr.', 'table' => ['sortable' => true, 'filter' => 'search']],
['key' => 'total', 'text' => 'Netto', 'table' => ['sortable' => true, 'formatter' => 'formatPrice']],
['key' => 'total_gross', 'text' => 'Brutto', 'table' => ['sortable' => true, 'formatter' => 'formatPrice']],
['key' => 'status', 'text' => 'Status', 'table' => ['filter' => 'select', 'filterOptions' => [
['value' => 'draft', 'text' => 'Entwurf'],
['value' => 'finalized', 'text' => 'Finalisiert'],
['value' => 'exported', 'text' => 'Exportiert'],
]]],
['key' => 'billing_type', 'text' => 'Zahlungsart', 'table' => ['filter' => 'select', 'filterOptions' => [
['value' => 'invoice', 'text' => 'Rechnung'],
['value' => 'sepa', 'text' => 'SEPA'],
]]],
['key' => 'actions', 'text' => 'Aktionen', 'modal' => false, 'table' => ['filter' => false, 'sortable' => false]],
];
//@formatter:on
protected array $additionalActions = [
['key' => 'createGutschrift', 'title' => 'Gutschrift erstellen', 'class' => 'fas fa-file-invoice text-warning']
];
protected function createPDFAction($returnFilename = false) {
// Get data from POST for preview or from database for saved invoice
$post = json_decode(file_get_contents('php://input'), true);
$invoice = (object)[];
$positions = [];
if (isset($post['preview']) && $post['preview'] === true) {
// Create temporary invoice object from POST data for preview
$invoice = (object)[];
$invoice->id = 0;
$invoice->invoice_number = $post['invoice_number'] ?? null;
$invoice->invoice_date = $post['invoice_date'] ?? time();
$invoice->customer_number = $post['customer_number'] ?? 0;
$invoice->fibu_account_number = $post['fibu_account_number'] ?? 0;
$invoice->company = $post['company'] ?? '';
$invoice->firstname = $post['firstname'] ?? '';
$invoice->lastname = $post['lastname'] ?? '';
$invoice->street = $post['street'] ?? '';
$invoice->zip = $post['zip'] ?? '';
$invoice->city = $post['city'] ?? '';
$invoice->country = $post['country'] ?? 'Österreich';
$invoice->email = $post['email'] ?? '';
$invoice->uid = $post['uid'] ?? '';
$invoice->tax_text = $post['tax_text'] ?? '';
$invoice->billing_type = $post['billing_type'] ?? 'invoice';
$invoice->total = $post['total'] ?? 0;
$invoice->total_gross = $post['total_gross'] ?? 0;
$positions = [];
foreach ($post['positions'] ?? [] as $pos) {
$positions[] = (object)$pos;
}
$invoice = (object) array_merge([
'id' => 0, 'invoice_number' => null, 'invoice_date' => time(),
'customer_number' => 0, 'fibu_account_number' => 0,
'company' => '', 'firstname' => '', 'lastname' => '',
'street' => '', 'zip' => '', 'city' => '', 'country' => 'Österreich',
'email' => '', 'uid' => '', 'tax_text' => '', 'billing_type' => 'invoice',
'total' => 0, 'total_gross' => 0
], $post);
$positions = array_map(fn($p) => (object)$p, $post['positions'] ?? []);
} else {
// Load from database
$id = $this->request->id ?? $post['id'] ?? null;
if (!$id) {
if (!$id || !($invoice = ManualInvoiceModel::get($id))) {
http_response_code(500);
self::returnJson(['success' => false, 'message' => 'Rechnung wurde nicht gefunden']);
return;
}
$invoice = new ManualInvoice($id);
if (!$invoice->id) {
http_response_code(500);
self::returnJson(['success' => false, 'message' => 'Rechnung wurde nicht gefunden']);
return;
}
$positions = $invoice->getProperty('positions');
}
// Calculate VAT totals
$vat = [];
foreach ($positions as $p) {
$vatrate = is_object($p) ? $p->vatrate : $p['vatrate'];
$price_gross = is_object($p) ? $p->price_gross : ($p['price_gross'] ?? 0);
$price_total = is_object($p) ? $p->price_total : ($p['price_total'] ?? 0);
if (!array_key_exists($vatrate, $vat)) {
$vat[$vatrate] = 0;
}
$vat[$vatrate] += $price_gross - $price_total;
$pObj = (object)$p;
$vat[$pObj->vatrate] = ($vat[$pObj->vatrate] ?? 0) + ($pObj->price_gross ?? 0) - ($pObj->price_total ?? 0);
}
// Convert positions array to objects if needed
$invoice->positions = $positions;
$pdf_vars = [
"invoice" => $invoice,
"vat" => $vat,
@@ -101,44 +75,40 @@ class ManualInvoiceController extends TTCrud
"bank_owner" => TT_INVOICE_BANK_OWNER
];
// Replace placeholders in header
$headerHtml = file_get_contents(BASEDIR . "/Layout/default/ManualInvoice/PDF_HEADER.html");
$headerHtml = str_replace("{{ basedir }}", BASEDIR, $headerHtml);
$headerHtml = str_replace("{{ addressLine_1 }}", $invoice->company ? $invoice->company : "", $headerHtml);
$headerHtml = str_replace("{{ addressLine_2 }}", trim($invoice->firstname . " " . $invoice->lastname), $headerHtml);
$headerHtml = str_replace("{{ addressLine_3 }}", $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 }}", $invoice->fibu_account_number ?? '', $headerHtml);
$headerHtml = str_replace("{{ invoiceNumber }}", $invoice->invoice_number ?? "VORSCHAU", $headerHtml);
$headerHtml = str_replace("{{ invoiceDate }}", date("d.m.Y", $invoice->invoice_date ?? time()), $headerHtml);
$headerHtml = str_replace("{{ vatHtml }}", ($invoice->uid ?? '') ? "<tr><td>Ihre UID:</td><td>" . $invoice->uid . "</td></tr>" : "", $headerHtml);
// Generate QR code for SEPA payment
$qrCode = $this->generateSepaQRCode($invoice->invoice_number ?? "VORSCHAU", round($invoice->total_gross ?? 0, 2));
$headerHtml = str_replace("{{ qrCodeSrc }}", $qrCode, $headerHtml);
$replacements = [
"{{ basedir }}" => BASEDIR,
"{{ addressLine_1 }}" => $invoice->company ?: "",
"{{ addressLine_2 }}" => trim($invoice->firstname . " " . $invoice->lastname),
"{{ addressLine_3 }}" => $invoice->street ?? '',
"{{ addressLine_4 }}" => ($invoice->zip ?? '') . " " . ($invoice->city ?? ''),
"{{ addressLine_5 }}" => ($invoice->country ?? '') != "Österreich" ? ($invoice->country ?? '') : "",
"{{ customerNumber }}" => $invoice->customer_number ?? '',
"{{ billingAccount }}" => $invoice->fibu_account_number ?? '',
"{{ invoiceNumber }}" => $invoice->invoice_number ?? "VORSCHAU",
"{{ invoiceDate }}" => date("d.m.Y", $invoice->invoice_date ?? time()),
"{{ vatHtml }}" => ($invoice->uid ?? '') ? "<tr><td>Ihre UID:</td><td>" . $invoice->uid . "</td></tr>" : "",
"{{ qrCodeSrc }}" => $this->generateSepaQRCode($invoice->invoice_number ?? "VORSCHAU", round($invoice->total_gross ?? 0, 2))
];
$headerHtml = str_replace(array_keys($replacements), array_values($replacements), file_get_contents(BASEDIR . "/Layout/default/ManualInvoice/PDF_HEADER.html"));
$headerFile = BASEDIR . "/var/temp/manualinvoice_header-" . date("U") . "-" . rand(1000, 9999) . ".html";
file_put_contents($headerFile, $headerHtml);
// Replace placeholders in footer
$footerHtml = file_get_contents(BASEDIR . "/Layout/default/ManualInvoice/PDF_FOOTER.html");
$footerHtml = str_replace("{{ bank_iban }}", TT_INVOICE_BANK_IBAN_FORMATTED, $footerHtml);
$footerHtml = str_replace("{{ bank_bic }}", TT_INVOICE_BANK_BIC, $footerHtml);
$footerHtml = str_replace("{{ bank_bank }}", TT_INVOICE_BANK_BANK, $footerHtml);
$footerHtml = str_replace("{{ bank_owner }}", TT_INVOICE_BANK_OWNER, $footerHtml);
$footerReplacements = [
"{{ bank_iban }}" => TT_INVOICE_BANK_IBAN_FORMATTED,
"{{ bank_bic }}" => TT_INVOICE_BANK_BIC,
"{{ bank_bank }}" => TT_INVOICE_BANK_BANK,
"{{ bank_owner }}" => TT_INVOICE_BANK_OWNER
];
$footerHtml = str_replace(array_keys($footerReplacements), array_values($footerReplacements), file_get_contents(BASEDIR . "/Layout/default/ManualInvoice/PDF_FOOTER.html"));
$footerFile = BASEDIR . "/var/temp/manualinvoice_footer-" . date("U") . "-" . rand(1000, 9999) . ".html";
file_put_contents($footerFile, $footerHtml);
$pdf = new PdfForm("ManualInvoice/PDF_MAIN", $pdf_vars);
$wkhtmltopdfArgs = "--header-html $headerFile --footer-html $footerFile";
$filename = $pdf->render($wkhtmltopdfArgs);
$filename = $pdf->render("--header-html $headerFile --footer-html $footerFile");
if ($returnFilename === true) return $filename;
// Return the PDF inline for preview
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . ($invoice->invoice_number ?? 'preview') . '.pdf"');
readfile($filename);
@@ -147,113 +117,185 @@ class ManualInvoiceController extends TTCrud
protected function downloadInvoicePdfAction() {
$id = $this->request->id;
if (!is_numeric($id) || !$id) {
if (!is_numeric($id) || !$id || !($invoice = ManualInvoiceModel::get($id))) {
$this->layout()->setFlash("Rechnung nicht gefunden", "error");
$this->redirect("ManualInvoice");
}
$invoice = new ManualInvoice($id);
if (!$invoice->id) {
$this->layout()->setFlash("Rechnung nicht gefunden", "error");
$this->redirect("ManualInvoice");
}
// Use createPDFAction to get filename
$pdf_filename = $this->createPDFAction(true);
if(!$pdf_filename || !file_exists($pdf_filename)) {
if(!($pdf_filename = $this->createPDFAction(true)) || !file_exists($pdf_filename)) {
$this->layout()->setFlash("PDF-Datei konnte nicht erstellt werden");
$this->redirect("ManualInvoice");
}
header('Content-Type: application/octet-stream');
header('Content-disposition: attachment; filename="'.$invoice->invoice_number.'.pdf"');
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application/pdf');
header("Content-Length: " . filesize($pdf_filename));
header('Content-disposition: attachment; filename="'.$invoice->invoice_number.'.pdf"');
header('Content-Length: ' . filesize($pdf_filename));
readfile($pdf_filename);
exit;
}
protected function beforeCreate(&$data): bool {
// Generate invoice number if not provided
if (empty($data['invoice_number'])) {
$data['invoice_number'] = ManualInvoiceModel::getNextInvoiceNumber();
if (isset($data['positions']) && is_array($data['positions'])) {
$this->tempPositions = $data['positions'];
unset($data['positions']);
}
// Set default values
if (empty($data['invoice_date'])) {
$data['invoice_date'] = time();
}
$me = new User();
$me->loadMe();
$data = array_merge([
'invoice_number' => ManualInvoiceModel::getNextInvoiceNumber(),
'invoice_date' => time(),
'status' => 'draft',
'fibu_payment_skonto' => 0,
'fibu_payment_skonto_rate' => 0,
'total' => 0,
'total_gross' => 0,
'create_by' => $me->id,
'edit_by' => $me->id,
'create' => time(),
'edit' => time()
], $data);
return true;
}
protected function afterCreate($data) {
$invoiceId = $data['id'];
// Save positions
if (isset($data['positions']) && is_array($data['positions'])) {
foreach ($data['positions'] as $position) {
$position['manualinvoice_id'] = $invoiceId;
$posModel = ManualInvoicepositionModel::create($position);
$posModel->save();
}
}
// Recalculate totals
$this->recalculateTotals($invoiceId);
$this->savePositions($data['id']);
$this->recalculateTotals($data['id']);
}
protected function beforeUpdate(&$data): bool {
if (isset($data['positions']) && is_array($data['positions'])) {
$this->tempPositions = $data['positions'];
unset($data['positions']);
}
if (isset($data['id']) && ($invoice = ManualInvoiceModel::get($data['id'])) && $invoice->status === 'exported') {
$this->infoMessages['update'] = 'Rechnung wurde bereits exportiert und kann nicht mehr bearbeitet werden';
return false;
}
$me = new User();
$me->loadMe();
$data['edit_by'] = $me->id;
$data['edit'] = time();
return true;
}
protected function afterUpdate($data) {
$invoiceId = $data['id'];
$existingPositions = ManualInvoicepositionModel::search(['manualinvoice_id' => $data['id']]);
foreach ($existingPositions as $pos) ManualInvoicepositionModel::delete($pos->id);
// Delete existing positions
$existingPositions = ManualInvoicepositionModel::search(['manualinvoice_id' => $invoiceId]);
foreach ($existingPositions as $pos) {
$pos->delete();
$this->savePositions($data['id']);
$this->recalculateTotals($data['id']);
}
private function savePositions($invoiceId) {
if (empty($this->tempPositions)) return;
$me = new User();
$me->loadMe();
foreach ($this->tempPositions as $position) {
ManualInvoicepositionModel::create(array_merge([
'manualinvoice_id' => $invoiceId,
'start_date' => date('Y-m-d'),
'create_by' => $me->id,
'edit_by' => $me->id,
'create' => time(),
'edit' => time()
], $position));
}
// Save new positions
if (isset($data['positions']) && is_array($data['positions'])) {
foreach ($data['positions'] as $position) {
$position['manualinvoice_id'] = $invoiceId;
$posModel = ManualInvoicepositionModel::create($position);
$posModel->save();
}
}
// Recalculate totals
$this->recalculateTotals($invoiceId);
$this->tempPositions = [];
}
protected function recalculateTotals($invoiceId) {
$invoice = new ManualInvoice($invoiceId);
if (!($invoice = ManualInvoiceModel::get($invoiceId))) return;
$positions = ManualInvoicepositionModel::search(['manualinvoice_id' => $invoiceId]);
$invoice->total = array_sum(array_column($positions, 'price_total'));
$invoice->total_gross = array_sum(array_column($positions, 'price_gross'));
$invoice->save();
}
$total = 0;
$total_gross = 0;
protected function getAction() {
$filter = $this->postData['filters'] ?? [];
$order = $this->postData['order']['key'] ? $this->postData['order'] : ($this->defaultOrder ?? ['key' => null, 'order' => 'ASC']);
$page = $this->postData['pagination']['page'] ?? 1;
$perPage = $this->postData['pagination']['per_page'] ?? 10;
foreach ($positions as $pos) {
$total += $pos->price_total;
$total_gross += $pos->price_gross;
$rows = ManualInvoiceModel::getAll($filter, $perPage, ($page - 1) * $perPage, $order);
$filteredAvailable = ManualInvoiceModel::count($filter);
$totalRows = ManualInvoiceModel::count();
foreach ($rows as &$row) {
$row->customerName = trim(($row->company ?: '') . ' ' . $row->firstname . ' ' . $row->lastname);
$row->positions = array_map([$this, 'formatPosition'], $row->getProperty('positions'));
}
$invoice->total = $total;
$invoice->total_gross = $total_gross;
$invoice->save();
self::returnJson([
"rows" => $rows,
"autoCompleteData" => [],
"pagination" => [
"page" => $page,
"total_pages" => ceil($filteredAvailable / $perPage),
"per_page" => $perPage,
"filtered_available" => intval($filteredAvailable),
"total_rows" => intval($totalRows)
]
]);
}
protected function getByIdAction() {
$id = $_GET['id'] ?? null;
if (!$id || !is_numeric($id)) {
http_response_code(500);
self::returnJson(['success' => false, 'message' => 'No ID provided.']);
die();
}
if (!($invoice = ManualInvoiceModel::get($id))) {
http_response_code(404);
self::returnJson(['success' => false, 'message' => 'Invoice not found.']);
die();
}
$data = (array) $invoice;
$data['positions'] = array_map([$this, 'formatPosition'], $invoice->getProperty('positions'));
self::returnJson($data);
}
private function formatPosition($pos) {
return [
'id' => $pos->id,
'manualinvoice_id' => $pos->manualinvoice_id,
'billing_id' => $pos->billing_id,
'contract_id' => $pos->contract_id,
'start_date' => $pos->start_date,
'end_date' => $pos->end_date,
'matchcode' => $pos->matchcode,
'product_id' => $pos->product_id,
'product_name' => $pos->product_name,
'product_info' => $pos->product_info,
'amount' => $pos->amount,
'price' => $pos->price,
'price_total' => $pos->price_total,
'price_gross' => $pos->price_gross,
'vatrate' => $pos->vatrate,
'fibu_cost_account' => $pos->fibu_cost_account,
'fibu_cost_account_legacy' => $pos->fibu_cost_account_legacy,
'fibu_taxcode' => $pos->fibu_taxcode,
'billing_period' => $pos->billing_period,
'options' => $pos->options
];
}
protected function customRowsHandler($rows) {
foreach ($rows as &$row) {
// Add customer name
$row->customerName = trim(($row->company ? $row->company : '') . ' ' . $row->firstname . ' ' . $row->lastname);
$row->customerName = trim(($row->company ?: '') . ' ' . $row->firstname . ' ' . $row->lastname);
}
return $rows;
}
@@ -262,20 +304,168 @@ class ManualInvoiceController extends TTCrud
$xinonIBAN = TT_INVOICE_BANK_IBAN;
$xinonBIC = TT_INVOICE_BANK_BIC;
$xinonOwner = TT_INVOICE_BANK_OWNER;
$epc = "BCD
001
1
SCT
$xinonBIC
$xinonOwner
$xinonIBAN
EUR$amount
XINO
$paymentReference
XINON GmbH";
$epc = "BCD\n001\n1\nSCT\n$xinonBIC\n$xinonOwner\n$xinonIBAN\nEUR$amount\nXINO\n$paymentReference\n\nXINON GmbH";
return (new \chillerlan\QRCode\QRCode)->render($epc);
}
}
protected function getInvoiceForGutschriftAction() {
if (!($id = $_GET['id'] ?? null) || !($invoice = ManualInvoiceModel::get($id))) {
self::returnJson(['success' => false, 'message' => 'Invoice not found']);
}
if ($invoice->total < 0) {
self::returnJson(['success' => false, 'message' => 'Kann keine Gutschrift für eine Gutschrift erstellen']);
}
$positions = $invoice->getProperty('positions');
$existingCredits = ManualInvoiceModel::getAll(['credit_for_invoice_id' => $id]);
$creditedAmounts = [];
foreach ($existingCredits as $credit) {
foreach ($credit->getProperty('positions') as $creditPos) {
$key = $creditPos->product_id . '_' . $creditPos->matchcode;
$creditedAmounts[$key] = ($creditedAmounts[$key] ?? 0) + abs($creditPos->amount);
}
}
$availablePositions = [];
foreach ($positions as $pos) {
$key = $pos->product_id . '_' . $pos->matchcode;
$availableAmount = $pos->amount - ($creditedAmounts[$key] ?? 0);
if ($availableAmount > 0) {
$availablePositions[] = [
'id' => $pos->id,
'product_name' => $pos->product_name,
'product_info' => $pos->product_info,
'original_amount' => $pos->amount,
'credited_amount' => $creditedAmounts[$key] ?? 0,
'available_amount' => $availableAmount,
'price' => $pos->price,
'vatrate' => $pos->vatrate,
'product_id' => $pos->product_id,
'matchcode' => $pos->matchcode,
'fibu_cost_account' => $pos->fibu_cost_account,
'fibu_taxcode' => $pos->fibu_taxcode
];
}
}
self::returnJson([
'success' => true,
'invoice' => [
'id' => $invoice->id,
'invoice_number' => $invoice->invoice_number,
'customer_name' => trim(($invoice->company ?? '') . ' ' . $invoice->firstname . ' ' . $invoice->lastname),
'positions' => $availablePositions
]
]);
}
protected function createGutschriftAction() {
$post = json_decode(file_get_contents('php://input'), true);
$originalInvoiceId = $post['original_invoice_id'] ?? null;
$positions = $post['positions'] ?? [];
if (!$originalInvoiceId || empty($positions) || !($originalInvoice = ManualInvoiceModel::get($originalInvoiceId))) {
self::returnJson(['success' => false, 'message' => 'Ungültige Anfrage']);
}
$me = new User();
$me->loadMe();
$invoiceData = [
'invoice_number' => ManualInvoiceModel::getNextInvoiceNumber(),
'invoice_date' => time(),
'owner_id' => $originalInvoice->owner_id,
'billingaddress_id' => $originalInvoice->billingaddress_id,
'customer_number' => $originalInvoice->customer_number,
'fibu_account_number' => $originalInvoice->fibu_account_number,
'fibu_payment_due' => $originalInvoice->fibu_payment_due,
'fibu_payment_skonto' => $originalInvoice->fibu_payment_skonto,
'fibu_payment_skonto_rate' => $originalInvoice->fibu_payment_skonto_rate,
'sepa_date' => $originalInvoice->sepa_date,
'sepa_id' => $originalInvoice->sepa_id,
'sepa_last_date' => $originalInvoice->sepa_last_date,
'fibu_cost_area' => $originalInvoice->fibu_cost_area,
'fibu_cost_account' => $originalInvoice->fibu_cost_account,
'fibu_cost_account_legacy' => $originalInvoice->fibu_cost_account_legacy,
'fibu_taxcode' => $originalInvoice->fibu_taxcode,
'tax_text' => $originalInvoice->tax_text,
'company' => $originalInvoice->company,
'firstname' => $originalInvoice->firstname,
'lastname' => $originalInvoice->lastname,
'street' => $originalInvoice->street,
'zip' => $originalInvoice->zip,
'city' => $originalInvoice->city,
'country' => $originalInvoice->country,
'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,
'bank_account_bic' => $originalInvoice->bank_account_bic,
'total' => 0,
'total_gross' => 0,
'vatgroup_id' => $originalInvoice->vatgroup_id,
'credit_for_invoice_id' => $originalInvoiceId,
'status' => 'finalized',
'create' => time(),
'edit' => time(),
'create_by' => $me->id,
'edit_by' => $me->id
];
if (!($creditInvoiceId = ManualInvoiceModel::create($invoiceData))) {
self::returnJson(['success' => false, 'message' => 'Fehler beim Erstellen der Gutschrift']);
}
foreach ($positions as $pos) {
$priceTotal = (-abs($pos['amount'])) * $pos['price'];
ManualInvoicepositionModel::create([
'manualinvoice_id' => $creditInvoiceId,
'product_id' => $pos['product_id'],
'product_name' => $pos['product_name'],
'product_info' => $pos['product_info'] ?? '',
'amount' => -abs($pos['amount']),
'price' => $pos['price'],
'vatrate' => $pos['vatrate'],
'price_total' => $priceTotal,
'price_gross' => $priceTotal * (1 + $pos['vatrate'] / 100),
'matchcode' => $pos['matchcode'] ?? null,
'fibu_cost_account' => $pos['fibu_cost_account'] ?? null,
'fibu_taxcode' => $pos['fibu_taxcode'] ?? null,
'contract_id' => 0,
'start_date' => date('Y-m-d'),
'billing_period' => 0,
'create_by' => $me->id,
'edit_by' => $me->id,
'create' => time(),
'edit' => time()
]);
}
$this->recalculateTotals($creditInvoiceId);
self::returnJson(['success' => true, 'message' => 'Gutschrift erfolgreich erstellt', 'credit_invoice_id' => $creditInvoiceId]);
}
protected function beforeDelete(): bool {
if ($id = $this->request->id) {
$invoice = ManualInvoiceModel::get($id);
if ($invoice && $invoice->status === 'exported') {
$this->infoMessages['delete'] = 'Rechnung wurde bereits exportiert und kann nicht gelöscht werden';
return false;
}
if (ManualInvoiceModel::count(['credit_for_invoice_id' => $id]) > 0) {
$this->infoMessages['delete'] = 'Rechnung kann nicht gelöscht werden, da bereits Gutschriften existieren';
return false;
}
foreach (ManualInvoicepositionModel::search(['manualinvoice_id' => $id]) as $pos) {
ManualInvoicepositionModel::delete($pos->id);
}
}
return true;
}
}

View File

@@ -1,408 +1,76 @@
<?php
class ManualInvoiceModel {
public $invoice_number;
public $invoice_date;
public $owner_id;
public $billingaddress_id;
public $customer_number;
public $fibu_account_number;
public $fibu_payment_due;
public $fibu_payment_skonto;
public $fibu_payment_skonto_rate;
public $sepa_date;
public $sepa_id;
public $sepa_last_date;
public $fibu_cost_area;
public $fibu_cost_account;
public $fibu_cost_account_legacy;
public $fibu_taxcode;
public $tax_text;
public $company;
public $firstname;
public $lastname;
public $street;
public $zip;
public $city;
public $country;
public $email;
public $uid;
public $billing_type;
public $billing_delivery;
public $bank_account_bank;
public $bank_account_owner;
public $bank_account_iban;
public $bank_account_bic;
public $total;
public $total_gross;
public $vatgroup_id;
public $bmd_export_date;
public $date_delivered;
public $create_by;
public $edit_by;
public $create;
public $edit;
public static function create($data) {
$invoice = new ManualInvoice();
$me = new User();
$me->loadMe();
// Set audit fields
$invoice->create_by = $me->id;
$invoice->edit_by = $me->id;
$invoice->create = time();
$invoice->edit = time();
// Set invoice fields
foreach($data as $field => $value) {
if(property_exists($invoice, $field) && $field != 'id') {
$invoice->$field = $value;
}
}
// Set defaults
if (!$invoice->billing_type) {
$invoice->billing_type = 'invoice';
}
if (!$invoice->billing_delivery) {
$invoice->billing_delivery = 'email';
}
if (!$invoice->total) {
$invoice->total = 0;
}
if (!$invoice->total_gross) {
$invoice->total_gross = 0;
}
if (!$invoice->vatgroup_id) {
$invoice->vatgroup_id = 1; // Default VAT group
}
if (!$invoice->owner_id) {
$invoice->owner_id = 0;
}
if (!$invoice->billingaddress_id) {
$invoice->billingaddress_id = 0;
}
if (!$invoice->customer_number) {
$invoice->customer_number = 0;
}
if ($invoice->save()) {
return $invoice->id;
}
return false;
}
class ManualInvoiceModel extends TTCrudBaseModel {
public int $id;
public ?string $invoice_number;
public int $invoice_date;
public int $owner_id;
public int $billingaddress_id;
public int $customer_number;
public ?int $fibu_account_number;
public ?int $fibu_payment_due;
public int $fibu_payment_skonto;
public int $fibu_payment_skonto_rate;
public ?string $sepa_date;
public ?string $sepa_id;
public ?string $sepa_last_date;
public ?string $fibu_cost_area;
public ?int $fibu_cost_account;
public ?int $fibu_cost_account_legacy;
public ?int $fibu_taxcode;
public ?string $tax_text;
public ?string $company;
public ?string $firstname;
public ?string $lastname;
public string $street;
public string $zip;
public string $city;
public ?string $country;
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;
public ?string $bank_account_bic;
public float $total;
public float $total_gross;
public int $vatgroup_id;
public ?int $bmd_export_date;
public ?int $date_delivered;
public string $status;
public ?int $credit_for_invoice_id;
public int $create_by;
public int $edit_by;
public int $create;
public int $edit;
public static function getNextInvoiceNumber() {
$last_invoice_num = self::getLastInvoiceNumber();
$invoices = parent::getAll(['invoice_number' => '!NULL'], 1, 0, ['key' => 'invoice_number', 'order' => 'DESC']);
$last = $invoices[0]->invoice_number ?? null;
$year = date("Y");
if(!$last_invoice_num) {
return "MRN".date("Y")."-X000001";
}
$year_part = 0;
$num_part = 0;
$m = [];
if(preg_match('/^MRN(\d+)-X(\d+)$/', $last_invoice_num, $m)) {
if(array_key_exists(1, $m)) {
$year_part = $m[1];
if(array_key_exists(2, $m)) {
$num_part = $m[2];
}
}
}
if(!$year_part || !$num_part) {
return "MRN".date("Y")."-X000001";
}
if(date("Y") == $year_part) {
$new_year_part = $year_part;
$new_num_part = $num_part + 1;
if ($last && preg_match('/^MRN(\d+)-X(\d+)$/', $last, $m)) {
$num = ($m[1] == $year) ? $m[2] + 1 : 1;
} else {
$new_year_part = date("Y");
$new_num_part = 1;
$num = 1;
}
$new_invoice_num = "MRN$new_year_part-X".str_pad($new_num_part,"6", "0", STR_PAD_LEFT);
return $new_invoice_num;
return sprintf("MRN%s-X%06d", $year, $num);
}
public static function getLastInvoiceNumber() {
$last_invoice = self::getLast(["invoice_number" => true]);
if(!$last_invoice || !$last_invoice->invoice_number) {
return false;
public function getProperty($name) {
if (!$this->id) return null;
switch ($name) {
case 'positions': return ManualInvoicepositionModel::search(['manualinvoice_id' => $this->id]);
case 'creator': return $this->create_by ? new User($this->create_by) : null;
case 'editor': return $this->edit_by ? new User($this->edit_by) : null;
default:
$classname = ucfirst($name);
$idfield = $name . '_id';
return (property_exists($this, $idfield) && class_exists($classname)) ? new $classname($this->$idfield) : null;
}
return $last_invoice->invoice_number;
}
public static function getAll($filter = [], $limit = null, $offset = 0, $order = ["key" => null]) {
$items = [];
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT ManualInvoice.* FROM ManualInvoice WHERE $where";
if ($order['key'] !== null) {
$orderDir = isset($order['order']) ? $order['order'] : 'ASC';
$sql .= " ORDER BY " . $order['key'] . " " . $orderDir;
} else {
$sql .= " ORDER BY invoice_number";
}
if($limit !== null) {
$sql .= " LIMIT " . intval($offset) . ", " . intval($limit);
}
$res = $db->query($sql);
if($db->num_rows($res)) {
while($data = $db->fetch_object($res)) {
$items[] = new ManualInvoice($data);
}
}
return $items;
}
public static function getFirst($filter) {
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT ManualInvoice.* FROM ManualInvoice WHERE $where ORDER BY invoice_number LIMIT 1";
$res = $db->query($sql);
if($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new ManualInvoice($data);
if($item->id) {
return $item;
} else {
return null;
}
}
return null;
}
public static function getLast($filter) {
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT ManualInvoice.* FROM ManualInvoice WHERE $where ORDER BY invoice_number DESC LIMIT 1";
mfLoghandler::singleton()->debug($sql);
$res = $db->query($sql);
if($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new ManualInvoice($data);
if($item->id) {
return $item;
} else {
return null;
}
}
return null;
}
public static function count($filter = []) {
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT COUNT(*) as cnt FROM ManualInvoice WHERE $where";
mfLoghandler::singleton()->debug($sql);
$res = $db->query($sql);
if($db->num_rows($res)) {
$data = $db->fetch_object($res);
return $data->cnt;
}
return 0;
}
public static function search($filter, $limit = false, $order = false) {
$items = [];
if(!$order) {
$order = "invoice_number ASC";
}
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT ManualInvoice.* FROM ManualInvoice WHERE $where ORDER BY $order";
if(is_array($limit) && count($limit)) {
if(is_numeric($limit['start']) && is_numeric($limit['count'])) {
$sql .= " LIMIT ".$limit['start'].", ".$limit['count'];
} elseif(is_numeric($limit['count'])) {
$sql .= " LIMIT ".$limit['count'];
}
}
mfLoghandler::singleton()->debug($sql);
$res = $db->query($sql);
if($db->num_rows($res)) {
while($data = $db->fetch_object($res)) {
$items[$data->id] = new ManualInvoice($data);
}
}
return $items;
}
private static function getSqlFilter($filter) {
$where = "1=1 ";
$db = FronkDB::singleton();
if(array_key_exists("id", $filter)) {
$id = $filter['id'];
if(is_numeric($id)) {
$where .= " AND ManualInvoice.id like '%$id%'";
}
}
if(array_key_exists("invoice_number", $filter)) {
$invoice_number = $filter['invoice_number'];
if($invoice_number === true) {
$where .= " AND ManualInvoice.invoice_number IS NOT NULL AND ManualInvoice.invoice_number <> ''";
} elseif($invoice_number) {
$invoice_number = $db->escape($invoice_number);
$where .= " AND ManualInvoice.invoice_number='$invoice_number'";
} elseif($invoice_number === null || $invoice_number === false) {
$where .= " AND ManualInvoice.invoice_number IS NULL";
}
}
if(array_key_exists("invoice_number%", $filter)) {
$invoice_number = $filter['invoice_number%'];
if($invoice_number) {
$where .= " AND ManualInvoice.invoice_number LIKE '%$invoice_number%'";
}
}
if(array_key_exists("invoice_date", $filter)) {
$invoice_date = $filter['invoice_date'];
if($invoice_date) {
$where .= " AND ManualInvoice.invoice_date='$invoice_date'";
} elseif($invoice_date === null || $invoice_date === false) {
$where .= " AND ManualInvoice.invoice_date IS NULL";
}
}
if(array_key_exists("invoice_date>=", $filter)) {
$invoice_date = $db->escape($filter['invoice_date>=']);
if($invoice_date) {
$where .= " AND ManualInvoice.invoice_date >= '$invoice_date'";
}
}
if(array_key_exists("invoice_date<=", $filter)) {
$invoice_date = $db->escape($filter['invoice_date<=']);
if($invoice_date) {
$where .= " AND ManualInvoice.invoice_date <= '$invoice_date'";
}
}
if(array_key_exists("owner_id", $filter)) {
$owner_id = $filter['owner_id'];
if(is_numeric($owner_id)) {
$where .= " AND ManualInvoice.owner_id=$owner_id";
}
}
if(array_key_exists("billingaddress_id", $filter)) {
$billingaddress_id = $filter['billingaddress_id'];
if(is_numeric($billingaddress_id)) {
$where .= " AND ManualInvoice.billingaddress_id=$billingaddress_id";
}
}
if(array_key_exists("customer_number", $filter)) {
$customer_number = $filter['customer_number'];
if(is_numeric($customer_number)) {
$where .= " AND ManualInvoice.customer_number LIKE $customer_number";
}
}
if(array_key_exists("billing_type", $filter)) {
$billing_type = $db->escape($filter['billing_type']);
if($billing_type) {
$where .= " AND ManualInvoice.billing_type LIKE '$billing_type'";
}
}
if(array_key_exists("billing_delivery", $filter)) {
$billing_delivery = $db->escape($filter['billing_delivery']);
if($billing_delivery) {
$where .= " AND ManualInvoice.billing_delivery LIKE '$billing_delivery'";
}
}
if(array_key_exists("add-where", $filter)) {
$where .= " ".$filter['add-where'];
}
return $where;
}
public static function get($id) {
return self::getFirst(["id" => $id]);
}
public static function update($data) {
if (!isset($data['id'])) {
return 0;
}
$invoice = new ManualInvoice($data['id']);
if (!$invoice->id) {
return 0;
}
$me = new User();
$me->loadMe();
// Set audit fields
$invoice->edit_by = $me->id;
$invoice->edit = time();
// Update fields
foreach($data as $field => $value) {
if(property_exists($invoice, $field) && $field != 'id' && $field != 'create' && $field != 'create_by') {
$invoice->$field = $value;
}
}
if ($invoice->save()) {
return 1;
}
return 0;
}
public static function delete($id) {
$invoice = new ManualInvoice($id);
if (!$invoice->id) {
return 0;
}
// Delete positions first
$positions = ManualInvoicepositionModel::search(['manualinvoice_id' => $id]);
foreach ($positions as $pos) {
$pos->delete();
}
// Delete invoice
if ($invoice->delete()) {
return 1;
}
return 0;
}
}
}

View File

@@ -1,35 +0,0 @@
<?php
class ManualInvoiceposition extends mfBaseModel {
public function getOption($key) {
if(!$this->options) {
return null;
}
$options = json_decode($this->options, true);
if(!$options || !is_array($options)) {
return null;
}
if(array_key_exists($key, $options)) {
return $options[$key];
}
return null;
}
public function setOption($key, $value) {
if(!$this->options) {
$this->options = json_encode([]);
}
$options = json_decode($this->options, true);
if(!is_array($options)) {
$options = [];
}
$options[$key] = $value;
$this->options = json_encode($options);
}
}

View File

@@ -1,160 +1,28 @@
<?php
class ManualInvoicepositionModel {
public $manualinvoice_id;
public $billing_id;
public $contract_id;
public $start_date;
public $end_date;
public $matchcode;
public $product_id;
public $product_name;
public $product_info;
public $amount;
public $price;
public $price_total;
public $price_gross;
public $vatrate;
public $fibu_cost_account;
public $fibu_cost_account_legacy;
public $fibu_taxcode;
public $billing_period;
public $options;
public $create_by;
public $edit_by;
public $create;
public $edit;
public static function create(Array $data) {
$model = new ManualInvoiceposition();
foreach($data as $field => $value) {
if(property_exists(get_called_class(), $field)) {
$model ->$field = $value;
}
}
$me = new User();
$me->loadMe();
if($model->create_by === null) {
$model->create_by = $me->id;
}
if($model->edit_by === null) {
$model->edit_by = $me->id;
}
return $model;
}
public static function getAll() {
$items = [];
$db = FronkDB::singleton();
$res = $db->select("ManualInvoiceposition", "*", "1 = 1 ORDER BY manualinvoice_id,contract_id,start_date,matchcode");
if($db->num_rows($res)) {
while($data = $db->fetch_object($res)) {
$items[] = new ManualInvoiceposition($data);
}
}
return $items;
}
public static function getFirst($filter) {
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT * FROM ManualInvoiceposition
WHERE $where
ORDER BY manualinvoice_id,contract_id,start_date,matchcode LIMIT 1";
$res = $db->query($sql);
if($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new ManualInvoiceposition($data);
if($item->id) {
return $item;
} else {
return null;
}
}
return null;
}
public static function count($filter) {
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT COUNT(*) as cnt FROM ManualInvoiceposition WHERE $where";
$res = $db->query($sql);
if($db->num_rows($res)) {
$data = $db->fetch_object($res);
return $data->cnt;
}
return 0;
}
public static function search($filter, $limit = false, $order = false) {
$items = [];
if(!$order) {
$order = "manualinvoice_id,contract_id,id ASC";
}
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT * FROM ManualInvoiceposition WHERE $where ORDER BY $order";
if(is_array($limit) && count($limit)) {
if(is_numeric($limit['start']) && is_numeric($limit['count'])) {
$sql .= " LIMIT ".$limit['start'].", ".$limit['count'];
} elseif(is_numeric($limit['count'])) {
$sql .= " LIMIT ".$limit['count'];
}
}
mfLoghandler::singleton()->debug($sql);
$res = $db->query($sql);
if($db->num_rows($res)) {
while($data = $db->fetch_object($res)) {
$items[$data->id] = new ManualInvoiceposition($data);
}
}
return $items;
}
private static function getSqlFilter($filter) {
$where = "1=1 ";
$db = FronkDB::singleton();
if(array_key_exists("id", $filter)) {
$id = $filter['id'];
if(is_numeric($id)) {
$where .= " AND ManualInvoiceposition.id = $id";
}
}
if(array_key_exists("manualinvoice_id", $filter)) {
$manualinvoice_id = $filter['manualinvoice_id'];
if(is_numeric($manualinvoice_id)) {
$where .= " AND ManualInvoiceposition.manualinvoice_id=$manualinvoice_id";
}
}
if(array_key_exists("add-where", $filter)) {
$where .= " ".$filter['add-where'];
}
return $where;
}
class ManualInvoicepositionModel extends TTCrudBaseModel {
public int $id;
public ?int $manualinvoice_id;
public ?int $billing_id;
public int $contract_id;
public string $start_date;
public ?string $end_date;
public ?string $matchcode;
public int $product_id;
public string $product_name;
public ?string $product_info;
public float $amount;
public float $price;
public float $price_total;
public float $price_gross;
public float $vatrate;
public ?int $fibu_cost_account;
public ?int $fibu_cost_account_legacy;
public ?int $fibu_taxcode;
public int $billing_period;
public ?string $options;
public int $create_by;
public int $edit_by;
public int $create;
public int $edit;
}