fix billing process

This commit is contained in:
Dennis Eichhorn 2023-04-08 04:36:25 +02:00
parent 2adbdc8765
commit 55b0d09005
25 changed files with 670 additions and 386 deletions

View File

@ -8,9 +8,11 @@ assignees: ''
---
# Bug Description
A clear and concise description of what the bug is.
# How to Reproduce
Steps to reproduce the behavior:
1. Go to '...'
@ -19,16 +21,20 @@ Steps to reproduce the behavior:
4. See error
# Expected Behavior
A clear and concise description of what you expected to happen.
# Screenshots
If applicable, add screenshots to help explain your problem.
# System Information
- System: [e.g. PC or iPhone11, ...]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- KarakaVersion [e.g. 22]
- System: [e.g. PC or iPhone11, ...]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- KarakaVersion [e.g. 22]
# Additional Information
Add any other context about the problem here.

View File

@ -0,0 +1,9 @@
[
{
"type": "setting",
"name": "1005100003",
"content": "[\"en\", \"de\"]",
"pattern": "",
"module": "Billing"
}
]

View File

@ -1,16 +0,0 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Admin
* @copyright Dennis Eichhorn
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
return [
];

43
Admin/Install/Admin.php Normal file
View File

@ -0,0 +1,43 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Admin\Install
* @copyright Dennis Eichhorn
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Admin\Install;
use phpOMS\Application\ApplicationAbstract;
/**
* Admin class.
*
* @package Modules\Billing\Admin\Install
* @license OMS License 2.0
* @link https://jingga.app
* @since 1.0.0
*/
class Admin
{
/**
* Install Admin providing
*
* @param ApplicationAbstract $app Application
* @param string $path Module path
*
* @return void
*
* @since 1.0.0
*/
public static function install(ApplicationAbstract $app, string $path) : void
{
\Modules\Admin\Admin\Installer::installExternal($app, ['path' => __DIR__ . '/Admin.install.json']);
}
}

View File

