Add Gutschrift functionality to ManualInvoice with modal and backend support
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user