@ -12,181 +12,208 @@
*/
declare(strict_types=1);
/** @var \phpOMS\Views\View $this */
use Modules\Billing\Models\NullBill;
use phpOMS\Localization\ISO3166NameEnum;
/** @var \phpOMS\Views\View $this */
require_once $this->getData('defaultTemplates')
->findFile('.pdf.php')
->getAbsolutePath();
/** @var \Modules\Billing\Models\Bill $bill */
$bill = $this->getData('bill') ?? new NullBill();
// Set up default pdf template
/** @phpstan-import-type DefaultPdf from ../../../../Admin/Install/Media/PdfDefaultTemplate/pdfTemplate.pdf.php */
$pdf = new DefaultPdf('P', 'mm', 'A4', true, 'UTF-8', false);
$creator = $this->getData('bill_creator') ?? 'Jingga';
$author = 'Jingga';
$title = $this->getData('bill_title') ?? 'Invoice';
$subtitle = $this->getData('bill_subtitle') ?? 'Sub title';
$keywords = $this->getData('keywords') ?? [];
$logoName = $this->getData('bill_logo_name') ?? 'Jingga';
$slogan = $this->getData('bill_slogan') ?? 'Business solutions made simple.';
$lang = include __DIR__ . '/lang.php';
$legalCompanyName = $this->getData('legal_company_name') ?? 'Jingga e.K.';
$companyAddress = $this->getData('bill_company_address') ?? 'Gartenstr. 26';
$companyCity = $this->getData('bill_company_city') ?? '61206 Woellstadt';
$companyCEO = $this->getData('bill_company_ceo') ?? 'Dennis Eichhorn';
$companyWebsite = $this->getData('bill_company_website') ?? 'www.jingga.app';
$companyEmail = $this->getData('bill_company_email') ?? 'info@jingga.app';
$companyPhone = $this->getData('bill_company_phone') ?? '+49 0152 ????';
$pdf->setHeaderData(
__DIR__ . '/logo.png', 15,
$this->getData('bill_logo_name') ?? 'Jingga',
$this->getData('bill_slogan') ?? 'Business solutions made simple.'
);
$pdf->setCreator($this->getData('bill_creator') ?? 'Jingga');
$pdf->setAuthor($this->getData('bill_creator') ?? 'Jingga');
$pdf->setTitle($this->getData('bill_title') ?? $bill->type->getL11n());
$pdf->setSubject($this->getData('bill_subtitle') ?? '');
$pdf->setKeywords(\implode(', ', $this->getData('keywords') ?? []));
$pdf->language = $bill->getLanguage();
$taxOffice = $this->getData('bill_company_tax_office') ?? 'HRB';
$taxId = $this->getData('bill_company_tax_id') ?? 'DE ?????????';
$vatId = $this->getData('bill_company_vat_id') ?? 'DE ??????';
$pdf->attributes['legal_name'] = $this->getData('legal_company_name') ?? 'Jingga e.K.';
$pdf->attributes['address'] = $this->getData('bill_company_address') ?? 'Gartenstr. 26';
$pdf->attributes['city'] = $this->getData('bill_company_city') ?? '61206 Woellstadt';
$bankName = $this->getData('bill_company_bank_name') ?? 'Volksbank Mittelhessen';
$bic = $this->getData('bill_company_bic') ?? '';
$iban = $this->getData('bill_company_iban') ?? '';
$pdf->attributes['ceo'] = $this->getData('bill_company_ceo') ?? 'Dennis Eichhorn';
$pdf->attributes['tax_office'] = $this->getData('bill_company_tax_office') ?? 'HRB ???';
$pdf->attributes['tax_number'] = $this->getData('bill_company_tax_id') ?? '123456789';
$billTypeName = $this->getData('bill_type_name') ?? 'INVOICE';
$pdf->attributes['bank_name'] = $this->getData('bill_company_bank_name') ?? 'Volksbank Mittelhessen';
$pdf->attributes['swift'] = $this->getData('bill_company_swift') ?? '.....';
$pdf->attributes['bank_account'] = $this->getData('bill_company_bank_account') ?? '.....';
$billInvoiceNumber = $this->getData('bill_invoice_no') ?? '';
$billInvoiceDate = $this->getData('bill_invoice_date') ?? '';
$billServiceDate = $this->getData('bill_service_date') ?? '';
$billCustomerNo = $this->getData('bill_customer_no') ?? '';
$billPO = $this->getData('bill_po') ?? '';
$billDueDate = $this->getData('bill_due_date') ?? '';
$pdf->attributes['website'] = $this->getData('bill_company_website') ?? 'www.jingga.app';
$pdf->attributes['email'] = $this->getData('bill_company_email') ?? 'info@jingga.app';
$pdf->attributes['phone'] = $this->getData('bill_company_phone') ?? '+49 0152 ????';
$invoiceLines = $this->getData('bill_lines') ?? [];
$paymentTerms = $this->getData('bill_payment_terms') ?? '';
$terms = $this->getData('bill_terms') ?? 'https://jingga.app/terms';
$taxes = $this->getData('bill_taxes') ?? ['19%' => '0.00'];
$currency = $this->getData('bill_currency') ?? 'EUR';
// set document information
$pdf->SetCreator($creator);
$pdf->SetAuthor($author);
$pdf->SetTitle($title);
$pdf->SetSubject($subtitle);
$pdf->SetKeywords(\implode(', ', $keywords));
// set image scale factor
$pdf->SetImageScale(PDF_IMAGE_SCALE_RATIO);
$pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
// add a page
$pdf->AddPage();
$topPos = $pdf->getY();
// Set up default bill template
$billTypeName = \strtoupper($bill->type->getL11n());
// @todo: depending on amount of lines, there is a solution (html, or use backtracking of tcpdf)
// Address
$pdf->SetY(55); // @todo: depending on amount of lines, there is a solution (html, or use backtracking of tcpdf)
$pdf->SetFont('helvetica', '', 8);
$pdf->setY(55);
$pdf->setFont('helvetica', '', 8);
$countries = ISO3166NameEnum::getConstants();
$toCountry = isset($countries[$bill->billCountry]) ? $countries[$bill->billCountry] : '';
$addressString = \trim(
$bill->billTo . "\n"
. (!empty($bill->billAddress) ? ($bill->billAddress . "\n") : '')
. (!empty($bill->billCity) ? ($bill->billCity . "\n") : '')
. (!empty($toCountry) ? ($toCountry . "\n") : ''),
"\n "
);
// Count the char "\n" in $addressString
$addressLineCount = \substr_count($addressString, "\n") + 1;
$lineHeight = $pdf->getY();
$pdf->Write(0, "Dennis Eichhorn\nGartenstr. 26\n61206 Woellstadt", '', 0, 'L', false, 0, false, false, 0);
$lineHeight = ($lineHeight - $pdf->getY()) / 3;
$pdf->Write(
0,
$addressString,
'', 0, 'L', false, 0, false, false, 0
);
$lineHeight = ($lineHeight - $pdf->getY()) / $addressLineCount;
// Document head
$pdf->SetFont('helvetica', 'B', 20);
$titleWidth = $pdf->GetStringWidth($billTypeName, 'helvetica', 'B', 20);
// Bill head
$pdf->setFont('helvetica', 'B', 20);
$titleWidth = $pdf->getStringWidth($billTypeName, 'helvetica', 'B', 20);
$pdf->SetXY(
$pdf->setXY(
$rightPos = ($pdf->getPageWidth() - $titleWidth - ($titleWidth < 55 ? 55 : 35) + 15),
$topPos + 50 + $lineHeight * 3 - 38,
$topPos + 50 + $lineHeight * $addressLineCount - 38,
true
);
$pdf->SetTextColor(255, 255, 255);
$pdf->SetFillColor(255, 162, 7);
$pdf->setTextColor(255, 255, 255);
$pdf->setFillColor(255, 162, 7);
$pdf->Cell($pdf->getPageWidth() - $rightPos - 15, 0, $billTypeName, 0, 0, 'L', true);
$pdf->SetFont('helvetica', '', 8);
$pdf->SetTextColor(255, 162, 7);
$pdf->setFont('helvetica', '', 8);
$pdf->setTextColor(255, 162, 7);
$pdf->SetXY($rightPos, $tempY = $pdf->getY() + 10, true);
$pdf->MultiCell(23, 30, "Invoice No\nInvoice Date\nService Date\nCustomer No\nPO\nDue Date", 0, 'L');
$pdf->setXY($rightPos, $tempY = $pdf->getY() + 10, true);
$pdf->MultiCell(
23, 30,
$lang[$pdf->language]['InvoiceNo'] . "\n"
. $lang[$pdf->language]['InvoiceDate'] . "\n"
. $lang[$pdf->language]['ServiceDate'] . "\n"
. $lang[$pdf->language]['CustomerNo'] . "\n"
. $lang[$pdf->language]['PO'] . "\n"
. $lang[$pdf->language]['DueDate'],
0, 'L'
);
$pdf->SetFont('helvetica', '', 8);
$pdf->SetTextColor(0, 0, 0);
$pdf->setFont('helvetica', '', 8);
$pdf->setTextColor(0, 0, 0);
$pdf->SetXY($rightPos + 23 + 2, $tempY, true);
$pdf->MultiCell(25, 30, "2022-123456\nYYYY-MM-DD\nYYYY-MM-DD\n123-456-789\n2022-123456\nYYYY-MM-DD", 0, 'L');
$pdf->setXY($rightPos + 23 + 2, $tempY, true);
$pdf->MultiCell(
25, 30,
$bill->number . "\n"
. $bill->billDate->format('Y-m-d') . "\n"
. $bill->performanceDate->format('Y-m-d') . "\n"
. $bill->accountNumber . "\n"
. '' . "\n" /* @todo: implement customer / supplier reference as string */
. $bill->billDate->format('Y-m-d'), /* Consider to add dueDate in addition */
0, 'L'
);
$pdf->Ln();
$pdf->SetY($pdf->GetY() - 30);
$pdf->setY($pdf->getY() - 30);
/*
$pdf->writeHTMLCell(
$pdf->getPageWidth() - 15 * 2, 0, null, null,
"<strong>Lorem ipsum dolor sit amet,</strong><br \><br \>Consectetur adipiscing elit. Vivamus ac massa sit amet eros posuere accumsan feugiat vel est. Maecenas ultricies enim eu eros rhoncus, volutpat cursus enim imperdiet. Aliquam et odio ipsum. Quisque dapibus scelerisque tempor. Phasellus purus lorem, venenatis eget pretium ac, convallis et ante. Aenean pulvinar justo consectetur mi tincidunt venenatis. Suspendisse ultricies enim id nulla facilisis lacinia. <br /><br />Nam congue nunc nunc, eu pellentesque eros aliquam ac. Nunc placerat elementum turpis, quis facilisis diam volutpat at. Suspendisse enim leo, convallis nec ornare eu, auctor nec purus. Nunc neque metus, feugiat quis justo nec, mollis dignissim risus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In at ornare sem. Cras placerat, sapien sed ornare lacinia, mauris nulla volutpat nisl, eget dapibus nisl ipsum non est. Suspendisse ut nisl a ipsum rhoncus sodales.",
0, 0, false, true, 'J'
);
$pdf->Ln();
*/
$pdf->SetY($pdf->GetY() + 5);
$pdf->setY($pdf->getY() + 5);
$header = ['Item', 'Quantity', 'Rate', 'Total'];
$data = [
['ASDF', 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer.</span>", 2.0, "199.90\n-10 %", "150.399.80\n-15.039"],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains. Here we are testing how it looks like if a very long text is posted in the description without any additional line breaks. It should auto-break!</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
["123-456-789<br><strong>This is a item name</strong><br><span style=\"color: #444;\">This is the item description in more detail for the customer so he knows what this content actually contains.</span>", 2.0, 199.90, 399.80],
['ASDF', 2.0, 199.90, 399.80],
$header = [
$lang[$pdf->language]['Item'],
$lang[$pdf->language]['Quantity'],
$lang[$pdf->language]['UnitPrice'],
$lang[$pdf->language]['Total']
];
$lines = $bill->getElements();
// Header
$w = array($pdf->getPageWidth() - 20 - 20 - 20 - 2*15, 20, 20, 20);
$num_headers = \count($header);
$headerCount = \count($header);
$w = [$pdf->getPageWidth() - 20 - 20 - 20 - 2*15, 20, 20, 20];
$pdf->setCellPadding(1, 1, 1, 1);
$taxes = [];
$first = true;
// Data
$fill = false;
foreach($data as $row) {
if ($row === null || $first || $pdf->getY() > $pdf->getPageHeight() - 40) {
$pdf->SetFillColor(255, 162, 7);
$pdf->SetTextColor(255);
$pdf->SetDrawColor(255, 162, 7);
foreach($lines as $line) {
// @todo: add support for empty lines (row = line)
if (/*$row === null || */$first || $pdf->getY() > $pdf->getPageHeight() - 40) {
$pdf->setFillColor(255, 162, 7);
$pdf->setTextColor(255);
$pdf->setDrawColor(255, 162, 7);
//$pdf->SetLineWidth(0.3);
$pdf->SetFont('helvetica', 'B', 8);
$pdf->setFont('helvetica', 'B', 8);
if (!$first || $row === null) {
if (!$first/* || $row === null*/) {
$pdf->AddPage();
$pdf->Ln();
}
for($i = 0; $i < $num_headers; ++$i) {
for($i = 0; $i < $headerCount; ++$i) {
$pdf->Cell($w[$i], 7, $header[$i], 1, 0, 'L', true);
}
$pdf->Ln();
$pdf->SetFillColor(245, 245, 245);
$pdf->SetTextColor(0);
$pdf->SetFont('helvetica', '', 8);
$pdf->setFillColor(245, 245, 245);
$pdf->setTextColor(0);
$pdf->setFont('helvetica', '', 8);
$first = false;
}
$tempY = $pdf->getY();
$pdf->writeHTMLCell($w[0], 10, null, null, $row[0], 0, 2, $fill);
$pdf->writeHTMLCell($w[0], 10, null, null, $line->itemNumber . ' ' . $line->itemName, 0, 2, $fill);
$height = $pdf->getY() - $tempY;
/*
$pdf->writeHTMLCell($w[1], $height, 15 + $w[0], $tempY, $row[1], 0, 0, $fill);
$pdf->writeHTMLCell($w[2], $height, 15 + $w[0] + $w[1], $tempY, $row[2], 0, 0, $fill);
$pdf->writeHTMLCell($w[3], $height, 15 + $w[0] + $w[1] + $w[2], $tempY, $row[3], 0, 1, $fill);
*/
$pdf->MultiCell($w[1], $height, $row[1], 0, 'L', $fill, 0, 15 + $w[0], $tempY, true, 0, false, true, 0, 'M', true);
$pdf->MultiCell($w[2], $height, $row[2], 0, 'L', $fill, 0, 15 + $w[0] + $w[1], $tempY, true, 0, false, true, 0, 'M', true);
$pdf->MultiCell($w[3], $height, $row[3], 0, 'L', $fill, 1, 15 + $w[0] + $w[1] + $w[2], $tempY, true, 0, false, true, 0, 'M', true);
$pdf->MultiCell($w[1], $height, $line->getQuantity(), 0, 'L', $fill, 0, 15 + $w[0], $tempY, true, 0, false, true, 0, 'M', true);
$pdf->MultiCell($w[2], $height, $line->singleSalesPriceNet->getCurrency(2, symbol: ''), 0, 'L', $fill, 0, 15 + $w[0] + $w[1], $tempY, true, 0, false, true, 0, 'M', true);
$pdf->MultiCell($w[3], $height, $line->totalSalesPriceNet->getCurrency(2, symbol: ''), 0, 'L', $fill, 1, 15 + $w[0] + $w[1] + $w[2], $tempY, true, 0, false, true, 0, 'M', true);
$fill = !$fill;
// get taxes
if (!isset($taxes[$line->taxR->getInt() / 100])) {
$taxes[$line->taxR->getInt() / 100] = $line->taxP;
} else {
$taxes[$line->taxR->getInt() / 100]->add($line->taxP);
}
}
$pdf->Cell(\array_sum($w), 0, '', 'T');
@ -196,68 +223,71 @@ if ($pdf->getY() > $pdf->getPageHeight() - 40) {
$pdf->AddPage();
}
$pdf->SetFillColor(240, 240, 240);
$pdf->SetTextColor(0);
$pdf->SetDrawColor(240, 240, 240);
$pdf->SetFont('helvetica', 'B', 8);
$pdf->setFillColor(240, 240, 240);
$pdf->setTextColor(0);
$pdf->setDrawColor(240, 240, 240);
$pdf->setFont('helvetica', 'B', 8);
$tempY = $pdf->GetY();
$tempY = $pdf->getY();
$pdf->SetX($w[0] + $w[1] + 15);
$pdf->Cell($w[2], 7, 'Subtotal', 0, 0, 'L', false);
$pdf->Cell($w[3], 7, '40.000', 0, 0, 'L', false);
$pdf->Ln();
$pdf->SetX($w[0] + $w[1] + 15);
$pdf->Cell($w[2], 7, 'Taxes (0%)', 0, 0, 'L', false);
$pdf->Cell($w[3], 7, '40.000', 0, 0, 'L', false);
$pdf->Ln();
$pdf->SetX($w[0] + $w[1] + 15);
$pdf->Cell($w[2], 7, 'Taxes (16%)', 0, 0, 'L', false);
$pdf->Cell($w[3], 7, '40.000', 0, 0, 'L', false);
$pdf->Ln();
$pdf->SetX($w[0] + $w[1] + 15);
$pdf->Cell($w[2], 7, 'Taxes (19%)', 0, 0, 'L', false);
$pdf->Cell($w[3], 7, '40.000', 0, 0, 'L', false);
$pdf->setX($w[0] + $w[1] + 15);
$pdf->Cell($w[2], 7, $lang[$pdf->language]['Subtotal'], 0, 0, 'L', false);
$pdf->Cell($w[3], 7, $bill->netSales->getCurrency(2, symbol: ''), 0, 0, 'L', false);
$pdf->Ln();
$pdf->SetFillColor(255, 162, 7);
$pdf->SetTextColor(255);
$pdf->SetDrawColor(255, 162, 7);
$pdf->SetFont('helvetica', 'B', 8);
foreach ($taxes as $rate => $tax) {
$pdf->setX($w[0] + $w[1] + 15);
$pdf->Cell($w[2], 7, $lang[$pdf->language]['Taxes'] . ' (' . $rate . '%)', 0, 0, 'L', false);
$pdf->Cell($w[3], 7, $tax->getCurrency(2, symbol: ''), 0, 0, 'L', false);
$pdf->Ln();
}
$pdf->SetX($w[0] + $w[1] + 15);
$pdf->Cell($w[2], 7, 'TOTAL', 1, 0, 'L', true);
$pdf->Cell($w[3], 7, '40.000', 1, 0, 'L', true);
// @todo: add currency
$pdf->setFillColor(255, 162, 7);
$pdf->setTextColor(255);
$pdf->setDrawColor(255, 162, 7);
$pdf->setFont('helvetica', 'B', 8);
$pdf->setX($w[0] + $w[1] + 15);
$pdf->Cell($w[2], 7, \strtoupper($lang[$pdf->language]['Total']), 1, 0, 'L', true);
$pdf->Cell($w[3], 7, $bill->grossSales->getCurrency(2, symbol: ''), 1, 0, 'L', true);
$pdf->Ln();
$tempY2 = $pdf->getY();
$pdf->SetTextColor(0);
$pdf->SetFont('helvetica', 'B', 8);
$pdf->SetY($tempY);
$pdf->Write(0, 'Payment Terms: ', '', 0, 'L', false, 0, false, false, 0);
// @todo: fix payment terms
$pdf->setTextColor(0);
$pdf->setFont('helvetica', 'B', 8);
$pdf->setY($tempY);
$pdf->Write(0, $lang[$pdf->language]['PaymentTerms'] . ': CreditCard', '', 0, 'L', false, 0, false, false, 0);
$pdf->SetFont('helvetica', '', 8);
$pdf->Write(0, 'Payment within 30 business days', '', 0, 'L', false, 0, false, false, 0);
$pdf->setFont('helvetica', '', 8);
$pdf->Write(0, $bill->paymentText, '', 0, 'L', false, 0, false, false, 0);
$pdf->Ln();
$pdf->SetFont('helvetica', 'B', 8);
$pdf->Write(0, 'Terms: ', '', 0, 'L', false, 0, false, false, 0);
// @todo: fix terms
$pdf->setFont('helvetica', 'B', 8);
$pdf->Write(0, $lang[$pdf->language]['Terms'] . ': https://jingga.app/terms', '', 0, 'L', false, 0, false, false, 0);
$pdf->SetFont('helvetica', '', 8);
$pdf->Write(0, 'https://jingga.app/terms', '', 0, 'L', false, 0, false, false, 0);
$pdf->setFont('helvetica', '', 8);
$pdf->Write(0, $bill->termsText, '', 0, 'L', false, 0, false, false, 0);
$pdf->Ln();
$pdf->SetY($tempY2);
$pdf->setY($tempY2);
$pdf->Ln();
// $pdf->SetY($pdf->GetY() - 30);
/*
$pdf->writeHTMLCell(
$pdf->getPageWidth() - 15 * 2, 0, null, null,
"Consectetur adipiscing elit. Vivamus ac massa sit amet eros posuere accumsan feugiat vel est. Maecenas ultricies enim eu eros rhoncus, volutpat cursus enim imperdiet. Aliquam et odio ipsum. Quisque dapibus scelerisque tempor. Phasellus purus lorem, venenatis eget pretium ac, convallis et ante. Aenean pulvinar justo consectetur mi tincidunt venenatis. Suspendisse ultricies enim id nulla facilisis lacinia. Nam congue nunc nunc, eu pellentesque eros aliquam ac.<br /><br />Nunc placerat elementum turpis, quis facilisis diam volutpat at. Suspendisse enim leo, convallis nec ornare eu, auctor nec purus. Nunc neque metus, feugiat quis justo nec, mollis dignissim risus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In at ornare sem. Cras placerat, sapien sed ornare lacinia, mauris nulla volutpat nisl, eget dapibus nisl ipsum non est. Suspendisse ut nisl a ipsum rhoncus sodales.",
0, 0, false, true, 'J'
);
$pdf->Ln();
*/
//Close and output PDF document
$pdf->Output('example_048.pdf', 'I');
$pdf->Output(
$this->getData('path') ?? ($bill->billDate->format('Y-m-d') . '_' . $bill->number . '.pdf'),
'I'
);

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
return [
'en' => [
'InvoiceNo' => 'Invoice No.',
'InvoiceDate' => 'Invoice Date',
'ServiceDate' => 'Service Date',
'CustomerNo' => 'Customer No.',
'PO' => 'PO',
'DueDate' => 'Due Date',
'Item' => 'Item',
'Quantity' => 'Quantity',
'UnitPrice' => 'Unit Price',
'Total' => 'Total',
'Net' => 'Net',
'Gross' => 'Gross',
'Subtotal' => 'Subtotal',
'Taxes' => 'Taxes',
'Terms' => 'Terms',
'PaymentTerms' => 'Payment Terms',
],
'de' => [
'InvoiceNo' => 'Belegnummer',
'InvoiceDate' => 'Belegdatum',
'ServiceDate' => 'Leistungsdatum',
'CustomerNo' => 'Kundennummer',
'PO' => 'Kundenreferenz',
'DueDate' => 'Fälligkeitsdatum',
'Item' => 'Artikel',
'Quantity' => 'Menge',
'UnitPrice' => 'Einzelpreis',
'Total' => 'Gesamt',
'Net' => 'Netto',
'Gross' => 'Brutto',
'Subtotal' => 'Netto',
'Taxes' => 'USt',
'Terms' => 'AGB',
'PaymentTerms' => 'Zahlungsbedingungen',
],
];

View File

@ -393,11 +393,6 @@
"type": "VARCHAR(255)",
"null": false
},
"billing_bill_numberformat": {
"name": "billing_bill_numberformat",
"type": "VARCHAR(255)",
"null": false
},
"billing_bill_info": {
"name": "billing_bill_info",
"type": "TEXT",
@ -405,7 +400,12 @@
},
"billing_bill_status": {
"name": "billing_bill_status",
"type": "INT",
"type": "TINYINT",
"null": false
},
"billing_bill_paymentstatus": {
"name": "billing_bill_paymentstatus",
"type": "TINYINT",
"null": false
},
"billing_bill_type": {
@ -423,6 +423,11 @@
"foreignTable": "media",
"foreignKey": "media_id"
},
"billing_bill_account_no": {
"name": "billing_bill_account_no",
"type": "VARCHAR(50)",
"null": false
},
"billing_bill_supplier": {
"name": "billing_bill_supplier",
"type": "INT",

View File

@ -111,9 +111,9 @@ final class ApiAttributeController extends Controller
private function validateBillAttributeCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['type'] = empty($request->getData('type')))
|| ($val['value'] = (empty($request->getData('value')) && empty($request->getData('custom'))))
|| ($val['bill'] = empty($request->getData('bill')))
if (($val['type'] = !$request->hasData('type'))
|| ($val['value'] = (!$request->hasData('value') && !$request->hasData('custom')))
|| ($val['bill'] = !$request->hasData('bill'))
) {
return $val;
}
@ -181,8 +181,8 @@ final class ApiAttributeController extends Controller
private function validateBillAttributeTypeL11nCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['type'] = empty($request->getData('type')))
if (($val['title'] = !$request->hasData('title'))
|| ($val['type'] = !$request->hasData('type'))
) {
return $val;
}
@ -252,8 +252,8 @@ final class ApiAttributeController extends Controller
private function validateBillAttributeTypeCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['name'] = empty($request->getData('name')))
if (($val['title'] = !$request->hasData('title'))
|| ($val['name'] = !$request->hasData('name'))
) {
return $val;
}
@ -337,8 +337,8 @@ final class ApiAttributeController extends Controller
private function validateBillAttributeValueCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['type'] = empty($request->getData('type')))
|| ($val['value'] = empty($request->getData('value')))
if (($val['type'] = !$request->hasData('type'))
|| ($val['value'] = !$request->hasData('value'))
) {
return $val;
}
@ -406,8 +406,8 @@ final class ApiAttributeController extends Controller
private function validateBillAttributeValueL11nCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['value'] = empty($request->getData('value')))
if (($val['title'] = !$request->hasData('title'))
|| ($val['value'] = !$request->hasData('value'))
) {
return $val;
}

View File

@ -24,8 +24,14 @@ use Modules\Billing\Models\BillElementMapper;
use Modules\Billing\Models\BillMapper;
use Modules\Billing\Models\BillStatus;
use Modules\Billing\Models\BillTypeMapper;
use Modules\Billing\Models\SettingsEnum;
use Modules\Billing\Models\Tax\TaxCombinationMapper;
use Modules\ClientManagement\Models\Client;
use Modules\ClientManagement\Models\ClientMapper;
use Modules\Finance\Models\TaxCode;
use Modules\Finance\Models\TaxCodeMapper;
use Modules\ItemManagement\Models\Item;
use Modules\ItemManagement\Models\ItemL11nMapper;
use Modules\ItemManagement\Models\ItemMapper;
use Modules\Media\Models\CollectionMapper;
use Modules\Media\Models\MediaMapper;
@ -99,7 +105,7 @@ final class ApiBillController extends Controller
private function validateBillUpdate(RequestAbstract $request) : array
{
$val = [];
if (($val['bill'] = empty($request->getData('bill')))) {
if (($val['bill'] = !$request->hasData('bill'))) {
return $val;
}
@ -147,7 +153,6 @@ final class ApiBillController extends Controller
return;
}
// @todo: validate vat before creation
$bill = $this->createBillFromRequest($request, $response, $data);
$this->createBillDatabaseEntry($bill, $request);
@ -168,9 +173,9 @@ final class ApiBillController extends Controller
{
$this->createModel($request->header->account, $bill, BillMapper::class, 'bill', $request->getOrigin());
$new = clone $bill;
$new->buildNumber(); // The bill id is part of the number
$this->updateModel($request->header->account, $bill, $new, BillMapper::class, 'bill', $request->getOrigin());
$old = clone $bill;
$bill->buildNumber(); // The bill id is part of the number
$this->updateModel($request->header->account, $old, $bill, BillMapper::class, 'bill', $request->getOrigin());
}
/**
@ -181,7 +186,7 @@ final class ApiBillController extends Controller
*
* @return Bill The new Bill object with default values
*
* @todo Validate VAT before creation
* @todo Validate VAT before creation (maybe need to add a status when last validated, we don't want to validate every time)
* @todo Set the correct date of payment
* @todo Use bill and shipping address instead of main address if available
* @todo Implement allowed invoice languages and a default invoice language if none match
@ -197,6 +202,7 @@ final class ApiBillController extends Controller
$bill->createdBy = new NullAccount($request->header->account);
$bill->billDate = new \DateTime('now'); // @todo: Date of payment
$bill->performanceDate = new \DateTime('now'); // @todo: Date of payment
$bill->accountNumber = $client->number;
$bill->shipping = 0;
$bill->shippingText = '';
@ -204,6 +210,10 @@ final class ApiBillController extends Controller
$bill->payment = 0;
$bill->paymentText = '';
$bill->type = BillTypeMapper::get()
->where('name', 'sales_invoice')
->execute();
// @todo: use bill and shipping address instead of main address if available
$bill->client = $client;
$bill->billTo = $client->account->name1;
@ -214,20 +224,60 @@ final class ApiBillController extends Controller
$bill->setCurrency(ISO4217CharEnum::_EUR);
// @todo implement allowed invoice languages and a default invoice language if none match
// @todo implement client invoice langage (this would allow invoice langauges which are different from the invoice address)
$bill->setLanguage(
!\in_array(
$client->mainAddress->getCountry(),
[ISO3166TwoEnum::_DEU, ISO3166TwoEnum::_AUT]
)
? ISO639x1Enum::_EN
: ISO639x1Enum::_DE
/** @var \Model\Setting $settings */
$settings = $this->app->appSettings->get(null,
SettingsEnum::VALID_BILL_LANGUAGES,
unit: $this->app->unitId,
module: 'Admin'
);
if (empty($settings)) {
/** @var \Model\Setting $settings */
$settings = $this->app->appSettings->get(null,
SettingsEnum::VALID_BILL_LANGUAGES,
unit: null,
module: 'Admin'
);
}
$validLanguages = [];
if (!empty($settings)) {
$validLanguages = \json_decode($settings->content, true);
} else {
$validLanguages = [
ISO639x1Enum::_EN,
];
}
$billLanguage = $validLanguages[0];
$clientBillLanguage = $client->getAttribute('bill_language')?->value->getValue();
if (!empty($clientBillLanguage) && \in_array($clientBillLanguage, $validLanguages)) {
$billLanguage = $clientBillLanguage;
} else {
$clientLanguages = ISO639x1Enum::languageFromCountry($client->mainAddress->getCountry());
$clientLanguage = !empty($clientLanguages) ? $clientLanguages[0] : '';
if (\in_array($clientLanguage, $validLanguages)) {
$billLanguage = $clientLanguage;
}
}
$bill->setLanguage($billLanguage);
return $bill;
}
public function createBaseBillElement(Client $client, Item $item, Bill $bill, RequestAbstract $request) : BillElement
{
$taxCode = $this->app->moduleManager->get('Billing', 'ApiTax')->getTaxCodeFromClientItem($client, $item, $request->getCountry());
$element = BillElement::fromItem($item, $taxCode, $request->getDataInt('quantity') ?? 1);
$element->bill = $request->getDataInt('bill') ?? 0;
return $element;
}
/**
* Method to create a bill from request.
*
@ -271,23 +321,22 @@ final class ApiBillController extends Controller
$bill = new Bill();
$bill->createdBy = new NullAccount($request->header->account);
$bill->type = $billType;
$bill->numberFormat = $billType->numberFormat;
// @todo: use defaultInvoiceAddress or mainAddress. also consider to use billto1, billto2, billto3 (for multiple lines e.g. name2, fao etc.)
$bill->billTo = (string) ($request->getData('billto')
$bill->billTo = (string) ($request->getDataString('billto')
?? ($account->account->name1 . (!empty($account->account->name2)
? ', ' . $account->account->name2
: ''
)));
$bill->billAddress = (string) ($request->getData('billaddress') ?? $account->mainAddress->address);
$bill->billZip = (string) ($request->getData('billtopostal') ?? $account->mainAddress->postal);
$bill->billCity = (string) ($request->getData('billtocity') ?? $account->mainAddress->city);
$bill->billAddress = (string) ($request->getDataString('billaddress') ?? $account->mainAddress->address);
$bill->billZip = (string) ($request->getDataString('billtopostal') ?? $account->mainAddress->postal);
$bill->billCity = (string) ($request->getDataString('billtocity') ?? $account->mainAddress->city);
$bill->billCountry = (string) (
$request->getData('billtocountry') ?? (
$request->getDataString('billtocountry') ?? (
($country = $account->mainAddress->getCountry()) === ISO3166TwoEnum::_XXX ? '' : $country)
);
$bill->client = !$request->hasData('client') ? null : $account;
$bill->supplier = !$request->hasData('supplier') ? null : $account;
$bill->performanceDate = new \DateTime($request->getData('performancedate') ?? 'now');
$bill->performanceDate = new \DateTime($request->getDataString('performancedate') ?? 'now');
$bill->setStatus($request->getDataInt('status') ?? BillStatus::ACTIVE);
return $bill;
@ -305,11 +354,11 @@ final class ApiBillController extends Controller
private function validateBillCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['client/supplier'] = (empty($request->getData('client'))
&& (empty($request->getData('supplier'))
if (($val['client/supplier'] = (!$request->hasData('client')
&& (!$request->hasData('supplier')
&& ($request->getDataInt('supplier') ?? -1) !== 0)
))
|| ($val['type'] = (empty($request->getData('type'))))
|| ($val['type'] = (!$request->hasData('type')))
) {
return $val;
}
@ -456,8 +505,8 @@ final class ApiBillController extends Controller
private function validateMediaAddToBill(RequestAbstract $request) : array
{
$val = [];
if (($val['media'] = (empty($request->getData('media')) && empty($request->getFiles())))
|| ($val['bill'] = empty($request->getData('bill')))
if (($val['media'] = (!$request->hasData('media') && empty($request->getFiles())))
|| ($val['bill'] = !$request->hasData('bill'))
) {
return $val;
}
@ -487,12 +536,20 @@ final class ApiBillController extends Controller
return;
}
$element = $this->createBillElementFromRequest($request, $response, $data);
/** @var \Modules\Billing\Models\Bill $old */
$old = BillMapper::get()
->with('client')
->with('client/attributes')
->with('client/attributes/type')
->with('client/attributes/value')
->where('id', $request->getDataInt('bill') ?? 0)
->execute();
$element = $this->createBillElementFromRequest($request, $response, $old, $data);
$this->createModel($request->header->account, $element, BillElementMapper::class, 'bill_element', $request->getOrigin());
/** @var \Modules\Billing\Models\Bill $old */
$old = BillMapper::get()->where('id', $element->bill)->execute();
$new = $this->updateBillWithBillElement(clone $old, $element, 1);
$new = clone $old;
$new->addElement($element);
$this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill_element', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Bill element', 'Bill element successfully created.', $element);
@ -508,74 +565,32 @@ final class ApiBillController extends Controller
* @return BillElement
*
* @since 1.0.0
* @todo in the database the customer localized version should be stored because this is the version which went out
*/
public function createBillElementFromRequest(RequestAbstract $request, ResponseAbstract $response, $data = null) : BillElement
private function createBillElementFromRequest(RequestAbstract $request, ResponseAbstract $response, Bill $bill, $data = null) : BillElement
{
$element = new BillElement();
$element->bill = (int) $request->getData('bill');
$element->item = $request->getDataInt('item') ?? 0;
if ($element->item === null) {
return $element;
}
/** @var \Modules\ItemManagement\Models\Item $item */
$item = ItemMapper::get()
->with('attributes')
->with('attributes/type')
->with('attributes/value')
->with('l11n')
->with('l11n/type')
->where('id', $element->item)
->where('id', $request->getDataInt('item') ?? 0)
->where('l11n/type/title', ['name1', 'name2', 'name3'], 'IN')
->where('l11n/language', $response->getLanguage())
->where('l11n/language', $bill->getLanguage())
->execute();
$element->itemNumber = $item->number;
$element->itemName = $item->getL11n('name1')->description;
$element->quantity = $request->getDataInt('quantity') ?? 0;
$element->singleSalesPriceNet = new Money($request->getDataInt('singlesalespricenet') ?? $item->salesPrice->getInt());
$element->totalSalesPriceNet = clone $element->singleSalesPriceNet;
$element->totalSalesPriceNet->mult($element->quantity);
$element = $this->createBaseBillElement($bill->client, $item, $bill, $request);
$element->bill = $bill->getId();
// discounts
if ($request->getData('discount_percentage') !== null) {
$discount = (int) $request->getData('discount_percentage');
$element->singleSalesPriceNet
->sub((int) ($element->singleSalesPriceNet->getInt() / 100 * $discount));
$element->totalSalesPriceNet
->sub((int) ($element->totalSalesPriceNet->getInt() / 100 * $discount));
// @todo: implement a addDiscount function
}
$element->singlePurchasePriceNet = new Money($item->purchasePrice->getInt());
$element->totalPurchasePriceNet = clone $element->singlePurchasePriceNet;
$element->totalPurchasePriceNet->mult($element->quantity);
return $element;
}
/**
* Method to update a bill because of a changed bill element (add, remove, change) from request.
*
* @param Bill $bill Bill
* @param BillElement $element Bill element
* @param int $type Change type (0 = update, -1 = remove, +1 = add)
*
* @return Bill
*
* @since 1.0.0
*/
public function updateBillWithBillElement(Bill $bill, BillElement $element, int $type = 1) : Bill
{
if ($type === 1) {
$bill->netSales->add($element->totalSalesPriceNet);
$bill->netCosts->add($element->totalPurchasePriceNet);
}
return $bill;
}
/**
* Method to validate bill element creation from request
*
@ -588,7 +603,7 @@ final class ApiBillController extends Controller
private function validateBillElementCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['bill'] = empty($request->getData('bill')))) {
if (($val['bill'] = !$request->hasData('bill'))) {
return $val;
}
@ -597,6 +612,14 @@ final class ApiBillController extends Controller
public function apiPreviewRender(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
/** @var \Modules\Billing\Models\Bill $bill */
$bill = BillMapper::get()
->with('type')
->with('type/l11n')
->with('elements')
->where('id', $request->getDataInt('bill') ?? 0)
->execute();
Autoloader::addPath(__DIR__ . '/../../../Resources/');
$templateId = $request->getData('bill_template', 'int');
@ -634,7 +657,7 @@ final class ApiBillController extends Controller
module: 'Admin'
);
if ($settings === false) {
if (empty($settings)) {
/** @var \Model\Setting[] $settings */
$settings = $this->app->appSettings->get(null,
[
@ -661,49 +684,45 @@ final class ApiBillController extends Controller
$view->setData('defaultTemplates', $defaultTemplates);
$view->setData('defaultAssets', $defaultAssets);
$path = $this->createBillDir($bill);
$pdfDir = __DIR__ . '/../../../Modules/Media/Files' . $path;
$view->setData('bill', $bill);
$view->setData('path', $pdfDir . '/' . $request->getData('bill') . '.pdf');
$view->setData('path', $pdfDir . '/' .$bill->billDate->format('Y-m-d') . '_' . $bill->number . '.pdf');
$view->setData('bill_creator', $request->getData('bill_creator'));
$view->setData('bill_title', $request->getData('bill_title'));
$view->setData('bill_subtitle', $request->getData('bill_subtitle'));
$view->setData('keywords', $request->getData('keywords'));
$view->setData('bill_logo_name', $request->getData('bill_logo_name'));
$view->setData('bill_slogan', $request->getData('bill_slogan'));
$view->setData('bill_creator', $request->getDataString('bill_creator'));
$view->setData('bill_title', $request->getDataString('bill_title'));
$view->setData('bill_subtitle', $request->getDataString('bill_subtitle'));
$view->setData('keywords', $request->getDataString('keywords'));
$view->setData('bill_logo_name', $request->getDataString('bill_logo_name'));
$view->setData('bill_slogan', $request->getDataString('bill_slogan'));
$view->setData('legal_company_name', $request->getData('legal_company_name'));
$view->setData('bill_company_address', $request->getData('bill_company_address'));
$view->setData('bill_company_city', $request->getData('bill_company_city'));
$view->setData('bill_company_ceo', $request->getData('bill_company_ceo'));
$view->setData('bill_company_website', $request->getData('bill_company_website'));
$view->setData('bill_company_email', $request->getData('bill_company_email'));
$view->setData('bill_company_phone', $request->getData('bill_company_phone'));
$view->setData('legal_company_name', $request->getDataString('legal_company_name'));
$view->setData('bill_company_address', $request->getDataString('bill_company_address'));
$view->setData('bill_company_city', $request->getDataString('bill_company_city'));
$view->setData('bill_company_ceo', $request->getDataString('bill_company_ceo'));
$view->setData('bill_company_website', $request->getDataString('bill_company_website'));
$view->setData('bill_company_email', $request->getDataString('bill_company_email'));
$view->setData('bill_company_phone', $request->getDataString('bill_company_phone'));
$view->setData('bill_company_tax_office', $request->getData('bill_company_tax_office'));
$view->setData('bill_company_tax_id', $request->getData('bill_company_tax_id'));
$view->setData('bill_company_vat_id', $request->getData('bill_company_vat_id'));
$view->setData('bill_company_tax_office', $request->getDataString('bill_company_tax_office'));
$view->setData('bill_company_tax_id', $request->getDataString('bill_company_tax_id'));
$view->setData('bill_company_vat_id', $request->getDataString('bill_company_vat_id'));
$view->setData('bill_company_bank_name', $request->getData('bill_company_bank_name'));
$view->setData('bill_company_bic', $request->getData('bill_company_bic'));
$view->setData('bill_company_iban', $request->getData('bill_company_iban'));
$view->setData('bill_company_bank_name', $request->getDataString('bill_company_bank_name'));
$view->setData('bill_company_bic', $request->getDataString('bill_company_bic'));
$view->setData('bill_company_iban', $request->getDataString('bill_company_iban'));
$view->setData('bill_type_name', $request->getData('bill_type_name'));
$view->setData('bill_type_name', $request->getDataString('bill_type_name'));
$view->setData('bill_invoice_no', $request->getData('bill_invoice_no'));
$view->setData('bill_invoice_date', $request->getData('bill_invoice_date'));
$view->setData('bill_service_date', $request->getData('bill_service_date'));
$view->setData('bill_customer_no', $request->getData('bill_customer_no'));
$view->setData('bill_po', $request->getData('bill_po'));
$view->setData('bill_due_date', $request->getData('bill_due_date'));
$view->setData('bill_start_text', $request->getDataString('bill_start_text'));
$view->setData('bill_lines', $request->getDataString('bill_lines'));
$view->setData('bill_end_text', $request->getDataString('bill_end_text'));
$view->setData('bill_start_text', $request->getData('bill_start_text'));
$view->setData('bill_lines', $request->getData('bill_lines'));
$view->setData('bill_end_text', $request->getData('bill_end_text'));
$view->setData('bill_payment_terms', $request->getData('bill_payment_terms'));
$view->setData('bill_terms', $request->getData('bill_terms'));
$view->setData('bill_taxes', $request->getData('bill_taxes'));
$view->setData('bill_currency', $request->getData('bill_currency'));
$view->setData('bill_payment_terms', $request->getDataString('bill_payment_terms'));
$view->setData('bill_terms', $request->getDataString('bill_terms'));
$view->setData('bill_taxes', $request->getDataString('bill_taxes'));
$view->setData('bill_currency', $request->getDataString('bill_currency'));
$pdf = $view->render();
@ -729,6 +748,8 @@ final class ApiBillController extends Controller
/** @var \Modules\Billing\Models\Bill $bill */
$bill = BillMapper::get()
->with('type')
->with('type/l11n')
->with('elements')
->where('id', $request->getDataInt('bill') ?? 0)
->execute();
@ -766,7 +787,7 @@ final class ApiBillController extends Controller
module: 'Admin'
);
if ($settings === false) {
if (empty($settings)) {
/** @var \Model\Setting[] $settings */
$settings = $this->app->appSettings->get(null,
[
@ -792,10 +813,9 @@ final class ApiBillController extends Controller
$view->setData('defaultTemplates', $defaultTemplates);
$view->setData('defaultAssets', $defaultAssets);
$view->setData('bill', $bill);
/**
@todo: pass data to bill
*/
// @todo: add bill data such as company name bank information, ..., etc.
$pdf = $view->render();
@ -812,20 +832,22 @@ final class ApiBillController extends Controller
// @codeCoverageIgnoreEnd
}
\file_put_contents($pdfDir . '/' . $request->getData('bill') . '.pdf', $pdf);
if (!\is_file($pdfDir . '/' . $request->getData('bill') . '.pdf')) {
$billFileName = $bill->billDate->format('Y-m-d') . '_' . $bill->number . '.pdf';
\file_put_contents($pdfDir . '/' . $billFileName, $pdf);
if (!\is_file($pdfDir . '/' . $billFileName)) {
$response->header->status = RequestStatusCode::R_400;
return;
}
$media = $this->app->moduleManager->get('Media')->createDbEntry(
$media = $this->app->moduleManager->get('Media', 'Api')->createDbEntry(
status: [
'status' => UploadStatus::OK,
'name' => $request->getData('bill') . '.pdf',
'name' => $billFileName,
'path' => $pdfDir,
'filename' => $request->getData('bill') . '.pdf',
'size' => \filesize($pdfDir . '/' . $request->getData('bill') . '.pdf'),
'filename' => $billFileName,
'size' => \filesize($pdfDir . '/' . $billFileName),
'extension' => 'pdf',
],
account: $request->header->account,
@ -846,7 +868,14 @@ final class ApiBillController extends Controller
$request->getOrigin()
);
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'PDF', 'Bill Pdf successfully created.', $media);
$this->fillJsonResponse(
$request,
$response,
NotificationLevel::OK,
'PDF',
'Bill Pdf successfully created.',
$media
);
}
/**
@ -897,7 +926,7 @@ final class ApiBillController extends Controller
private function validateNoteCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['id'] = empty($request->getData('id')))) {
if (($val['id'] = !$request->hasData('id'))) {
return $val;
}

View File

@ -107,8 +107,8 @@ final class ApiBillTypeController extends Controller
private function validateBillTypeCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['name'] = empty($request->getData('name')))
if (($val['title'] = !$request->hasData('title'))
|| ($val['name'] = !$request->hasData('name'))
) {
return $val;
}
@ -176,8 +176,8 @@ final class ApiBillTypeController extends Controller
private function validateBillTypeL11nCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['type'] = empty($request->getData('type')))
if (($val['title'] = !$request->hasData('title'))
|| ($val['type'] = !$request->hasData('type'))
) {
return $val;
}

View File

@ -205,13 +205,14 @@ final class ApiPriceController extends Controller
}
// Get tax definition
$tax = ($request->getData('price_type', 'int') ?? PriceType::SALES)
/** @var \Modules\Billing\Models\Tax\TaxCombination $tax */
$tax = ($request->getDataInt('price_type') ?? PriceType::SALES) === PriceType::SALES
? TaxCombinationMapper::get()
->where('itemCode', $request->getData('price_item'))
->where('itemCode', $request->getDataInt('price_item'))
->where('clientCode', $account->getAttribute('client_code')->getId())
->execute()
: TaxCombinationMapper::get()
->where('itemCode', $request->getData('price_item'))
->where('itemCode', $request->getDataInt('price_item'))
->where('supplierCode', $account->getAttribute('supplier_code')->getId())
->execute();
@ -287,9 +288,9 @@ final class ApiPriceController extends Controller
$price->discountPercentage = (int) $request->getData('discountPercentage');
$price->bonus = (int) $request->getData('bonus');
$price->multiply = $request->getDataBool('multiply') ?? false;
$price->currency = $request->getData('currency') ?? ISO4217CharEnum::_EUR;
$price->start = $request->hasData('start') ? new \DateTime($request->getData('start')) : null;
$price->end = $request->hasData('end') ? new \DateTime($request->getData('end')) : null;
$price->currency = $request->getDataString('currency') ?? ISO4217CharEnum::_EUR;
$price->start = $request->hasData('start') ? new \DateTime($request->getDataString('start')) : null;
$price->end = $request->hasData('end') ? new \DateTime($request->getDataString('end')) : null;
return $price;
}

View File

@ -23,7 +23,12 @@ use Modules\ClientManagement\Models\Client;
use Modules\ClientManagement\Models\ClientAttributeTypeMapper;
use Modules\ClientManagement\Models\ClientAttributeValue;
use Modules\ClientManagement\Models\NullClientAttributeValue;
use Modules\Finance\Models\NullTaxCode;
use Modules\Finance\Models\TaxCode;
use Modules\Finance\Models\TaxCodeMapper;
use Modules\ItemManagement\Models\Item;
use Modules\ItemManagement\Models\NullItemAttributeValue;
use Modules\Organization\Models\UnitMapper;
use Modules\SupplierManagement\Models\NullSupplierAttributeValue;
use phpOMS\Localization\ISO3166CharEnum;
use phpOMS\Message\Http\RequestStatusCode;
@ -42,6 +47,55 @@ use phpOMS\Model\Message\FormValidation;
*/
final class ApiTaxController extends Controller
{
public function getTaxCodeFromClientItem(Client $client, Item $item, string $defaultCountry = '') : TaxCode
{
// @todo: define default sales tax code if none available?!
// @todo: consider to actually use a ownsOne reference instead of only a string, this way the next line with the TaxCodeMapper can be removed
/** @var \Modules\Billing\Models\Tax\TaxCombination $taxCombination */
$taxCombination = TaxCombinationMapper::get()
->where('itemCode', $item->getAttribute('sales_tax_code')?->value->getId())
->where('clientCode', $client->getAttribute('sales_tax_code')?->value->getId())
->execute();
/** @var \Modules\Finance\Models\TaxCode $taxCode */
$taxCode = TaxCodeMapper::get()
->where('abbr', $taxCombination->taxCode)
->execute();
// If now tax code could be found, the local tax code should be used.
if ($taxCode instanceof NullTaxCode) {
/** @var \Modules\Organization\Models\Unit $unit */
$unit = UnitMapper::get()
->with('mainAddress')
->where('id', $this->app->unitId)
->execute();
// Create dummy client
$client = new Client();
$client->mainAddress = $unit->mainAddress;
if (!empty($defaultCountry)) {
$client->mainAddress->setCountry($defaultCountry);
}
$taxCodeAttribute = $this->getClientTaxCode($client, $unit->mainAddress);
/** @var \Modules\Billing\Models\Tax\TaxCombination $taxCombination */
$t = $item->getAttribute('sales_tax_code');
$taxCombination = TaxCombinationMapper::get()
->where('itemCode', $item->getAttribute('sales_tax_code')?->value->getId())
->where('clientCode', $taxCodeAttribute->getId())
->execute();
/** @var \Modules\Finance\Models\TaxCode $taxCode */
$taxCode = TaxCodeMapper::get()
->where('abbr', $taxCombination->taxCode)
->execute();
}
return $taxCode;
}
public function apiTaxCombinationCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateTaxCombinationCreate($request))) {
@ -94,10 +148,10 @@ final class ApiTaxController extends Controller
private function validateTaxCombinationCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['tax_type'] = empty($request->getData('tax_type')))
|| ($val['tax_code'] = empty($request->getData('tax_code')))
|| ($val['item_code'] = empty($request->getData('item_code')))
|| ($val['account_code'] = empty($request->getData('account_code')))
if (($val['tax_type'] = !$request->hasData('tax_type'))
|| ($val['tax_code'] = !$request->hasData('tax_code'))
|| ($val['item_code'] = !$request->hasData('item_code'))
|| ($val['account_code'] = !$request->hasData('account_code'))
) {
return $val;
}

View File

@ -105,7 +105,7 @@ final class CliController extends Controller
/* Type */
$type = $this->findSupplierInvoiceType($content, $identifiers['type'], $language);
/** @var \Modules\Billing\Models\BillType $billTye */
/** @var \Modules\Billing\Models\BillType $billType */
$billType = BillTypeMapper::get()
->where('name', $type)
->execute();
@ -274,7 +274,7 @@ final class CliController extends Controller
foreach ($lines as $row => $line) {
if (\preg_match($match, $line, $found) === 1) {
if ($row < $bestPos) {
$bestPos = $row;
$bestPos = $row;
$bestMatch = \trim($found['bill_date']);
}
@ -292,7 +292,7 @@ final class CliController extends Controller
* @param string[] $lines Bill lines
* @param array $matches Gross match patterns
*
* @return string
* @return int
*
* @since 1.0.0
*/
@ -416,7 +416,7 @@ final class CliController extends Controller
foreach ($formats as $format) {
if (($obj = \DateTime::createFromFormat($format, $date)) !== false) {
return $obj;
return $obj === false ? null : $obj;
}
}

View File

@ -19,7 +19,6 @@ use Modules\Admin\Models\NullAccount;
use Modules\Billing\Models\Attribute\BillAttribute;
use Modules\ClientManagement\Models\Client;
use Modules\Editor\Models\EditorDoc;
use Modules\ItemManagement\Models\Item;
use Modules\Media\Models\Collection;
use Modules\Media\Models\Media;
use Modules\Media\Models\NullMedia;
@ -56,14 +55,6 @@ class Bill implements \JsonSerializable
*/
public string $number = '';
/**
* Number format ID.
*
* @var string
* @since 1.0.0
*/
public string $numberFormat = '';
/**
* Bill type.
*
@ -82,6 +73,8 @@ class Bill implements \JsonSerializable
*/
private int $status = BillStatus::DRAFT;
private int $paymentStatus = BillPaymentStatus::UNPAID;
/**
* Bill created at.
*
@ -136,6 +129,8 @@ class Bill implements \JsonSerializable
public string $language = ISO639x1Enum::_EN;
public string $accountNumber = '';
/**
* Receiver.
*
@ -468,10 +463,10 @@ class Bill implements \JsonSerializable
$this->netDiscount = new Money(0);
$this->grossDiscount = new Money(0);
$this->createdAt = new \DateTimeImmutable();
$this->createdBy = new NullAccount();
$this->referral = new NullAccount();
$this->type = new NullBillType();
$this->createdAt = new \DateTimeImmutable();
$this->createdBy = new NullAccount();
$this->referral = new NullAccount();
$this->type = new NullBillType();
}
/**
@ -510,7 +505,7 @@ class Bill implements \JsonSerializable
$this->id,
$this->type->getId(),
],
$this->numberFormat
$this->type->numberFormat
);
}
@ -569,7 +564,7 @@ class Bill implements \JsonSerializable
{
foreach ($this->attributes as $attribute) {
if ($attribute->type->name === $attrName) {
return $attribute->value;
return $attribute;
}
}
@ -602,6 +597,32 @@ class Bill implements \JsonSerializable
$this->status = $status;
}
/**
* Get paymentStatus
*
* @return int
*
* @since 1.0.0
*/
public function getPaymentStatus() : int
{
return $this->paymentStatus;
}
/**
* Set paymentStatus
*
* @param int $paymentStatus Status
*
* @return void
*
* @since 1.0.0
*/
public function setPaymentStatus(int $paymentStatus) : void
{
$this->paymentStatus = $paymentStatus;
}
/**
* Set currency.
*
@ -709,7 +730,7 @@ class Bill implements \JsonSerializable
/**
* Get Bill elements.
*
* @return array
* @return BillElement[]
*
* @since 1.0.0
*/
@ -740,7 +761,7 @@ class Bill implements \JsonSerializable
$this->netDiscount->add($element->totalDiscountP->getInt());
// @todo: Discount might be in quantities
$this->grossDiscount->add((int) ($element->taxR * $element->totalDiscountP->getInt() / 1000));
$this->grossDiscount->add((int) ($element->taxR->getInt() * $element->totalDiscountP->getInt() / 10000));
}
/**
@ -848,7 +869,6 @@ class Bill implements \JsonSerializable
return [
'id' => $this->id,
'number' => $this->number,
'numberFormat' => $this->numberFormat,
'type' => $this->type,
'shipTo' => $this->shipTo,
'shipFAO' => $this->shipFAO,

View File

@ -39,6 +39,7 @@ class BillElement implements \JsonSerializable
public int $order = 0;
/** @todo: consider to reference the model instead of the int, this would make it much easier in other places like the shop */
public ?int $item = null;
public string $itemNumber = '';
@ -47,7 +48,7 @@ class BillElement implements \JsonSerializable
public string $itemDescription = '';
public int $quantity = 0;
protected int $quantity = 0;
public Money $singleSalesPriceNet;
@ -57,9 +58,9 @@ class BillElement implements \JsonSerializable
public Money $totalSalesPriceGross;
public ?FloatInt $singleDiscountP = null;
public Money $singleDiscountP;
public ?FloatInt $totalDiscountP = null;
public Money $totalDiscountP;
public ?FloatInt $singleDiscountR = null;
@ -91,19 +92,19 @@ class BillElement implements \JsonSerializable
/**
* Tax amount
*
* @var null|FloatInt
*
* @var Money
* @since 1.0.0
*/
public ?FloatInt $taxP = null;
public Money $taxP;
/**
* Tax percentage
*
*
* @var null|FloatInt
* @since 1.0.0
*/
public ?FloatInt $taxR = null;
public FloatInt $taxR;
public string $taxCode = '';
@ -155,6 +156,12 @@ class BillElement implements \JsonSerializable
$this->totalProfitNet = new Money();
$this->totalProfitGross = new Money();
$this->singleDiscountP = new Money();
$this->totalDiscountP = new Money();
$this->taxP = new Money();
$this->taxR = new FloatInt();
}
/**
@ -169,6 +176,21 @@ class BillElement implements \JsonSerializable
return $this->id;
}
public function setQuantity(int $quantity) : void
{
if ($this->quantity === $quantity) {
return;
}
$this->quantity = $quantity;
// @todo: recalculate all the prices!!!
}
public function getQuantity() : int
{
return $this->quantity;
}
/**
* Set item.
*
@ -183,14 +205,14 @@ class BillElement implements \JsonSerializable
$this->item = $item;
}
public static function fromItem(Item $item, TaxCode $code) : self
public static function fromItem(Item $item, TaxCode $code, int $quantity = 1) : self
{
$element = new self();
$element->item = $item->getId();
$element->itemNumber = $item->number;
$element->itemName = $item->getL11n('name1')->description;
$element->itemDescription = $item->getL11n('description_short')->description;
$element->quantity = 0;
$element->quantity = $quantity;
// @todo: Use pricing instead of the default sales price
// @todo: discounts might be in quantities
@ -201,19 +223,19 @@ class BillElement implements \JsonSerializable
$element->singlePurchasePriceNet->setInt($item->purchasePrice->getInt());
$element->totalPurchasePriceNet->setInt($element->quantity * $item->purchasePrice->getInt());
$element->singleProfitNet->setInt($element->singleSalesPriceNet->getInt() - $element->singlePurchasePriceNet->getInt());
$element->singleProfitNet->setInt($element->singleSalesPriceNet->getInt() - $element->singlePurchasePriceNet->getInt());
$element->totalProfitNet->setInt($element->quantity * ($element->totalSalesPriceNet->getInt() - $element->totalPurchasePriceNet->getInt()));
$element->taxP = new FloatInt((int) (($code->percentageInvoice * $element->totalSalesPriceNet->getInt()) / 1000));
$element->taxP = new FloatInt((int) (($code->percentageInvoice * $element->totalSalesPriceNet->getInt()) / 10000));
$element->taxR = new FloatInt($code->percentageInvoice);
$element->taxCode = $code->abbr;
$element->singleListPriceGross->setInt((int) ($element->singleListPriceNet->getInt() + $element->singleListPriceNet->getInt() * $element->taxR->getInt() / 1000));
$element->totalListPriceGross->setInt((int) ($element->totalListPriceNet->getInt() + $element->totalListPriceNet->getInt() * $element->taxR->getInt() / 1000));
$element->singleSalesPriceGross->setInt((int) ($element->singleSalesPriceNet->getInt() + $element->singleSalesPriceNet->getInt() * $element->taxR->getInt() / 1000));
$element->totalSalesPriceGross->setInt((int) ($element->totalSalesPriceNet->getInt() + $element->totalSalesPriceNet->getInt() * $element->taxR->getInt() / 1000));
$element->singlePurchasePriceGross->setInt((int) ($element->singlePurchasePriceNet->getInt() + $element->singlePurchasePriceNet->getInt() * $element->taxR->getInt() / 1000));
$element->totalPurchasePriceGross->setInt((int) ($element->totalPurchasePriceNet->getInt() + $element->totalPurchasePriceNet->getInt() * $element->taxR->getInt() / 1000));
$element->singleListPriceGross->setInt((int) ($element->singleListPriceNet->getInt() + $element->singleListPriceNet->getInt() * $element->taxR->getInt() / 10000));
$element->totalListPriceGross->setInt((int) ($element->totalListPriceNet->getInt() + $element->totalListPriceNet->getInt() * $element->taxR->getInt() / 10000));
$element->singleSalesPriceGross->setInt((int) ($element->singleSalesPriceNet->getInt() + $element->singleSalesPriceNet->getInt() * $element->taxR->getInt() / 10000));
$element->totalSalesPriceGross->setInt((int) ($element->totalSalesPriceNet->getInt() + $element->totalSalesPriceNet->getInt() * $element->taxR->getInt() / 10000));
$element->singlePurchasePriceGross->setInt((int) ($element->singlePurchasePriceNet->getInt() + $element->singlePurchasePriceNet->getInt() * $element->taxR->getInt() / 10000));
$element->totalPurchasePriceGross->setInt((int) ($element->totalPurchasePriceNet->getInt() + $element->totalPurchasePriceNet->getInt() * $element->taxR->getInt() / 10000));
$element->singleProfitGross->setInt($element->singleSalesPriceGross->getInt() - $element->singlePurchasePriceGross->getInt());
$element->totalProfitGross->setInt($element->quantity * ($element->totalSalesPriceGross->getInt() - $element->totalPurchasePriceGross->getInt()));

View File

@ -42,13 +42,13 @@ class BillMapper extends DataMapperFactory
public const COLUMNS = [
'billing_bill_id' => ['name' => 'billing_bill_id', 'type' => 'int', 'internal' => 'id'],
'billing_bill_number' => ['name' => 'billing_bill_number', 'type' => 'string', 'internal' => 'number'],
'billing_bill_numberformat' => ['name' => 'billing_bill_numberformat', 'type' => 'string', 'internal' => 'numberFormat'],
'billing_bill_type' => ['name' => 'billing_bill_type', 'type' => 'int', 'internal' => 'type'],
'billing_bill_template' => ['name' => 'billing_bill_template', 'type' => 'int', 'internal' => 'template'],
'billing_bill_header' => ['name' => 'billing_bill_header', 'type' => 'string', 'internal' => 'header'],
'billing_bill_footer' => ['name' => 'billing_bill_footer', 'type' => 'string', 'internal' => 'footer'],
'billing_bill_info' => ['name' => 'billing_bill_info', 'type' => 'string', 'internal' => 'info'],
'billing_bill_status' => ['name' => 'billing_bill_status', 'type' => 'int', 'internal' => 'status'],
'billing_bill_paymentstatus' => ['name' => 'billing_bill_paymentstatus', 'type' => 'int', 'internal' => 'paymentStatus'],
'billing_bill_shipTo' => ['name' => 'billing_bill_shipTo', 'type' => 'string', 'internal' => 'shipTo'],
'billing_bill_shipFAO' => ['name' => 'billing_bill_shipFAO', 'type' => 'string', 'internal' => 'shipFAO'],
'billing_bill_shipAddr' => ['name' => 'billing_bill_shipAddr', 'type' => 'string', 'internal' => 'shipAddress'],
@ -80,6 +80,7 @@ class BillMapper extends DataMapperFactory
'billing_bill_paymentterms_text' => ['name' => 'billing_bill_paymentterms_text', 'type' => 'string', 'internal' => 'termsText'],
'billing_bill_ship_type' => ['name' => 'billing_bill_ship_type', 'type' => 'int', 'internal' => 'shipping'],
'billing_bill_ship_text' => ['name' => 'billing_bill_ship_text', 'type' => 'string', 'internal' => 'shippingText'],
'billing_bill_account_no' => ['name' => 'billing_bill_account_no', 'type' => 'string', 'internal' => 'accountNumber'],
'billing_bill_client' => ['name' => 'billing_bill_client', 'type' => 'int', 'internal' => 'client'],
'billing_bill_supplier' => ['name' => 'billing_bill_supplier', 'type' => 'int', 'internal' => 'supplier'],
'billing_bill_created_by' => ['name' => 'billing_bill_created_by', 'type' => 'int', 'internal' => 'createdBy', 'readonly' => true],

View File

@ -0,0 +1,34 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models
* @copyright Dennis Eichhorn
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models;
use phpOMS\Stdlib\Base\Enum;
/**
* Status for external references
*
* @package Modules\Billing\Models
* @license OMS License 2.0
* @link https://jingga.app
* @since 1.0.0
*/
abstract class BillPaymentStatus extends Enum
{
public const UNKNOWN = 0;
public const PAID = 1;
public const UNPAID = 2;
}

View File

@ -15,7 +15,6 @@ declare(strict_types=1);
namespace Modules\Billing\Models;
use Modules\Media\Models\Collection;
use Modules\Media\Models\NullCollection;
use phpOMS\Localization\BaseStringL11n;
use phpOMS\Localization\ISO639x1Enum;

View File

@ -59,7 +59,7 @@ final class BillTypeMapper extends DataMapperFactory
'external' => null,
],
'templates' => [
'mapper' => MediaMapper::class,
'mapper' => CollectionMapper::class,
'table' => 'billing_bill_type_media_rel',
'external' => 'billing_bill_type_media_rel_dst',
'self' => 'billing_bill_type_media_rel_src',

View File

@ -97,17 +97,17 @@ class Price implements \JsonSerializable
public function __construct()
{
$this->item = new NullItem();
$this->itemgroup = new NullItemAttributeValue();
$this->item = new NullItem();
$this->itemgroup = new NullItemAttributeValue();
$this->itemsegment = new NullItemAttributeValue();
$this->itemsection = new NullItemAttributeValue();
$this->itemtype = new NullItemAttributeValue();
$this->itemtype = new NullItemAttributeValue();
$this->client = new NullClient();
$this->clientgroup = new NullClientAttributeValue();
$this->client = new NullClient();
$this->clientgroup = new NullClientAttributeValue();
$this->clientsegment = new NullClientAttributeValue();
$this->clientsection = new NullClientAttributeValue();
$this->clienttype = new NullClientAttributeValue();
$this->clienttype = new NullClientAttributeValue();
$this->supplier = new NullSupplier();
}

View File

@ -26,7 +26,9 @@ use phpOMS\Stdlib\Base\Enum;
*/
abstract class SettingsEnum extends Enum
{
public const PREVIEW_MEDIA_TYPE = '1005100001_1'; // internally generated preview
public const PREVIEW_MEDIA_TYPE = '1005100001'; // internally generated preview
public const ORIGINAL_MEDIA_TYPE = '1005100001_2'; // original document (mostly supplier invoice/delivery note)
public const ORIGINAL_MEDIA_TYPE = '1005100002'; // original document (mostly supplier invoice/delivery note)
public const VALID_BILL_LANGUAGES = '1005100003'; // List of valid languages for bills
}

View File

@ -47,6 +47,8 @@ class TaxCombination implements \JsonSerializable
public string $taxCode = '';
// @todo: consider to add the tax code object directly, it is annoying to make a manuall mapper call which is often required afterwards.
public int $taxType = BillTaxType::SALES;
public string $account = '';

View File

@ -787,7 +787,7 @@ echo $this->getData('nav')->render();
?>
<tr>
<td><?= $values['month'] . '/' . \substr((string) $values['year'], -2); ?>
<td><?= (new Money(((int) $values['net_sales']) / 1000))->getCurrency(); ?>
<td><?= (new Money(((int) $values['net_sales']) / 10000))->getCurrency(); ?>
<td><?= ((int) $values['customers']); ?>
<td><?= ((int) $values['customers']); ?>
<?php endforeach; ?>
@ -910,7 +910,7 @@ echo $this->getData('nav')->render();
?>
<tr>
<td><?= (string) $values['year']; ?>
<td><?= (new Money(((int) $values['net_sales']) / 1000))->getCurrency(); ?>
<td><?= (new Money(((int) $values['net_sales']) / 10000))->getCurrency(); ?>
<td><?= ((int) $values['customers']); ?>
<td><?= ((int) $values['customers']); ?>
<?php endforeach; ?>
@ -1617,7 +1617,7 @@ echo $this->getData('nav')->render();
?>
<tr>
<td><?= $values['month'] . '/' . \substr((string) $values['year'], -2); ?>
<td><?= (new Money(((int) $values['net_sales']) / 1000))->getCurrency(); ?>
<td><?= (new Money(((int) $values['net_sales']) / 10000))->getCurrency(); ?>
<td><?= ((int) $values['customers']); ?>
<td><?= ((int) $values['customers']); ?>
<?php endforeach; ?>
@ -1739,7 +1739,7 @@ echo $this->getData('nav')->render();
?>
<tr>
<td><?= (string) $values['year']; ?>
<td><?= (new Money(((int) $values['net_sales']) / 1000))->getCurrency(); ?>
<td><?= (new Money(((int) $values['net_sales']) / 10000))->getCurrency(); ?>
<td><?= ((int) $values['customers']); ?>
<td><?= ((int) $values['customers']); ?>
<?php endforeach; ?>

View File

@ -26,6 +26,7 @@
"SupplierManagement": "1.0.0"
},
"providing": {
"Admin": "*",
"Navigation": "*",
"Media": "*",
"Workflow": "*"

View File

@ -70,7 +70,7 @@ final class ApiControllerTest extends \PHPUnit\Framework\TestCase
$permission = new AccountPermission();
$permission->setUnit(1);
$permission->setApp('backend');
$permission->setApp(2);
$permission->setPermission(
PermissionType::READ
| PermissionType::CREATE