too many changes

This commit is contained in:
Dennis Eichhorn 2023-03-11 23:38:17 +01:00
parent ff90dba39e
commit 44b4db558b
50 changed files with 5365 additions and 1110 deletions

View File

@ -2,83 +2,11 @@
declare(strict_types=1);
class MYPDF extends TCPDF
{
public string $fontName = '';
public int $fontSize = 8;
public int $sideMargin = 15;
require_once $this->getData('defaultTemplates')
->findFile('.pdf.php')
->getAbsolutePath();
//Page header
public function Header() {
if ($this->header_xobjid === false) {
$this->header_xobjid = $this->startTemplate($this->w, 0);
// Set Logo
$image_file = __DIR__ . '/logo.png';
$this->Image($image_file, 15, 15, 15, 15, 'PNG', '', 'T', false, 300, '', false, false, 0, false, false, false);
// Set Title
$this->SetFont('helvetica', 'B', 20);
$this->setX(15 + 15 + 3);
$this->Cell(0, 14, $this->header_title, 0, false, 'L', 0, '', 0, false, 'T', 'M');
$this->SetFont('helvetica', '', 10);
$this->setX(15 + 15 + 3);
$this->Cell(0, 26, $this->header_string, 0, false, 'L', 0, '', 0, false, 'T', 'M');
$this->endTemplate();
}
$x = 0;
$dx = 0;
if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
// adjust margins for booklet mode
$dx = ($this->original_lMargin - $this->original_rMargin);
}
if ($this->rtl) {
$x = $this->w + $dx;
} else {
$x = 0 + $dx;
}
$this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
if ($this->header_xobj_autoreset) {
// reset header xobject template at each page
$this->header_xobjid = false;
}
}
// Page footer
public function Footer() {
$this->SetY(-25);
$this->SetFont('helvetica', 'I', 7);
$this->Cell($this->getPageWidth() - 22, 0, 'Page '.$this->getAliasNumPage().'/'.$this->getAliasNbPages(), 0, false, 'R', 0, '', 0, false, 'T', 'M');
$this->Ln();
$this->Ln();
$this->SetFillColor(245, 245, 245);
$this->SetX(0);
$this->Cell($this->getPageWidth(), 25, '', 0, 0, 'L', true, '', 0, false, 'T', 'T');
$this->SetFont('helvetica', '', 7);
$this->SetXY(15 + 10, -15, true);
$this->MultiCell(30, 0, "Jingga e.K.\nGartenstr. 26\n61206 Woellstadt", 0, 'L', false, 1, null, null, true, 0, false, true, 0, 'B');
$this->SetXY(25 + 15 + 20, -15, true);
$this->MultiCell(40, 0, "Geschäftsführer: Dennis Eichhorn\nFinanzamt: HRB ???\nUSt Id: DE ??????????", 0, 'L', false, 1, null, null, true, 0, false, true, 0, 'B');
$this->SetXY(25 + 45 + 15 + 30, -15, true);
$this->MultiCell(35, 0, "Volksbank Mittelhessen\nBIC: ??????????\nIBAN: ???????????", 0, 'L', false, 1, null, null, true, 0, false, true, 0, 'B');
$this->SetXY(25 + 45 + 35 + 15 + 40, -15, true);
$this->MultiCell(35, 0, "www.jingga.app\ninfo@jingga.app\n+49 0152 ???????", 0, 'L', false, 1, null, null, true, 0, false, true, 0, 'B');
}
}
$pdf = new MYPDF('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf = new DefaultPdf('P', 'mm', 'A4', true, 'UTF-8', false);
$creator = $this->getData('bill_creator') ?? 'Jingga';
$author = 'Jingga';
@ -120,7 +48,6 @@ $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);
@ -128,28 +55,9 @@ $pdf->SetTitle($title);
$pdf->SetSubject($subtitle);
$pdf->SetKeywords(\implode(', ', $keywords));
// set default header data
$pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, $logoName, $slogan);
// set header and footer fonts
$pdf->SetHeaderFont([PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN]);
$pdf->SetFooterFont([PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA]);
// set default monospaced font
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
// set margins
$pdf->SetMargins(15, 30, 15);
// set auto page breaks
$pdf->SetAutoPageBreak(true, 25);
// set image scale factor
$pdf->SetImageScale(PDF_IMAGE_SCALE_RATIO);
// add a page
$pdf->AddPage();
$topPos = $pdf->getY();
// Address

View File

@ -0,0 +1,15 @@
[
{
"name": "external_payment_id",
"l11n": {
"en": "External payment Id",
"de": "Externe Zahlungsid"
},
"value_type": 2,
"is_custom_allowed": true,
"validation_pattern": "",
"is_required": false,
"default_value": "",
"values": []
}
]

View File

@ -1,179 +1,271 @@
{
"billing_prices": {
"name": "billing_prices",
"billing_price": {
"name": "billing_price",
"fields": {
"billing_prices_id": {
"name": "billing_prices_id",
"billing_price_id": {
"name": "billing_price_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"billing_prices_name": {
"name": "billing_prices_name",
"billing_price_name": {
"name": "billing_price_name",
"type": "VARCHAR(255)",
"null": false
},
"billing_prices_promocode": {
"name": "billing_prices_promocode",
"type": "INT",
"billing_price_promocode": {
"name": "billing_price_promocode",
"type": "VARCHAR(50)",
"null": false
},
"billing_prices_item": {
"name": "billing_prices_item",
"billing_price_item": {
"name": "billing_price_item",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "itemmgmt_item",
"foreignKey": "itemmgmt_item_id"
},
"billing_prices_itemgroup": {
"name": "billing_prices_itemgroup",
"billing_price_itemgroup": {
"name": "billing_price_itemgroup",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "itemmgmt_attr_value",
"foreignKey": "itemmgmt_attr_value_id"
},
"billing_prices_itemsegment": {
"name": "billing_prices_itemsegment",
"billing_price_itemsegment": {
"name": "billing_price_itemsegment",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "itemmgmt_attr_value",
"foreignKey": "itemmgmt_attr_value_id"
},
"billing_prices_itemsection": {
"name": "billing_prices_itemsection",
"billing_price_itemsection": {
"name": "billing_price_itemsection",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "itemmgmt_attr_value",
"foreignKey": "itemmgmt_attr_value_id"
},
"billing_prices_itemtype": {
"name": "billing_prices_itemtype",
"billing_price_itemtype": {
"name": "billing_price_itemtype",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "itemmgmt_attr_value",
"foreignKey": "itemmgmt_attr_value_id"
},
"billing_prices_client": {
"name": "billing_prices_client",
"billing_price_client": {
"name": "billing_price_client",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "clientmgmt_client",
"foreignKey": "clientmgmt_client_id"
},
"billing_prices_clientgroup": {
"name": "billing_prices_clientgroup",
"billing_price_clientgroup": {
"name": "billing_price_clientgroup",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "clientmgmt_attr_value",
"foreignKey": "clientmgmt_attr_value_id"
},
"billing_prices_clientsegment": {
"name": "billing_prices_clientsegment",
"billing_price_clientsegment": {
"name": "billing_price_clientsegment",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "clientmgmt_attr_value",
"foreignKey": "clientmgmt_attr_value_id"
},
"billing_prices_clientsection": {
"name": "billing_prices_clientsection",
"billing_price_clientsection": {
"name": "billing_price_clientsection",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "clientmgmt_attr_value",
"foreignKey": "clientmgmt_attr_value_id"
},
"billing_prices_clienttype": {
"name": "billing_prices_clienttype",
"billing_price_clienttype": {
"name": "billing_price_clienttype",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "clientmgmt_attr_value",
"foreignKey": "clientmgmt_attr_value_id"
},
"billing_prices_clientcountry": {
"name": "billing_prices_clientcountry",
"billing_price_clientcountry": {
"name": "billing_price_clientcountry",
"type": "VARCHAR(2)",
"null": true,
"default": null,
"foreignTable": "country",
"foreignKey": "country_code2"
},
"billing_prices_supplier": {
"name": "billing_prices_supplier",
"billing_price_supplier": {
"name": "billing_price_supplier",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "suppliermgmt_supplier",
"foreignKey": "suppliermgmt_supplier_id"
},
"billing_prices_unit": {
"name": "billing_prices_unit",
"billing_price_unit": {
"name": "billing_price_unit",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "unit",
"foreignKey": "unit_id"
"default": null
},
"billing_prices_type": {
"name": "billing_prices_type",
"billing_price_type": {
"name": "billing_price_type",
"type": "TINYINT(1)",
"null": false
},
"billing_prices_quantity": {
"name": "billing_prices_quantity",
"billing_price_quantity": {
"name": "billing_price_quantity",
"type": "BIGINT",
"null": false
},
"billing_prices_price": {
"name": "billing_prices_price",
"billing_price_price": {
"name": "billing_price_price",
"type": "BIGINT",
"null": false
},
"billing_prices_discount": {
"name": "billing_prices_discount",
"billing_price_price_new": {
"name": "billing_price_price_new",
"type": "BIGINT",
"null": false
},
"billing_prices_discountp": {
"name": "billing_prices_discountp",
"billing_price_discount": {
"name": "billing_price_discount",
"type": "BIGINT",
"null": false
},
"billing_prices_bonus": {
"name": "billing_prices_bonus",
"billing_price_discountp": {
"name": "billing_price_discountp",
"type": "BIGINT",
"null": false
},
"billing_prices_multiply": {
"name": "billing_prices_multiply",
"billing_price_bonus": {
"name": "billing_price_bonus",
"type": "BIGINT",
"null": false
},
"billing_price_multiply": {
"name": "billing_price_multiply",
"type": "TINYINT(1)",
"null": false
},
"billing_prices_currency": {
"name": "billing_prices_currency",
"billing_price_currency": {
"name": "billing_price_currency",
"type": "VARCHAR(3)",
"null": true,
"default": null,
"foreignTable": "currency",
"foreignKey": "currency_code"
},
"billing_prices_start": {
"name": "billing_prices_start",
"billing_price_start": {
"name": "billing_price_start",
"type": "DATETIME",
"null": true,
"default": null
},
"billing_prices_end": {
"name": "billing_prices_end",
"billing_price_end": {
"name": "billing_price_end",
"type": "DATETIME",
"null": true,
"default": null
}
}
},
"billing_tax": {
"name": "billing_tax",
"fields": {
"billing_tax_id": {
"name": "billing_tax_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"billing_tax_client_code": {
"name": "billing_tax_client_code",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "clientmgmt_attr_value",
"foreignKey": "clientmgmt_attr_value_id"
},
"billing_tax_supplier_code": {
"name": "billing_tax_supplier_code",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "suppliermgmt_attr_value",
"foreignKey": "suppliermgmt_attr_value_id"
},
"billing_tax_item_code": {
"name": "billing_tax_item_code",
"type": "INT",
"null": false,
"foreignTable": "itemmgmt_attr_value",
"foreignKey": "itemmgmt_attr_value_id"
},
"billing_tax_code": {
"description": "tax abbr. code",
"name": "billing_tax_code",
"type": "VARCHAR(10)",
"null": false
},
"billing_tax_type": {
"description": "sales/purchase",
"name": "billing_tax_type",
"type": "TINYINT",
"null": false
},
"billing_tax_account": {
"name": "billing_tax_account",
"type": "VARCHAR(10)",
"null": false,
"default": null
},
"billing_tax_refund_account": {
"name": "billing_tax_refund_account",
"type": "VARCHAR(10)",
"null": false,
"default": null
},
"billing_tax_discount_account": {
"name": "billing_tax_discount_account",
"type": "VARCHAR(10)",
"null": false,
"default": null
},
"billing_tax_min_price": {
"name": "billing_tax_min_price",
"type": "BIGINT",
"null": true,
"default": null
},
"billing_tax_max_price": {
"name": "billing_tax_max_price",
"type": "BIGINT",
"null": true,
"default": null
},
"billing_tax_start": {
"name": "billing_tax_start",
"type": "DATETIME",
"null": true,
"default": null
},
"billing_tax_end": {
"name": "billing_tax_end",
"type": "DATETIME",
"null": true,
"default": null
@ -444,10 +536,17 @@
"type": "DATETIME",
"null": false
},
"billing_bill_date": {
"name": "billing_bill_date",
"type": "DATETIME",
"default": null,
"null": true
},
"billing_bill_performance_date": {
"name": "billing_bill_performance_date",
"type": "DATETIME",
"null": false
"default": null,
"null": true
},
"billing_bill_created_by": {
"name": "billing_bill_created_by",
@ -799,6 +898,235 @@
}
}
},
"billing_attr_type": {
"name": "billing_attr_type",
"fields": {
"billing_attr_type_id": {
"name": "billing_attr_type_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"billing_attr_type_name": {
"name": "billing_attr_type_name",
"type": "VARCHAR(255)",
"null": false,
"unique": true
},
"billing_attr_type_datatype": {
"name": "billing_attr_type_datatype",
"type": "INT(11)",
"null": false
},
"billing_attr_type_fields": {
"name": "billing_attr_type_fields",
"type": "INT(11)",
"null": false
},
"billing_attr_type_custom": {
"name": "billing_attr_type_custom",
"type": "TINYINT(1)",
"null": false
},
"billing_attr_type_required": {
"description": "Every item must have this attribute type if set to true.",
"name": "billing_attr_type_required",
"type": "TINYINT(1)",
"null": false
},
"billing_attr_type_pattern": {
"description": "This is a regex validation pattern.",
"name": "billing_attr_type_pattern",
"type": "VARCHAR(255)",
"null": false
}
}
},
"billing_attr_type_l11n": {
"name": "billing_attr_type_l11n",
"fields": {
"billing_attr_type_l11n_id": {
"name": "billing_attr_type_l11n_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"billing_attr_type_l11n_title": {
"name": "billing_attr_type_l11n_title",
"type": "VARCHAR(255)",
"null": false
},
"billing_attr_type_l11n_type": {
"name": "billing_attr_type_l11n_type",
"type": "INT(11)",
"null": false,
"foreignTable": "billing_attr_type",
"foreignKey": "billing_attr_type_id"
},
"billing_attr_type_l11n_lang": {
"name": "billing_attr_type_l11n_lang",
"type": "VARCHAR(2)",
"null": false,
"foreignTable": "language",
"foreignKey": "language_639_1"
}
}
},
"billing_attr_value": {
"name": "billing_attr_value",
"fields": {
"billing_attr_value_id": {
"name": "billing_attr_value_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"billing_attr_value_default": {
"name": "billing_attr_value_default",
"type": "TINYINT(1)",
"null": false
},
"billing_attr_value_valueStr": {
"name": "billing_attr_value_valueStr",
"type": "VARCHAR(255)",
"null": true,
"default": null
},
"billing_attr_value_valueInt": {
"name": "billing_attr_value_valueInt",
"type": "INT(11)",
"null": true,
"default": null
},
"billing_attr_value_valueDec": {
"name": "billing_attr_value_valueDec",
"type": "DECIMAL(19,5)",
"null": true,
"default": null
},
"billing_attr_value_valueDat": {
"name": "billing_attr_value_valueDat",
"type": "DATETIME",
"null": true,
"default": null
},
"billing_attr_value_unit": {
"name": "billing_attr_value_unit",
"type": "VARCHAR(255)",
"null": false
},
"billing_attr_value_deptype": {
"name": "billing_attr_value_deptype",
"type": "INT(11)",
"null": true,
"default": null,
"foreignTable": "billing_attr_type",
"foreignKey": "billing_attr_type_id"
},
"billing_attr_value_depvalue": {
"name": "billing_attr_value_depvalue",
"type": "INT(11)",
"null": true,
"default": null,
"foreignTable": "billing_attr_value",
"foreignKey": "billing_attr_value_id"
}
}
},
"billing_attr_value_l11n": {
"name": "billing_attr_value_l11n",
"fields": {
"billing_attr_value_l11n_id": {
"name": "billing_attr_value_l11n_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"billing_attr_value_l11n_title": {
"name": "billing_attr_value_l11n_title",
"type": "VARCHAR(255)",
"null": false
},
"billing_attr_value_l11n_value": {
"name": "billing_attr_value_l11n_value",
"type": "INT(11)",
"null": false,
"foreignTable": "billing_attr_value",
"foreignKey": "billing_attr_value_id"
},
"billing_attr_value_l11n_lang": {
"name": "billing_attr_value_l11n_lang",
"type": "VARCHAR(2)",
"null": false,
"foreignTable": "language",
"foreignKey": "language_639_1"
}
}
},
"billing_bill_attr_default": {
"name": "billing_bill_attr_default",
"fields": {
"billing_bill_attr_default_id": {
"name": "billing_bill_attr_default_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"billing_bill_attr_default_type": {
"name": "billing_bill_attr_default_type",
"type": "INT(11)",
"null": false,
"foreignTable": "billing_attr_type",
"foreignKey": "billing_attr_type_id"
},
"billing_bill_attr_default_value": {
"name": "billing_bill_attr_default_value",
"type": "INT(11)",
"null": false,
"foreignTable": "billing_attr_value",
"foreignKey": "billing_attr_value_id"
}
}
},
"billing_bill_attr": {
"name": "billing_bill_attr",
"fields": {
"billing_bill_attr_id": {
"name": "billing_bill_attr_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"billing_bill_attr_bill": {
"name": "billing_bill_attr_bill",
"type": "INT(11)",
"null": false,
"foreignTable": "billing_bill",
"foreignKey": "billing_bill_id"
},
"billing_bill_attr_type": {
"name": "billing_bill_attr_type",
"type": "INT(11)",
"null": false,
"foreignTable": "billing_attr_type",
"foreignKey": "billing_attr_type_id"
},
"billing_bill_attr_value": {
"name": "billing_bill_attr_value",
"type": "INT(11)",
"null": true,
"default": null,
"foreignTable": "billing_attr_value",
"foreignKey": "billing_attr_value_id"
}
}
},
"billing_bill_media": {
"name": "billing_bill_media",
"fields": {

362
Admin/Install/taxes.json Normal file
View File

@ -0,0 +1,362 @@
[
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "EU",
"tax_code": "EU_S0"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "AT",
"tax_code": "AT_S20"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "BE",
"tax_code": "BE_S21"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "BG",
"tax_code": "BG_S20"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "HR",
"tax_code": "HR_S25"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "CY",
"tax_code": "CY_S19"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "CZ",
"tax_code": "CZ_S21"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "DK",
"tax_code": "DK_S25"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "EE",
"tax_code": "EE_S20"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "FI",
"tax_code": "FI_S24"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "FR",
"tax_code": "FR_S20"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "DE",
"tax_code": "DE_S19"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "GR",
"tax_code": "GR_S24"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "HU",
"tax_code": "HU_S27"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "IE",
"tax_code": "IE_S23"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "IT",
"tax_code": "IT_S22"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "LV",
"tax_code": "LV_S21"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "LT",
"tax_code": "LT_S21"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "LU",
"tax_code": "LU_S17"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "MT",
"tax_code": "MT_S18"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "NL",
"tax_code": "NL_S21"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "PL",
"tax_code": "PL_S23"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "PT",
"tax_code": "PT_S23"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "RO",
"tax_code": "RO_S19"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "SK",
"tax_code": "SK_S20"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "SI",
"tax_code": "SI_S22"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "ES",
"tax_code": "ES_S21"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "SE",
"tax_code": "SE_S25"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "GB",
"tax_code": "GB_S20"
},
{
"type": 1,
"item_code": "SOFTWARE",
"account_code": "INT",
"tax_code": "S0"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "EU",
"tax_code": "EU_S0"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "AT",
"tax_code": "AT_S20"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "BE",
"tax_code": "BE_S21"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "BG",
"tax_code": "BG_S20"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "HR",
"tax_code": "HR_S25"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "CY",
"tax_code": "CY_S19"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "CZ",
"tax_code": "CZ_S21"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "DK",
"tax_code": "DK_S25"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "EE",
"tax_code": "EE_S20"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "FI",
"tax_code": "FI_S24"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "FR",
"tax_code": "FR_S20"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "DE",
"tax_code": "DE_S19"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "GR",
"tax_code": "GR_S24"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "HU",
"tax_code": "HU_S27"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "IE",
"tax_code": "IE_S23"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "IT",
"tax_code": "IT_S22"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "LV",
"tax_code": "LV_S21"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "LT",
"tax_code": "LT_S21"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "LU",
"tax_code": "LU_S17"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "MT",
"tax_code": "MT_S18"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "NL",
"tax_code": "NL_S21"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "PL",
"tax_code": "PL_S23"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "PT",
"tax_code": "PT_S23"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "RO",
"tax_code": "RO_S19"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "SK",
"tax_code": "SK_S20"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "SI",
"tax_code": "SI_S22"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "ES",
"tax_code": "ES_S21"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "SE",
"tax_code": "SE_S25"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "GB",
"tax_code": "GB_S20"
},
{
"type": 1,
"item_code": "SERVICE",
"account_code": "INT",
"tax_code": "S0"
}
]

View File

@ -200,7 +200,7 @@
{
"name": "purchase_subscritpion",
"numberFormat": "{y}-{id}",
"transferType": 1,
"transferType": 2,
"transferStock": false,
"isTemplate": false,
"l11n": {
@ -211,7 +211,7 @@
{
"name": "purchase_template",
"numberFormat": "{y}-{id}",
"transferType": 1,
"transferType": 2,
"transferStock": false,
"isTemplate": true,
"l11n": {

View File

@ -15,6 +15,9 @@ declare(strict_types=1);
namespace Modules\Billing\Admin;
use Modules\Billing\Models\BillTransferType;
use Modules\ClientManagement\Models\ClientAttributeTypeMapper;
use Modules\ItemManagement\Models\ItemAttributeTypeMapper;
use Modules\SupplierManagement\Models\SupplierAttributeTypeMapper;
use phpOMS\Application\ApplicationAbstract;
use phpOMS\Config\SettingsInterface;
use phpOMS\Message\Http\HttpRequest;
@ -62,6 +65,208 @@ final class Installer extends InstallerAbstract
$types = \json_decode($fileContent, true);
self::createBillTypes($app, $types, $defaultTemplate);
/* Tax types */
$fileContent = \file_get_contents(__DIR__ . '/Install/taxes.json');
if ($fileContent === false) {
return;
}
$taxes = \json_decode($fileContent, true);
self::createTaxCombination($app, $taxes);
/* Attributes */
$fileContent = \file_get_contents(__DIR__ . '/Install/attributes.json');
if ($fileContent === false) {
return;
}
$attributes = \json_decode($fileContent, true);
$attrTypes = self::createBillAttributeTypes($app, $attributes);
$attrValues = self::createBillAttributeValues($app, $attrTypes, $attributes);
}
/**
* Install default attribute types
*
* @param ApplicationAbstract $app Application
* @param array<array{name:string, l11n?:array<string, string>, is_required?:bool, is_custom_allowed?:bool, validation_pattern?:string, value_type?:string, values?:array<string, mixed>}> $attributes Attribute definition
*
* @return array<string, array>
*
* @since 1.0.0
*/
private static function createBillAttributeTypes(ApplicationAbstract $app, array $attributes) : array
{
/** @var array<string, array> $billAttrType */
$billAttrType = [];
/** @var \Modules\Billing\Controller\ApiController $module */
$module = $app->moduleManager->getModuleInstance('Billing');
/** @var array $attribute */
foreach ($attributes as $attribute) {
$response = new HttpResponse();
$request = new HttpRequest(new HttpUri(''));
$request->header->account = 1;
$request->setData('name', $attribute['name'] ?? '');
$request->setData('title', \reset($attribute['l11n']));
$request->setData('language', \array_keys($attribute['l11n'])[0] ?? 'en');
$request->setData('is_required', $attribute['is_required'] ?? false);
$request->setData('is_custom_allowed', $attribute['is_custom_allowed'] ?? false);
$request->setData('validation_pattern', $attribute['validation_pattern'] ?? '');
$request->setData('datatype', (int) $attribute['value_type']);
$module->apiBillAttributeTypeCreate($request, $response);
$responseData = $response->get('');
if (!\is_array($responseData)) {
continue;
}
$billAttrType[$attribute['name']] = !\is_array($responseData['response'])
? $responseData['response']->toArray()
: $responseData['response'];
$isFirst = true;
foreach ($attribute['l11n'] as $language => $l11n) {
if ($isFirst) {
$isFirst = false;
continue;
}
$response = new HttpResponse();
$request = new HttpRequest(new HttpUri(''));
$request->header->account = 1;
$request->setData('title', $l11n);
$request->setData('language', $language);
$request->setData('type', $billAttrType[$attribute['name']]['id']);
$module->apiBillAttributeTypeL11nCreate($request, $response);
}
}
return $billAttrType;
}
/**
* Create default attribute values for types
*
* @param ApplicationAbstract $app Application
* @param array $billAttrType Attribute types
* @param array<array{name:string, l11n?:array<string, string>, is_required?:bool, is_custom_allowed?:bool, validation_pattern?:string, value_type?:string, values?:array<string, mixed>}> $attributes Attribute definition
*
* @return array<string, array>
*
* @since 1.0.0
*/
private static function createBillAttributeValues(ApplicationAbstract $app, array $billAttrType, array $attributes) : array
{
/** @var array<string, array> $billAttrValue */
$billAttrValue = [];
/** @var \Modules\Billing\Controller\ApiController $module */
$module = $app->moduleManager->getModuleInstance('Billing');
foreach ($attributes as $attribute) {
$billAttrValue[$attribute['name']] = [];
/** @var array $value */
foreach ($attribute['values'] as $value) {
$response = new HttpResponse();
$request = new HttpRequest(new HttpUri(''));
$request->header->account = 1;
$request->setData('value', $value['value'] ?? '');
$request->setData('unit', $value['unit'] ?? '');
$request->setData('default', true); // always true since all defined values are possible default values
$request->setData('type', $billAttrType[$attribute['name']]['id']);
if (isset($value['l11n']) && !empty($value['l11n'])) {
$request->setData('title', \reset($value['l11n']));
$request->setData('language', \array_keys($value['l11n'])[0] ?? 'en');
}
$module->apiBillAttributeValueCreate($request, $response);
$responseData = $response->get('');
if (!\is_array($responseData)) {
continue;
}
$attrValue = !\is_array($responseData['response'])
? $responseData['response']->toArray()
: $responseData['response'];
$billAttrValue[$attribute['name']][] = $attrValue;
$isFirst = true;
foreach (($value['l11n'] ?? []) as $language => $l11n) {
if ($isFirst) {
$isFirst = false;
continue;
}
$response = new HttpResponse();
$request = new HttpRequest(new HttpUri(''));
$request->header->account = 1;
$request->setData('title', $l11n);
$request->setData('language', $language);
$request->setData('value', $attrValue['id']);
$module->apiBillAttributeValueL11nCreate($request, $response);
}
}
}
return $billAttrValue;
}
private static function createTaxCombination(ApplicationAbstract $app, array $taxes) : array
{
$result = [];
/** @var \Modules\Billing\Controller\ApiController $module */
$module = $app->moduleManager->getModuleInstance('Billing');
$itemAttributeSales = ItemAttributeTypeMapper::get()->with('defaults')->where('name', 'sales_tax_code')->execute();
$clientAttributeSales = ClientAttributeTypeMapper::get()->with('defaults')->where('name', 'sales_tax_code')->execute();
$supplierAttributeSales = SupplierAttributeTypeMapper::get()->with('defaults')->where('name', 'purchase_tax_code')->execute();
foreach ($taxes as $tax) {
$itemValue = $itemAttributeSales->getDefaultByValue($tax['item_code']);
$accountValue = $tax['type'] === 1
? $clientAttributeSales->getDefaultByValue($tax['account_code'])
: $supplierAttributeSales->getDefaultByValue($tax['account_code']);
$response = new HttpResponse();
$request = new HttpRequest(new HttpUri(''));
$request->header->account = 1;
$request->setData('tax_type', $tax['type']);
$request->setData('tax_code', $tax['tax_code']);
$request->setData('item_code', $itemValue->getId());
$request->setData('account_code', $accountValue->getId());
$module->apiTaxCombinationCreate($request, $response);
$responseData = $response->get('');
if (!\is_array($responseData)) {
continue;
}
$result = !\is_array($responseData['response'])
? $responseData['response']->toArray()
: $responseData['response'];
$results[] = $result;
}
return $result;
}
/**

13
Admin/Routes/Cli.php Normal file
View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
use phpOMS\Router\RouteVerb;
return [
'^/billing/bill/purchase/parse.*$' => [
[
'dest' => '\Modules\Billing\Controller\CliController:cliParseSupplierBill',
'verb' => RouteVerb::ANY,
],
],
];

View File

@ -0,0 +1,419 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Controller;
use Modules\Billing\Models\Attribute\BillAttribute;
use Modules\Billing\Models\Attribute\BillAttributeMapper;
use Modules\Billing\Models\Attribute\BillAttributeType;
use Modules\Billing\Models\Attribute\BillAttributeTypeL11nMapper;
use Modules\Billing\Models\Attribute\BillAttributeTypeMapper;
use Modules\Billing\Models\Attribute\BillAttributeValue;
use Modules\Billing\Models\Attribute\BillAttributeValueL11nMapper;
use Modules\Billing\Models\Attribute\BillAttributeValueMapper;
use Modules\Billing\Models\Attribute\NullBillAttributeType;
use Modules\Billing\Models\Attribute\NullBillAttributeValue;
use phpOMS\Localization\BaseStringL11n;
use phpOMS\Localization\ISO639x1Enum;
use phpOMS\Message\Http\RequestStatusCode;
use phpOMS\Message\NotificationLevel;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Model\Message\FormValidation;
/**
* Billing class.
*
* @package Modules\Billing
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class ApiAttributeController extends Controller
{
/**
* Api method to create item attribute
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillAttributeCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillAttributeCreate($request))) {
$response->set('attribute_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$attribute = $this->createBillAttributeFromRequest($request);
$this->createModel($request->header->account, $attribute, BillAttributeMapper::class, 'attribute', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Attribute', 'Attribute successfully created', $attribute);
}
/**
* Method to create item attribute from request.
*
* @param RequestAbstract $request Request
*
* @return BillAttribute
*
* @since 1.0.0
*/
private function createBillAttributeFromRequest(RequestAbstract $request) : BillAttribute
{
$attribute = new BillAttribute();
$attribute->bill = (int) $request->getData('bill');
$attribute->type = new NullBillAttributeType((int) $request->getData('type'));
if ($request->getData('value') !== null) {
$attribute->value = new NullBillAttributeValue((int) $request->getData('value'));
} else {
$newRequest = clone $request;
$newRequest->setData('value', $request->getData('custom'), true);
$value = $this->createBillAttributeValueFromRequest($newRequest);
$attribute->value = $value;
}
return $attribute;
}
/**
* Validate bill attribute create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
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')))
) {
return $val;
}
return [];
}
/**
* Api method to create bill attribute l11n
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillAttributeTypeL11nCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillAttributeTypeL11nCreate($request))) {
$response->set('attr_type_l11n_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$attrL11n = $this->createBillAttributeTypeL11nFromRequest($request);
$this->createModel($request->header->account, $attrL11n, BillAttributeTypeL11nMapper::class, 'attr_type_l11n', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Localization', 'Localization successfully created', $attrL11n);
}
/**
* Method to create bill attribute l11n from request.
*
* @param RequestAbstract $request Request
*
* @return BaseStringL11n
*
* @since 1.0.0
*/
private function createBillAttributeTypeL11nFromRequest(RequestAbstract $request) : BaseStringL11n
{
$attrL11n = new BaseStringL11n();
$attrL11n->ref = (int) ($request->getData('type') ?? 0);
$attrL11n->setLanguage((string) (
$request->getData('language') ?? $request->getLanguage()
));
$attrL11n->content = (string) ($request->getData('title') ?? '');
return $attrL11n;
}
/**
* Validate bill attribute l11n create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateBillAttributeTypeL11nCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['type'] = empty($request->getData('type')))
) {
return $val;
}
return [];
}
/**
* Api method to create bill attribute type
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillAttributeTypeCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillAttributeTypeCreate($request))) {
$response->set('attr_type_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$attrType = $this->createBillAttributeTypeFromRequest($request);
$this->createModel($request->header->account, $attrType, BillAttributeTypeMapper::class, 'attr_type', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Attribute type', 'Attribute type successfully created', $attrType);
}
/**
* Method to create bill attribute from request.
*
* @param RequestAbstract $request Request
*
* @return BillAttributeType
*
* @since 1.0.0
*/
private function createBillAttributeTypeFromRequest(RequestAbstract $request) : BillAttributeType
{
$attrType = new BillAttributeType($request->getData('name') ?? '');
$attrType->datatype = (int) ($request->getData('datatype') ?? 0);
$attrType->custom = (bool) ($request->getData('custom') ?? false);
$attrType->isRequired = (bool) ($request->getData('is_required') ?? false);
$attrType->validationPattern = (string) ($request->getData('validation_pattern') ?? '');
$attrType->setL11n((string) ($request->getData('title') ?? ''), $request->getData('language') ?? ISO639x1Enum::_EN);
$attrType->setFields((int) ($request->getData('fields') ?? 0));
return $attrType;
}
/**
* Validate bill attribute create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateBillAttributeTypeCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['name'] = empty($request->getData('name')))
) {
return $val;
}
return [];
}
/**
* Api method to create bill attribute value
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillAttributeValueCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillAttributeValueCreate($request))) {
$response->set('attr_value_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$attrValue = $this->createBillAttributeValueFromRequest($request);
$this->createModel($request->header->account, $attrValue, BillAttributeValueMapper::class, 'attr_value', $request->getOrigin());
if ($attrValue->isDefault) {
$this->createModelRelation(
$request->header->account,
(int) $request->getData('type'),
$attrValue->getId(),
BillAttributeTypeMapper::class, 'defaults', '', $request->getOrigin()
);
}
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Attribute value', 'Attribute value successfully created', $attrValue);
}
/**
* Method to create bill attribute value from request.
*
* @param RequestAbstract $request Request
*
* @return BillAttributeValue
*
* @since 1.0.0
*/
private function createBillAttributeValueFromRequest(RequestAbstract $request) : BillAttributeValue
{
/** @var BillAttributeType $type */
$type = BillAttributeTypeMapper::get()
->where('id', (int) ($request->getData('type') ?? 0))
->execute();
$attrValue = new BillAttributeValue();
$attrValue->isDefault = (bool) ($request->getData('default') ?? false);
$attrValue->setValue($request->getData('value'), $type->datatype);
if ($request->getData('title') !== null) {
$attrValue->setL11n($request->getData('title'), $request->getData('language') ?? ISO639x1Enum::_EN);
}
return $attrValue;
}
/**
* Validate bill attribute value create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateBillAttributeValueCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['type'] = empty($request->getData('type')))
|| ($val['value'] = empty($request->getData('value')))
) {
return $val;
}
return [];
}
/**
* Api method to create bill attribute l11n
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillAttributeValueL11nCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillAttributeValueL11nCreate($request))) {
$response->set('attr_value_l11n_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$attrL11n = $this->createBillAttributeValueL11nFromRequest($request);
$this->createModel($request->header->account, $attrL11n, BillAttributeValueL11nMapper::class, 'attr_value_l11n', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Localization', 'Localization successfully created', $attrL11n);
}
/**
* Method to create bill attribute l11n from request.
*
* @param RequestAbstract $request Request
*
* @return BaseStringL11n
*
* @since 1.0.0
*/
private function createBillAttributeValueL11nFromRequest(RequestAbstract $request) : BaseStringL11n
{
$attrL11n = new BaseStringL11n();
$attrL11n->ref = (int) ($request->getData('value') ?? 0);
$attrL11n->setLanguage((string) (
$request->getData('language') ?? $request->getLanguage()
));
$attrL11n->content = (string) ($request->getData('title') ?? '');
return $attrL11n;
}
/**
* Validate bill attribute l11n create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateBillAttributeValueL11nCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['value'] = empty($request->getData('value')))
) {
return $val;
}
return [];
}
}

View File

@ -0,0 +1,826 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Controller;
use Modules\Admin\Models\NullAccount;
use Modules\Admin\Models\SettingsEnum as AdminSettingsEnum;
use Modules\Billing\Models\Bill;
use Modules\Billing\Models\BillElement;
use Modules\Billing\Models\BillElementMapper;
use Modules\Billing\Models\BillMapper;
use Modules\Billing\Models\BillStatus;
use Modules\Billing\Models\BillTypeMapper;
use Modules\ClientManagement\Models\ClientMapper;
use Modules\ItemManagement\Models\ItemMapper;
use Modules\Media\Models\CollectionMapper;
use Modules\Media\Models\MediaMapper;
use Modules\Media\Models\NullCollection;
use Modules\Media\Models\PathSettings;
use Modules\Media\Models\UploadStatus;
use Modules\SupplierManagement\Models\NullSupplier;
use Modules\SupplierManagement\Models\SupplierMapper;
use phpOMS\Autoloader;
use phpOMS\Localization\ISO3166TwoEnum;
use phpOMS\Localization\Money;
use phpOMS\Message\Http\RequestStatusCode;
use phpOMS\Message\NotificationLevel;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Model\Message\FormValidation;
use phpOMS\System\MimeType;
use phpOMS\Views\View;
/**
* Billing class.
*
* @package Modules\Billing
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class ApiBillController extends Controller
{
/**
* Api method to update a bill
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillUpdate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillUpdate($request))) {
$response->set($request->uri->__toString(), new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
/** @var \Modules\Billing\Models\Bill $old */
$old = BillMapper::get()->where('id', (int) $request->getData('bill'));
$new = $this->updateBillFromRequest($request, $response, $data);
$this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Bill', 'Bill successfully created.', $new);
}
/**
* Method to validate bill creation from request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateBillUpdate(RequestAbstract $request) : array
{
$val = [];
if (($val['bill'] = empty($request->getData('bill')))) {
return $val;
}
return [];
}
/**
* Method to create a bill from request.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return Bill
*
* @since 1.0.0
*/
public function updateBillFromRequest(RequestAbstract $request, ResponseAbstract $response, $data = null) : Bill
{
/** @var Bill $bill */
$bill = BillMapper::get()->where('id', (int) $request->getData('bill'))->execute();
return $bill;
}
/**
* Api method to create a bill
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillCreate($request))) {
$response->set($request->uri->__toString(), new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$bill = $this->createBillFromRequest($request, $response, $data);
$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());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Bill', 'Bill successfully created.', $bill);
}
/**
* Method to create a bill from request.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return Bill
*
* @since 1.0.0
*/
public function createBillFromRequest(RequestAbstract $request, ResponseAbstract $response, $data = null) : Bill
{
/** @var \Modules\ClientManagement\Models\Client|\Modules\SupplierManagement\Models\Supplier $account */
$account = null;
if ($request->getData('client') !== null) {
/** @var \Modules\ClientManagement\Models\Client $account */
$account = ClientMapper::get()
->with('account')
->with('mainAddress')
->where('id', (int) $request->getData('client'))
->execute();
} elseif (((int) ($request->getData('supplier') ?? -1)) === 0) {
/** @var \Modules\SupplierManagement\Models\Supplier $account */
$account = new NullSupplier();
} elseif ($request->getData('supplier') !== null) {
/** @var \Modules\SupplierManagement\Models\Supplier $account */
$account = SupplierMapper::get()
->with('account')
->with('mainAddress')
->where('id', (int) $request->getData('supplier'))
->execute();
}
/** @var \Modules\Billing\Models\BillType $billType */
$billType = BillTypeMapper::get()
->where('id', (int) ($request->getData('type') ?? 1))
->execute();
/* @var \Modules\Account\Models\Account $account */
$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')
?? ($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->billCountry = (string) (
$request->getData('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->setStatus((int) ($request->getData('status') ?? BillStatus::ACTIVE));
return $bill;
}
/**
* Method to validate bill creation from request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateBillCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['client/supplier'] = (empty($request->getData('client'))
&& (empty($request->getData('supplier'))
&& ((int) ($request->getData('supplier') ?? -1) !== 0)
)))
|| ($val['type'] = (empty($request->getData('type'))))
) {
return $val;
}
return [];
}
/**
* Api method to create a bill
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiMediaAddToBill(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateMediaAddToBill($request))) {
$response->set($request->uri->__toString(), new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
/** @var \Modules\Billing\Models\Bill $bill */
$bill = BillMapper::get()->where('id', (int) $request->getData('bill'))->execute();
$path = $this->createBillDir($bill);
$uploaded = [];
if (!empty($uploadedFiles = $request->getFiles())) {
$uploaded = $this->app->moduleManager->get('Media')->uploadFiles(
names: [],
fileNames: [],
files: $uploadedFiles,
account: $request->header->account,
basePath: __DIR__ . '/../../../Modules/Media/Files' . $path,
virtualPath: $path,
pathSettings: PathSettings::FILE_PATH,
hasAccountRelation: false,
readContent: (bool) ($request->getData('parse_content') ?? false)
);
$collection = null;
foreach ($uploaded as $media) {
$this->createModelRelation(
$request->header->account,
$bill->getId(),
$media->getId(),
BillMapper::class,
'media',
'',
$request->getOrigin()
);
if ($request->hasData('type')) {
$this->createModelRelation(
$request->header->account,
$media->getId(),
$request->getData('type', 'int'),
MediaMapper::class,
'types',
'',
$request->getOrigin()
);
}
if ($collection === null) {
$collection = MediaMapper::getParentCollection($path)->limit(1)->execute();
if ($collection instanceof NullCollection) {
$collection = $this->app->moduleManager->get('Media')->createRecursiveMediaCollection(
$path,
$request->header->account,
__DIR__ . '/../../../Modules/Media/Files' . $path,
);
}
}
$this->createModelRelation(
$request->header->account,
$collection->getId(),
$media->getId(),
CollectionMapper::class,
'sources',
'',
$request->getOrigin()
);
}
}
if (!empty($mediaFiles = $request->getDataJson('media'))) {
foreach ($mediaFiles as $media) {
$this->createModelRelation(
$request->header->account,
$bill->getId(),
(int) $media,
BillMapper::class,
'media',
'',
$request->getOrigin()
);
}
}
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Media', 'Media added to bill.', [
'upload' => $uploaded,
'media' => $mediaFiles,
]);
}
/**
* Create media directory path
*
* @param Bill $bill Bill
*
* @return string
*
* @since 1.0.0
*/
private function createBillDir(Bill $bill) : string
{
return '/Modules/Billing/Bills/'
. $this->app->unitId . '/'
. $bill->createdAt->format('Y') . '/'
. $bill->createdAt->format('m') . '/'
. $bill->createdAt->format('d') . '/'
. $bill->getId();
}
/**
* Method to validate bill creation from request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateMediaAddToBill(RequestAbstract $request) : array
{
$val = [];
if (($val['media'] = (empty($request->getData('media')) && empty($request->getFiles())))
|| ($val['bill'] = empty($request->getData('bill')))
) {
return $val;
}
return [];
}
/**
* Api method to create a bill element
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillElementCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillElementCreate($request))) {
$response->set($request->uri->__toString(), new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$element = $this->createBillElementFromRequest($request, $response, $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);
$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);
}
/**
* Method to create a bill element from request.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @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
{
$element = new BillElement();
$element->bill = (int) $request->getData('bill');
$element->item = (int) ($request->getData('item') ?? 0);
if ($element->item === null) {
return $element;
}
/** @var \Modules\ItemManagement\Models\Item $item */
$item = ItemMapper::get()
->with('l11n')
->with('l11n/type')
->where('id', $element->item)
->where('l11n/type/title', ['name1', 'name2', 'name3'], 'IN')
->where('l11n/language', $response->getLanguage())
->execute();
$element->itemNumber = $item->number;
$element->itemName = $item->getL11n('name1')->description;
$element->quantity = (int) ($request->getData('quantity') ?? 0);
$element->singleSalesPriceNet = new Money($request->getData('singlesalespricenet', 'int') ?? $item->salesPrice->getInt());
$element->totalSalesPriceNet = clone $element->singleSalesPriceNet;
$element->totalSalesPriceNet->mult($element->quantity);
// 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));
}
$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
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateBillElementCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['bill'] = empty($request->getData('bill')))) {
return $val;
}
return [];
}
public function apiPreviewRender(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
Autoloader::addPath(__DIR__ . '/../../../Resources/');
$templateId = $request->getData('bill_template', 'int');
if ($templateId === null) {
$billTypeId = $request->getData('bill_type', 'int');
$billType = BillTypeMapper::get()
->where('id', $billTypeId)
->execute();
$templateId = $billType->defaultTemplate->getId();
}
$template = CollectionMapper::get()
->with('sources')
->where('id', $templateId)
->execute();
require_once __DIR__ . '/../../../Resources/tcpdf/tcpdf.php';
$response->header->set('Content-Type', MimeType::M_PDF, true);
$view = new View($this->app->l11nManager, $request, $response);
$view->setTemplate('/' . \substr($template->getSourceByName('bill.pdf.php')->getPath(), 0, -8), 'pdf.php');
$settings = $this->app->appSettings->get(null,
[
AdminSettingsEnum::DEFAULT_TEMPLATES,
AdminSettingsEnum::DEFAULT_ASSETS,
],
unit: $this->app->unitId,
module: 'Admin'
);
$postKey = '::' . $this->app->unitId . ':Admin';
if ($settings === false) {
$settings = $this->app->appSettings->get(null,
[
AdminSettingsEnum::DEFAULT_TEMPLATES,
AdminSettingsEnum::DEFAULT_ASSETS,
],
unit: null,
module: 'Admin'
);
$postKey = ':::Admin';
}
$defaultTemplates = CollectionMapper::get()
->with('sources')
->where('id', (int) $settings[AdminSettingsEnum::DEFAULT_TEMPLATES . $postKey]->content)
->execute();
$defaultAssets = CollectionMapper::get()
->with('sources')
->where('id', (int) $settings[AdminSettingsEnum::DEFAULT_ASSETS . $postKey]->content)
->execute();
$view->setData('defaultTemplates', $defaultTemplates);
$view->setData('defaultAssets', $defaultAssets);
$view->setData('bill', $bill);
$view->setData('path', $pdfDir . '/' . $request->getData('bill') . '.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('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('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_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_type_name', $request->getData('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->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'));
$pdf = $view->render();
$response->set('', $pdf);
}
/**
* Api method to create and archive a bill
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillPdfArchiveCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
Autoloader::addPath(__DIR__ . '/../../../Resources/');
/** @var \Modules\Billing\Models\Bill $bill */
$bill = BillMapper::get()
->with('elements')
->where('id', $request->getData('bill') ?? 0)
->execute();
$templateId = $request->getData('bill_template', 'int');
if ($templateId === null) {
$billTypeId = $bill->type->getId();
$billType = BillTypeMapper::get()
->where('id', $billTypeId)
->execute();
$templateId = $billType->defaultTemplate->getId();
}
$template = CollectionMapper::get()
->with('sources')
->where('id', $templateId)
->execute();
require_once __DIR__ . '/../../../Resources/tcpdf/tcpdf.php';
$view = new View($this->app->l11nManager, $request, $response);
$view->setTemplate('/' . \substr($template->getSourceByName('bill.pdf.php')->getPath(), 0, -8), 'pdf.php');
$settings = $this->app->appSettings->get(null,
[
AdminSettingsEnum::DEFAULT_TEMPLATES,
AdminSettingsEnum::DEFAULT_ASSETS,
],
unit: $this->app->unitId,
module: 'Admin'
);
$postKey = '::' . $this->app->unitId . ':Admin';
if ($settings === false) {
$settings = $this->app->appSettings->get(null,
[
AdminSettingsEnum::DEFAULT_TEMPLATES,
AdminSettingsEnum::DEFAULT_ASSETS,
],
unit: null,
module: 'Admin'
);
$postKey = ':::Admin';
}
$defaultTemplates = CollectionMapper::get()
->with('sources')
->where('id', (int) $settings[AdminSettingsEnum::DEFAULT_TEMPLATES . $postKey]->content)
->execute();
$defaultAssets = CollectionMapper::get()
->with('sources')
->where('id', (int) $settings[AdminSettingsEnum::DEFAULT_ASSETS . $postKey]->content)
->execute();
$view->setData('defaultTemplates', $defaultTemplates);
$view->setData('defaultAssets', $defaultAssets);
/**
@todo: pass data to bill
*/
$pdf = $view->render();
$path = $this->createBillDir($bill);
$pdfDir = __DIR__ . '/../../../Modules/Media/Files' . $path;
$status = !\is_dir($pdfDir) ? \mkdir($pdfDir, 0755, true) : true;
if ($status === false) {
// @codeCoverageIgnoreStart
$response->set($request->uri->__toString(), new FormValidation(['status' => $status]));
$response->header->status = RequestStatusCode::R_400;
return;
// @codeCoverageIgnoreEnd
}
\file_put_contents($pdfDir . '/' . $request->getData('bill') . '.pdf', $pdf);
if (!\is_file($pdfDir . '/' . $request->getData('bill') . '.pdf')) {
$response->header->status = RequestStatusCode::R_400;
return;
}
$media = $this->app->moduleManager->get('Media')->createDbEntry(
status: [
'status' => UploadStatus::OK,
'name' => $request->getData('bill') . '.pdf',
'path' => $pdfDir,
'filename' => $request->getData('bill') . '.pdf',
'size' => \filesize($pdfDir . '/' . $request->getData('bill') . '.pdf'),
'extension' => 'pdf',
],
account: $request->header->account,
virtualPath: $path,
ip: $request->getOrigin(),
app: $this->app,
readContent: true,
unit: $this->app->unitId
);
$this->createModelRelation(
$request->header->account,
$bill->getId(),
$media->getId(),
BillMapper::class,
'media',
'',
$request->getOrigin()
);
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'PDF', 'Bill Pdf successfully created.', $media);
}
/**
* Api method to create bill files
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiNoteCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateNoteCreate($request))) {
$response->set('bill_note_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
/** @var \Modules\Billing\Models\Bill $bill */
$bill = BillMapper::get()->where('id', (int) $request->getData('id'))->execute();
$request->setData('virtualpath', $this->createBillDir($bill), true);
$this->app->moduleManager->get('Editor')->apiEditorCreate($request, $response, $data);
if ($response->header->status !== RequestStatusCode::R_200) {
return;
}
$model = $response->get($request->uri->__toString())['response'];
$this->createModelRelation($request->header->account, $request->getData('id'), $model->getId(), BillMapper::class, 'bill_note', '', $request->getOrigin());
}
/**
* Validate bill note create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateNoteCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['id'] = empty($request->getData('id')))) {
return $val;
}
return [];
}
}

View File

@ -0,0 +1,187 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Controller;
use Modules\Billing\Models\BillTransferType;
use Modules\Billing\Models\BillType;
use Modules\Billing\Models\BillTypeL11nMapper;
use Modules\Billing\Models\BillTypeMapper;
use Modules\Media\Models\NullCollection;
use phpOMS\Localization\BaseStringL11n;
use phpOMS\Localization\ISO639x1Enum;
use phpOMS\Message\Http\RequestStatusCode;
use phpOMS\Message\NotificationLevel;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Model\Message\FormValidation;
/**
* Billing class.
*
* @package Modules\Billing
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class ApiBillTypeController extends Controller
{
/**
* Api method to create item bill type
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillTypeCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillTypeCreate($request))) {
$response->set('bill_type_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$billType = $this->createBillTypeFromRequest($request);
$this->createModel($request->header->account, $billType, BillTypeMapper::class, 'bill_type', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Bill type', 'Bill type successfully created', $billType);
}
/**
* Method to create item attribute from request.
*
* @param RequestAbstract $request Request
*
* @return BillType
*
* @since 1.0.0
*/
private function createBillTypeFromRequest(RequestAbstract $request) : BillType
{
$billType = new BillType($request->getData('name') ?? '');
$billType->setL11n((string) ($request->getData('title') ?? ''), $request->getData('language') ?? ISO639x1Enum::_EN);
$billType->numberFormat = (string) ($request->getData('number_format') ?? '{id}');
$billType->transferStock = (bool) ($request->getData('transfer_stock') ?? false);
$billType->isTemplate = (bool) ($request->getData('is_template') ?? false);
$billType->transferType = (int) ($request->getData('transfer_type') ?? BillTransferType::SALES);
$billType->defaultTemplate = $request->hasData('template')
? new NullCollection((int) $request->getData('template'))
: null;
if ($request->hasData('template')) {
$billType->addTemplate(new NullCollection((int) $request->getData('template')));
}
return $billType;
}
/**
* Validate item attribute create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateBillTypeCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['name'] = empty($request->getData('name')))
) {
return $val;
}
return [];
}
/**
* Api method to create item attribute l11n
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiBillTypeL11nCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateBillTypeL11nCreate($request))) {
$response->set('bill_type_l11n_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$billTypeL11n = $this->createBillTypeL11nFromRequest($request);
$this->createModel($request->header->account, $billTypeL11n, BillTypeL11nMapper::class, 'bill_type_l11n', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Localization', 'Localization successfully created', $billTypeL11n);
}
/**
* Method to create item attribute l11n from request.
*
* @param RequestAbstract $request Request
*
* @return BaseStringL11n
*
* @since 1.0.0
*/
private function createBillTypeL11nFromRequest(RequestAbstract $request) : BaseStringL11n
{
$billTypeL11n = new BaseStringL11n();
$billTypeL11n->ref = (int) ($request->getData('type') ?? 0);
$billTypeL11n->setLanguage((string) (
$request->getData('language') ?? $request->getLanguage()
));
$billTypeL11n->content = (string) ($request->getData('title') ?? '');
return $billTypeL11n;
}
/**
* Validate item attribute l11n create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateBillTypeL11nCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['title'] = empty($request->getData('title')))
|| ($val['type'] = empty($request->getData('type')))
) {
return $val;
}
return [];
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,305 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Controller;
use Modules\Billing\Models\Price\Price;
use Modules\Billing\Models\Price\PriceMapper;
use Modules\Billing\Models\Price\PriceType;
use Modules\Billing\Models\Tax\TaxCombinationMapper;
use Modules\ClientManagement\Models\ClientMapper;
use Modules\ClientManagement\Models\NullClient;
use Modules\ClientManagement\Models\NullClientAttributeValue;
use Modules\ItemManagement\Models\ItemMapper;
use Modules\ItemManagement\Models\NullItem;
use Modules\ItemManagement\Models\NullItemAttributeValue;
use Modules\SupplierManagement\Models\NullSupplier;
use Modules\SupplierManagement\Models\SupplierMapper;
use phpOMS\Localization\ISO4217CharEnum;
use phpOMS\Message\Http\RequestStatusCode;
use phpOMS\Message\NotificationLevel;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Model\Message\FormValidation;
use phpOMS\System\MimeType;
/**
* Billing class.
*
* @package Modules\Billing
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class ApiPriceController extends Controller
{
/**
* Api method to find items
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiPricingFind(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
// Get item
$item = null;
if ($request->hasData('price_item')) {
$item = ItemMapper::get()
->with('attributes')
->with('attributes/type')
->with('attributes/value')
->where('id', (int) $request->getData('price_item'))
->execute();
}
// Get account
$account = null;
/** @var null|\Modules\ClientManagement\Models\Client $client */
$client = null;
/** @var null|\Modules\SupplierManagement\Models\Supplier $supplier */
$supplier = null;
if ($request->hasData('price_client')) {
$client = ClientMapper::get()
->with('attributes/type')
->with('attributes/value')
->where('id', (int) $request->getData('price_client'))
->execute();
$account = $client;
} else {
$supplier = SupplierMapper::get()
->with('attributes/type')
->with('attributes/value')
->where('id', (int) $request->getData('price_supplier'))
->execute();
$account = $supplier;
}
// Get all relevant prices
// @todo: allow to define NOT IN somehow (e.g. not in France -> simple solution to define export prices etc.)
$queryMapper = PriceMapper::getAll();
if ($request->hasData('price_name')) {
$queryMapper->where('name', $request->getData('price_name'));
}
$queryMapper->where('promocode', \array_unique([$request->getData('price_promocode'), null]), 'IN');
$queryMapper->where('item', \array_unique([$request->getData('price_item', 'int'), null]), 'IN');
$queryMapper->where('itemgroup', \array_unique([$request->getData('price_itemgroup', 'int'), $item?->getAttribute('itemgroup')?->getId(), null]), 'IN');
$queryMapper->where('itemsegment', \array_unique([$request->getData('price_itemsegment', 'int'), $item?->getAttribute('itemsegment')?->getId(), null]), 'IN');
$queryMapper->where('itemsection', \array_unique([$request->getData('price_itemsection', 'int'), $item?->getAttribute('itemsection')?->getId(), null]), 'IN');
$queryMapper->where('itemtype', \array_unique([$request->getData('price_itemtype', 'int'), $item?->getAttribute('itemtype')?->getId(), null]), 'IN');
$queryMapper->where('client', \array_unique([$request->getData('price_client', 'int'), null]), 'IN');
$queryMapper->where('clientgroup', \array_unique([$request->getData('price_clientgroup', 'int'), $client?->getAttribute('clientgroup')?->getId(), null]), 'IN');
$queryMapper->where('clientsegment', \array_unique([$request->getData('price_clientsegment', 'int'), $client?->getAttribute('clientsegment')?->getId(), null]), 'IN');
$queryMapper->where('clientsection', \array_unique([$request->getData('price_clientsection', 'int'), $client?->getAttribute('clientsection')?->getId(), null]), 'IN');
$queryMapper->where('clienttype', \array_unique([$request->getData('price_clienttype', 'int'), $client?->getAttribute('clienttype')?->getId(), null]), 'IN');
$queryMapper->where('clientcountry', \array_unique([$request->getData('price_clientcountry'), $client?->mainAddress->getCountry(), null]), 'IN');
$queryMapper->where('supplier', \array_unique([$request->getData('price_supplier', 'int'), null]), 'IN');
$queryMapper->where('unit', \array_unique([$request->getData('price_unit', 'int'), null]), 'IN');
$queryMapper->where('type', $request->getData('price_type', 'int') ?? PriceType::SALES);
$queryMapper->where('currency', array_unique([$request->getData('price_currency', 'int'), null]), 'IN');
// @todo: implement start and end
/*
@todo: implement quantity
if ($request->hasData('price_quantity')) {
$whereQuery = new Where();
$whereQuery->where('quantity', (int) $request->getData('price_quantity'), '<=')
->where('quantity', null, '=', 'OR')
$queryMapper->where('quantity', $whereQuery);
}
*/
$prices = $queryMapper->execute();
// Find base price (@todo: probably not a good solution)
$bestBasePrice = null;
foreach ($prices as $price) {
if ($price->price !== 0 && $price->priceNew === 0
&& $price->item->getId() !== 0
&& $price->itemgroup->getId() === 0
&& $price->itemsegment->getId() === 0
&& $price->itemsection->getId() === 0
&& $price->itemtype->getId() === 0
&& $price->client->getId() === 0
&& $price->clientgroup->getId() === 0
&& $price->clientsegment->getId() === 0
&& $price->clientsection->getId() === 0
&& $price->clienttype->getId() === 0
&& $price->promocode === ''
) {
if ($price->price < $bestBasePrice?->price ?? \PHP_INT_MAX) {
$bestBasePrice = $price;
}
}
}
// @todo: implement prices which cannot be improved even if there are better prices available (i.e. some customer groups may not get better prices, Dentagen Beispiel)
// alternatively set prices as 'improvable' => which whitelists a price as can be improved or 'alwaysimproces' which always overwrites other prices
// Find best price
$bestPrice = null;
$bestPriceValue = \PHP_INT_MAX;
foreach ($prices as $price) {
$newPrice = $bestBasePrice->price;
if ($price->price < $newPrice) {
$newPrice = $price->price;
}
if ($price->priceNew < $newPrice) {
$newPrice = $price->priceNew;
}
$newPrice -= $price->discount;
$newPrice = (int) ((10000 / $price->discountPercentage) * $newPrice);
$newPrice = (int) (($price->quantity === 0 ? 10000 : $price->quantity) / (10000 + $price->bonus) * $newPrice);
// @todo: the calculation above regarding discount and bonus don't consider the purchased quantity.
// If a customer receives 1+1 but purchases 2, then he gets 2+2 (if multiply === true) which is better than 1+1 with multiply false.
if ($newPrice < $bestPriceValue) {
$bestPriceValue = $newPrice;
$bestPrice = $price;
}
}
// Get tax definition
$tax = ($request->getData('price_type', 'int') ?? PriceType::SALES)
? TaxCombinationMapper::get()
->where('itemCode', $request->getData('price_item'))
->where('clientCode', $account->getAttribute('client_code')->getId())
->execute()
: TaxCombinationMapper::get()
->where('itemCode', $request->getData('price_item'))
->where('supplierCode', $account->getAttribute('supplier_code')->getId())
->execute();
$response->header->set('Content-Type', MimeType::M_JSON, true);
$response->set(
$request->uri->__toString(),
\array_values($prices)
);
}
/**
* Api method to create item bill type
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiPriceCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validatePriceCreate($request))) {
$response->set('price_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$tax = $this->createPriceFromRequest($request);
$this->createModel($request->header->account, $tax, PriceMapper::class, 'price', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Price', 'Price successfully created', $tax);
}
/**
* Method to create item attribute from request.
*
* @param RequestAbstract $request Request
*
* @return Price
*
* @since 1.0.0
*/
private function createPriceFromRequest(RequestAbstract $request) : Price
{
$price = new Price();
$price->name = $request->getData('name') ?? '';
$price->promocode = $request->getData('promocode') ?? '';
$price->item = new NullItem((int) $request->getData('item'));
$price->itemgroup = new NullItemAttributeValue((int) $request->getData('itemgroup'));
$price->itemsegment = new NullItemAttributeValue((int) $request->getData('itemsegment'));
$price->itemsection = new NullItemAttributeValue((int) $request->getData('itemsection'));
$price->itemtype = new NullItemAttributeValue((int) $request->getData('itemtype'));
$price->client = new NullClient((int) $request->getData('client'));
$price->clientgroup = new NullClientAttributeValue((int) $request->getData('clientgroup'));
$price->clientsegment = new NullClientAttributeValue((int) $request->getData('clientsegment'));
$price->clientsection = new NullClientAttributeValue((int) $request->getData('clientsection'));
$price->clienttype = new NullClientAttributeValue((int) $request->getData('clienttype'));
$price->supplier = new NullSupplier((int) $request->getData('supplier'));
$price->unit = (int) $request->getData('unit');
$price->type = (int) ($request->getData('type') ?? PriceType::SALES);
$price->quantity = (int) $request->getData('quantity');
$price->price = (int) $request->getData('price');
$price->priceNew = (int) $request->getData('price_name');
$price->discount = (int) $request->getData('discount');
$price->discountPercentage = (int) $request->getData('discountPercentage');
$price->bonus = (int) $request->getData('bonus');
$price->multiply = (bool) ($request->getData('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;
return $price;
}
/**
* Validate item attribute create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validatePriceCreate(RequestAbstract $request) : array
{
$val = [];
if (false) {
return $val;
}
return [];
}
}

View File

@ -0,0 +1,132 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Controller;
use Modules\Billing\Models\BillStatus;
use Modules\Billing\Models\BillTransferType;
use Modules\Billing\Models\BillTypeMapper;
use Modules\Billing\Models\SettingsEnum;
use phpOMS\Message\Http\HttpRequest;
use phpOMS\Message\Http\HttpResponse;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\System\OperatingSystem;
use phpOMS\System\SystemType;
use phpOMS\System\SystemUtils;
use phpOMS\Uri\HttpUri;
/**
* Billing class.
*
* @package Modules\Billing
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class ApiPurchaseController extends Controller
{
/**
* Api method to create bill files
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiSupplierBillUpload(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
$originalType = (int) ($request->getData('type') ?? $this->app->appSettings->get(
names: SettingsEnum::ORIGINAL_MEDIA_TYPE,
module: self::NAME
)->content);
/** @var \Modules\Billing\Models\BillType $purchaseTransferType */
$purchaseTransferType = BillTypeMapper::get()
->where('transferType', BillTransferType::PURCHASE)
->limit(1)
->execute();
$files = $request->getFiles();
foreach ($files as $file) {
// Create default bill
$billRequest = new HttpRequest(new HttpUri(''));
$billRequest->header->account = $request->header->account;
$billRequest->header->l11n = $request->header->l11n;
$billRequest->setData('supplier', 0);
$billRequest->setData('status', BillStatus::UNPARSED);
$billRequest->setData('type', $purchaseTransferType->getId());
$billResponse = new HttpResponse();
$billResponse->header->l11n = $response->header->l11n;
$this->app->moduleManager->get('Billing', 'Api')->apiBillCreate($billRequest, $billResponse, $data);
$billId = $billResponse->get('')['response']->getId();
// Upload and assign document to bill
$mediaRequest = new HttpRequest();
$mediaRequest->header->account = $request->header->account;
$mediaRequest->header->l11n = $request->header->l11n;
$mediaRequest->addFile($file);
$mediaResponse = new HttpResponse();
$mediaResponse->header->l11n = $response->header->l11n;
$mediaRequest->setData('bill', $billId);
$mediaRequest->setData('type', $originalType);
$mediaRequest->setData('parse_content', true, true);
$this->app->moduleManager->get('Billing', 'Api')->apiMediaAddToBill($mediaRequest, $mediaResponse, $data);
$uploaded = $mediaResponse->get('')['response']['upload'];
$in = \reset($uploaded)->getAbsolutePath(); // pdf is parsed in $in->content
if (!\is_file($in)) {
throw new \Exception();
}
// @todo: Parse text and analyze text structure
// Create internal document
$billResponse = new HttpResponse();
$billRequest = new HttpRequest(new HttpUri(''));
$billRequest->header->account = $request->header->account;
$billRequest->setData('bill', $billId);
$this->app->moduleManager->get('Billing', 'Api')->apiBillPdfArchiveCreate($billRequest, $billResponse);
// Offload bill parsing to cli
$cliPath = \realpath(__DIR__ . '/../../../Cli/cli.php');
if ($cliPath === false) {
return;
}
SystemUtils::runProc(
OperatingSystem::getSystem() === SystemType::WIN ? 'php.exe' : 'php',
\escapeshellarg($cliPath)
. ' /billing/bill/purchase/parse '
. '-i ' . \escapeshellarg((string) $billId),
true
);
}
}
}

View File

@ -0,0 +1,139 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Controller;
use Modules\Admin\Models\Address;
use Modules\Billing\Models\Tax\TaxCombination;
use Modules\Billing\Models\Tax\TaxCombinationMapper;
use Modules\ClientManagement\Models\Client;
use Modules\ClientManagement\Models\ClientAttributeTypeMapper;
use Modules\ClientManagement\Models\ClientAttributeValue;
use Modules\ClientManagement\Models\NullClientAttributeValue;
use Modules\ItemManagement\Models\NullItemAttributeValue;
use Modules\SupplierManagement\Models\NullSupplierAttributeValue;
use phpOMS\Localization\ISO3166CharEnum;
use phpOMS\Message\Http\RequestStatusCode;
use phpOMS\Message\NotificationLevel;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Model\Message\FormValidation;
/**
* Billing class.
*
* @package Modules\Billing
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class ApiTaxController extends Controller
{
public function apiTaxCombinationCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
{
if (!empty($val = $this->validateTaxCombinationCreate($request))) {
$response->set('tax_combination_create', new FormValidation($val));
$response->header->status = RequestStatusCode::R_400;
return;
}
$tax = $this->createTaxCombinationFromRequest($request);
$this->createModel($request->header->account, $tax, TaxCombinationMapper::class, 'tax_combination', $request->getOrigin());
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Tax combination', 'Tax combination successfully created', $tax);
}
/**
* Method to create item attribute from request.
*
* @param RequestAbstract $request Request
*
* @return TaxCombination
*
* @since 1.0.0
*/
private function createTaxCombinationFromRequest(RequestAbstract $request) : TaxCombination
{
$tax = new TaxCombination();
$tax->taxType = (int) $request->getData('tax_type') ?? 1;
$tax->taxCode = (string) $request->getData('tax_code');
$tax->itemCode = new NullItemAttributeValue((int) $request->getData('item_code'));
if ($tax->taxType === 1) {
$tax->clientCode = new NullClientAttributeValue((int) $request->getData('account_code'));
} else {
$tax->supplierCode = new NullSupplierAttributeValue((int) $request->getData('account_code'));
}
return $tax;
}
/**
* Validate item attribute create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
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')))
) {
return $val;
}
return [];
}
public function getClientTaxCode(Client $client, Address $taxOfficeAddress) : ClientAttributeValue
{
$codes = ClientAttributeTypeMapper::get()
->with('defaults')
->where('name', 'sales_tax_code')
->execute();
$taxCode = new NullClientAttributeValue();
if ($taxOfficeAddress->getCountry() === $client->mainAddress->getCountry()) {
$taxCode = $codes->getDefaultByValue($client->mainAddress->getCountry());
} elseif (\in_array($taxOfficeAddress->getCountry(), ISO3166CharEnum::getRegion('eu'))
&& \in_array($client->mainAddress->getCountry(), ISO3166CharEnum::getRegion('eu'))
) {
if (!empty($client->getAttribute('vat_id')?->value->getValue())) {
// Is EU company
$taxCode = $codes->getDefaultByValue('EU');
} else {
// Is EU private customer
$taxCode = $codes->getDefaultByValue($client->mainAddress->getCountry());
}
} elseif (\in_array($taxOfficeAddress->getCountry(), ISO3166CharEnum::getRegion('eu'))) {
// None EU company but we are EU company
$taxCode = $codes->getDefaultByValue('INT');
} else {
// None EU company and we are also none EU company
$taxCode = $codes->getDefaultByValue('INT');
}
return $taxCode;
}
}

View File

@ -296,6 +296,7 @@ final class BackendController extends Controller
$bill = PurchaseBillMapper::get()
->with('elements')
->with('media')
->with('media/types')
->with('notes')
->where('id', (int) $request->getData('id'))
->execute();
@ -803,6 +804,7 @@ final class BackendController extends Controller
$bill = PurchaseBillMapper::get()
->with('elements')
->with('media')
->with('media/types')
->with('notes')
->where('id', (int) $request->getData('id'))
->execute();

View File

@ -0,0 +1,306 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Controller;
use Modules\Billing\Models\BillMapper;
use Modules\Billing\Models\BillTypeMapper;
use Modules\Billing\Models\NullBillType;
use Modules\Billing\Models\SettingsEnum;
use Modules\SupplierManagement\Models\NullSupplier;
use Modules\SupplierManagement\Models\Supplier;
use Modules\SupplierManagement\Models\SupplierMapper;
use phpOMS\Contract\RenderableInterface;
use phpOMS\Localization\ISO639x1Enum;
use phpOMS\Localization\LanguageDetection\Language;
use phpOMS\Localization\Money;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Views\View;
/**
* Billing controller class.
*
* @package Modules\Billing
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class CliController extends Controller
{
/**
* Analyse supplier bill
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return RenderableInterface Response can be rendered
*
* @since 1.0.0
* @codeCoverageIgnore
*/
public function cliParseSupplierBill(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : RenderableInterface
{
$originalType = (int) ($request->getData('type') ?? $this->app->appSettings->get(
names: SettingsEnum::ORIGINAL_MEDIA_TYPE,
module: self::NAME
)->content);
/** @var \Modules\Billing\Models\Bill $bill */
$bill = BillMapper::get()
->with('media')
->with('media/types')
->with('media/content')
->where('id', (int) $request->getData('i'))
->where('media/types/id', $originalType)
->execute();
$old = clone $bill;
$content = \strtolower($bill->getFileByType($originalType)->content->content ?? '');
$lines = \explode("\n", $content);
$language = $this->detectLanguage($content);
$bill->language = $language;
$identifiers = \json_decode(\file_get_contents(__DIR__ . '/../Models/billIdentifier.json'), true);
/* Supplier */
$suppliers = SupplierMapper::getAll()
->with('account')
->with('mainAddress')
->with('attributes/type')
->where('attributes/type/name', ['bill_match_pattern', 'bill_date_format'], 'IN')
->execute();
$supplierId = $this->matchSupplier($content, $suppliers);
$bill->supplier = new NullSupplier($supplierId);
$supplier = $suppliers[$supplierId] ?? new NullSupplier();
$bill->billTo = $supplier->account->name1;
$bill->billAddress = $supplier->mainAddress->address;
$bill->billCity = $supplier->mainAddress->city;
$bill->billZip = $supplier->mainAddress->postal;
$bill->billCountry = $supplier->mainAddress->getCountry();
/* Type */
$type = $this->findSupplierInvoiceType($content, $identifiers['type'], $language);
$billType = BillTypeMapper::get()
->where('name', $type)
->execute();
$bill->type = new NullBillType($billType->getId());
/* Number */
$billNumber = $this->findBillNumber($lines, $identifiers['bill_no'][$language]);
$bill->number = $billNumber;
/* Date */
$billDateTemp = $this->findBillDate($lines, $identifiers['bill_date'][$language]);
$billDate = $this->parseDate($billDateTemp, $supplier, $identifiers['date_format']);
$bill->billDate = $billDate;
/* Total Gross */
$totalGross = $this->findBillGross($lines, $identifiers['total_gross'][$language]);
$bill->grossCosts = new Money($totalGross);
$this->updateModel($request->header->account, $old, $bill, BillMapper::class, 'bill_parsing', $request->getOrigin());
$view = new View($this->app->l11nManager, $request, $response);
$view->setTemplate('/Modules/Billing/Theme/Cli/bill-parsed');
$view->setData('bill', $bill);
return $view;
}
private function detectLanguage(string $content) : string
{
$detector = new Language();
$language = $detector->detect($content)->bestResults()->close();
if (!\is_array($language) || \count($language) < 1) {
return 'en';
}
return \substr(\array_keys($language)[0], 0, 2);
}
private function findSupplierInvoiceType(string $content, array $types, string $language) : string
{
$bestPos = \strlen($content);
$bestMatch = '';
foreach ($types as $name => $type) {
foreach ($type[$language] as $l11n) {
$found = \stripos($content, \strtolower($l11n));
if ($found !== false && $found < $bestPos) {
$bestPos = $found;
$bestMatch = $name;
}
}
}
return empty($bestMatch) ? 'purchase_invoice' : $bestMatch;
}
private function findBillNumber(array $lines, array $matches) : string
{
$bestPos = \count($lines);
$bestMatch = '';
$found = [];
foreach ($matches as $match) {
foreach ($lines as $row => $line) {
if (\preg_match($match, $line, $found) === 1) {
if ($row < $bestPos) {
$bestPos = $row;
$bestMatch = \trim($found['bill_no']);
}
break;
}
}
}
return $bestMatch;
}
private function findBillDue(array $lines, array $matches) : string
{
$bestPos = \count($lines);
$bestMatch = '';
$found = [];
foreach ($matches as $match) {
foreach ($lines as $row => $line) {
if (\preg_match($match, $line, $found) === 1) {
if ($row < $bestPos) {
// @question: don't many invoices have the due date at the bottom? bestPos doesn't make sense?!
$bestPos = $row;
$bestMatch = \trim($found['bill_due']);
}
break;
}
}
}
return $bestMatch;
}
private function findBillDate(array $lines, array $matches) : string
{
$bestPos = \count($lines);
$bestMatch = '';
$found = [];
foreach ($matches as $match) {
foreach ($lines as $row => $line) {
if (\preg_match($match, $line, $found) === 1) {
if ($row < $bestPos) {
$bestPos = $row;
$bestMatch = \trim($found['bill_date']);
}
break;
}
}
}
return $bestMatch;
}
private function findBillGross(array $lines, array $matches) : int
{
$bestMatch = 0;
$found = [];
foreach ($matches as $match) {
foreach ($lines as $line) {
if (\preg_match($match, $line, $found) === 1) {
$temp = \trim($found['total_gross']);
$posD = \stripos($temp, '.');
$posK = \stripos($temp, ',');
$hasDecimal = ($posD !== false || $posK !== false)
&& \max((int) $posD, (int) $posK) + 3 >= \strlen($temp);
$gross = ((int) \str_replace(['.', ','], ['', ''], $temp)) * ($hasDecimal
? 100
: 10000);
if ($gross > $bestMatch) {
$bestMatch = $gross;
}
}
}
}
return $bestMatch;
}
private function matchSupplier(string $content, array $suppliers) : int
{
$bestMatch = 0;
foreach ($suppliers as $supplier) {
if ((!empty($supplier->getAttributeByTypeName('iban')->value->valueStr)
&& \stripos($content, $supplier->getAttributeByTypeName('iban')->value->valueStr) !== false)
|| (!empty($supplier->getAttributeByTypeName('bill_match_pattern')->value->valueStr)
&& \stripos($content, $supplier->getAttributeByTypeName('bill_match_pattern')->value->valueStr) !== false)
) {
return $supplier->getId();
}
if (\stripos($content, $supplier->account->name1) !== false) {
if ((!empty($supplier->mainAddress->city)
&& \stripos($content, $supplier->mainAddress->city) !== false)
|| (!empty( $supplier->mainAddress->address)
&& \stripos($content, $supplier->mainAddress->address) !== false)
) {
return $supplier->getId();
}
$bestMatch = $supplier->getId();
}
}
return $bestMatch;
}
private function parseDate(string $date, Supplier $supplier, array $formats) : ?\DateTime
{
if ((!empty($supplier->getAttributeByTypeName('bill_date_format')->value->valueStr))) {
return \DateTime::createFromFormat(
$supplier->getAttributeByTypeName('bill_date_format')->value->valueStr,
$date
);
}
foreach ($formats as $format) {
if (($obj = \DateTime::createFromFormat($format, $date)) !== false) {
return $obj;
}
}
return null;
}
}

View File

@ -0,0 +1,40 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
use phpOMS\Stdlib\Base\Enum;
/**
* Attribute value type enum.
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
abstract class AttributeValueType extends Enum
{
public const _INT = 1;
public const _STRING = 2;
public const _FLOAT = 3;
public const _DATETIME = 4;
public const _BOOL = 5;
public const _FLOAT_INT = 6;
}

View File

@ -0,0 +1,102 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
/**
* Bill class.
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
class BillAttribute implements \JsonSerializable
{
/**
* Id.
*
* @var int
* @since 1.0.0
*/
protected int $id = 0;
/**
* Bill this attribute belongs to
*
* @var int
* @since 1.0.0
*/
public int $bill = 0;
/**
* Attribute type the attribute belongs to
*
* @var BillAttributeType
* @since 1.0.0
*/
public BillAttributeType $type;
/**
* Attribute value the attribute belongs to
*
* @var BillAttributeValue
* @since 1.0.0
*/
public BillAttributeValue $value;
/**
* Constructor.
*
* @since 1.0.0
*/
public function __construct()
{
$this->type = new NullBillAttributeType();
$this->value = new NullBillAttributeValue();
}
/**
* Get id
*
* @return int
*
* @since 1.0.0
*/
public function getId() : int
{
return $this->id;
}
/**
* {@inheritdoc}
*/
public function toArray() : array
{
return [
'id' => $this->id,
'bill' => $this->bill,
'type' => $this->type,
'value' => $this->value,
];
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return $this->toArray();
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
/**
* Bill mapper class.
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class BillAttributeMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'billing_bill_attr_id' => ['name' => 'billing_bill_attr_id', 'type' => 'int', 'internal' => 'id'],
'billing_bill_attr_bill' => ['name' => 'billing_bill_attr_bill', 'type' => 'int', 'internal' => 'bill'],
'billing_bill_attr_type' => ['name' => 'billing_bill_attr_type', 'type' => 'int', 'internal' => 'type'],
'billing_bill_attr_value' => ['name' => 'billing_bill_attr_value', 'type' => 'int', 'internal' => 'value'],
];
/**
* Has one relation.
*
* @var array<string, array{mapper:class-string, external:string, by?:string, column?:string, conditional?:bool}>
* @since 1.0.0
*/
public const OWNS_ONE = [
'type' => [
'mapper' => BillAttributeTypeMapper::class,
'external' => 'billing_bill_attr_type',
],
'value' => [
'mapper' => BillAttributeValueMapper::class,
'external' => 'billing_bill_attr_value',
],
];
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'billing_bill_attr';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD = 'billing_bill_attr_id';
}

View File

@ -0,0 +1,216 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
use phpOMS\Localization\BaseStringL11n;
use phpOMS\Localization\ISO639x1Enum;
/**
* Bill Attribute Type class.
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
class BillAttributeType implements \JsonSerializable
{
/**
* Id
*
* @var int
* @since 1.0.0
*/
protected int $id = 0;
/**
* Name/string identifier by which it can be found/categorized
*
* @var string
* @since 1.0.0
*/
public string $name = '';
/**
* Which field data type is required (string, int, ...) in the value
*
* @var int
* @since 1.0.0
*/
protected int $fields = 0;
/**
* Is a custom value allowed (e.g. custom string)
*
* @var bool
* @since 1.0.0
*/
public bool $custom = false;
public string $validationPattern = '';
public bool $isRequired = false;
/**
* Datatype of the attribute
*
* @var int
* @since 1.0.0
*/
public int $datatype = AttributeValueType::_STRING;
/**
* Localization
*
* @var BaseStringL11n
*/
private string | BaseStringL11n $l11n = '';
/**
* Possible default attribute values
*
* @var array
*/
private array $defaults = [];
/**
* Default attribute value
*
* @var int
* @since 1.0.0
*/
public int $default = 0;
/**
* Constructor.
*
* @param string $name Name/identifier of the attribute type
*
* @since 1.0.0
*/
public function __construct(string $name = '')
{
$this->name = $name;
}
/**
* Get id
*
* @return int
*
* @since 1.0.0
*/
public function getId() : int
{
return $this->id;
}
public function getDefaultByValue(mixed $value) : BillAttributeValue
{
foreach ($this->defaults as $default) {
if ($default->getValue() === $value) {
return $default;
}
}
return new NullBillAttributeValue();
}
/**
* Set l11n
*
* @param string|BaseStringL11n $l11n Tag article l11n
* @param string $lang Language
*
* @return void
*
* @since 1.0.0
*/
public function setL11n(string | BaseStringL11n $l11n, string $lang = ISO639x1Enum::_EN) : void
{
if ($l11n instanceof BaseStringL11n) {
$this->l11n = $l11n;
} elseif (isset($this->l11n) && $this->l11n instanceof BaseStringL11n) {
$this->l11n->content = $l11n;
$this->l11n->setLanguage($lang);
} else {
$this->l11n = new BaseStringL11n();
$this->l11n->content = $l11n;
$this->l11n->setLanguage($lang);
}
}
/**
* @return string
*
* @since 1.0.0
*/
public function getL11n() : string
{
if (!isset($this->l11n)) {
return '';
}
return $this->l11n instanceof BaseStringL11n ? $this->l11n->content : $this->l11n;
}
/**
* Set fields
*
* @param int $fields Fields
*
* @return void
*
* @since 1.0.0
*/
public function setFields(int $fields) : void
{
$this->fields = $fields;
}
/**
* Get default values
*
* @return array
*
* @sicne 1.0.0
*/
public function getDefaults() : array
{
return $this->defaults;
}
/**
* {@inheritdoc}
*/
public function toArray() : array
{
return [
'id' => $this->id,
'name' => $this->name,
'validationPattern' => $this->validationPattern,
'custom' => $this->custom,
'isRequired' => $this->isRequired,
];
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return $this->toArray();
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
use phpOMS\Localization\BaseStringL11n;
/**
* Bill mapper class.
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class BillAttributeTypeL11nMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'billing_attr_type_l11n_id' => ['name' => 'billing_attr_type_l11n_id', 'type' => 'int', 'internal' => 'id'],
'billing_attr_type_l11n_title' => ['name' => 'billing_attr_type_l11n_title', 'type' => 'string', 'internal' => 'content', 'autocomplete' => true],
'billing_attr_type_l11n_type' => ['name' => 'billing_attr_type_l11n_type', 'type' => 'int', 'internal' => 'ref'],
'billing_attr_type_l11n_lang' => ['name' => 'billing_attr_type_l11n_lang', 'type' => 'string', 'internal' => 'language'],
];
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'billing_attr_type_l11n';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD = 'billing_attr_type_l11n_id';
/**
* Model to use by the mapper.
*
* @var class-string
* @since 1.0.0
*/
public const MODEL = BaseStringL11n::class;
}

View File

@ -0,0 +1,82 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
/**
* Bill mapper class.
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class BillAttributeTypeMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'billing_attr_type_id' => ['name' => 'billing_attr_type_id', 'type' => 'int', 'internal' => 'id'],
'billing_attr_type_name' => ['name' => 'billing_attr_type_name', 'type' => 'string', 'internal' => 'name', 'autocomplete' => true],
'billing_attr_type_datatype' => ['name' => 'billing_attr_type_datatype', 'type' => 'int', 'internal' => 'datatype'],
'billing_attr_type_fields' => ['name' => 'billing_attr_type_fields', 'type' => 'int', 'internal' => 'fields'],
'billing_attr_type_custom' => ['name' => 'billing_attr_type_custom', 'type' => 'bool', 'internal' => 'custom'],
'billing_attr_type_pattern' => ['name' => 'billing_attr_type_pattern', 'type' => 'string', 'internal' => 'validationPattern'],
'billing_attr_type_required' => ['name' => 'billing_attr_type_required', 'type' => 'bool', 'internal' => 'isRequired'],
];
/**
* Has many relation.
*
* @var array<string, array{mapper:class-string, table:string, self?:?string, external?:?string, column?:string}>
* @since 1.0.0
*/
public const HAS_MANY = [
'l11n' => [
'mapper' => BillAttributeTypeL11nMapper::class,
'table' => 'billing_attr_type_l11n',
'self' => 'billing_attr_type_l11n_type',
'column' => 'content',
'external' => null,
],
'defaults' => [
'mapper' => BillAttributeValueMapper::class,
'table' => 'billing_bill_attr_default',
'self' => 'billing_bill_attr_default_type',
'external' => 'billing_bill_attr_default_value',
],
];
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'billing_attr_type';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD = 'billing_attr_type_id';
}

View File

@ -0,0 +1,230 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
use phpOMS\Localization\BaseStringL11n;
use phpOMS\Localization\ISO639x1Enum;
/**
* Bill attribute value class.
*
* The relation with the type/bill is defined in the BillAttribute class.
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
class BillAttributeValue implements \JsonSerializable
{
/**
* Id
*
* @var int
* @since 1.0.0
*/
protected int $id = 0;
/**
* Depending attribute type
*
* @var null|int
* @since 1.0.0
*/
public ?int $dependingAttributeType = null;
/**
* Depending attribute value
*
* @var null|int
* @since 1.0.0
*/
public ?int $dependingAttributeValue = null;
/**
* Int value
*
* @var null|int
* @since 1.0.0
*/
public ?int $valueInt = null;
/**
* String value
*
* @var null|string
* @since 1.0.0
*/
public ?string $valueStr = null;
/**
* Decimal value
*
* @var null|float
* @since 1.0.0
*/
public ?float $valueDec = null;
/**
* DateTime value
*
* @var null|\DateTimeInterface
* @since 1.0.0
*/
public ?\DateTimeInterface $valueDat = null;
/**
* Is a default value which can be selected
*
* @var bool
* @since 1.0.0
*/
public bool $isDefault = false;
/**
* Unit of the value
*
* @var string
* @since 1.0.0
*/
public string $unit = '';
/**
* Localization
*
* @var null|BaseStringL11n
*/
private ?BaseStringL11n $l11n = null;
/**
* Get id
*
* @return int
*
* @since 1.0.0
*/
public function getId() : int
{
return $this->id;
}
/**
* Set l11n
*
* @param string|BaseStringL11n $l11n Tag article l11n
* @param string $lang Language
*
* @return void
*
* @since 1.0.0
*/
public function setL11n(string | BaseStringL11n $l11n, string $lang = ISO639x1Enum::_EN) : void
{
if ($l11n instanceof BaseStringL11n) {
$this->l11n = $l11n;
} elseif (isset($this->l11n) && $this->l11n instanceof BaseStringL11n) {
$this->l11n->content = $l11n;
$this->l11n->setLanguage($lang);
} else {
$this->l11n = new BaseStringL11n();
$this->l11n->content = $l11n;
$this->l11n->ref = $this->id;
$this->l11n->setLanguage($lang);
}
}
/**
* Get localization
*
* @return null|string
*
* @since 1.0.0
*/
public function getL11n() : ?string
{
return $this->l11n instanceof BaseStringL11n ? $this->l11n->content : $this->l11n;
}
/**
* Set value
*
* @param int|string|float $value Value
* @param int $datatype Datatype
*
* @return void
*
* @since 1.0.0
*/
public function setValue(mixed $value, int $datatype) : void
{
if ($datatype === AttributeValueType::_STRING) {
$this->valueStr = (string) $value;
} elseif ($datatype === AttributeValueType::_INT
|| $datatype === AttributeValueType::_FLOAT_INT
|| $datatype === AttributeValueType::_BOOL
) {
$this->valueInt = (int) $value;
} elseif ($datatype === AttributeValueType::_FLOAT) {
$this->valueDec = (float) $value;
} elseif ($datatype === AttributeValueType::_DATETIME) {
$this->valueDat = new \DateTime((string) $value);
}
}
/**
* Get value
*
* @return null|int|string|float|\DateTimeInterface
*
* @since 1.0.0
*/
public function getValue() : mixed
{
if (!empty($this->valueStr)) {
return $this->valueStr;
} elseif (!empty($this->valueInt)) {
return $this->valueInt;
} elseif (!empty($this->valueDec)) {
return $this->valueDec;
} elseif ($this->valueDat instanceof \DateTimeInterface) {
return $this->valueDat;
}
return null;
}
/**
* {@inheritdoc}
*/
public function toArray() : array
{
return [
'id' => $this->id,
'valueInt' => $this->valueInt,
'valueStr' => $this->valueStr,
'valueDec' => $this->valueDec,
'valueDat' => $this->valueDat,
'isDefault' => $this->isDefault,
];
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return $this->toArray();
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
use phpOMS\Localization\BaseStringL11n;
/**
* Bill mapper class.
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class BillAttributeValueL11nMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'billing_attr_value_l11n_id' => ['name' => 'billing_attr_value_l11n_id', 'type' => 'int', 'internal' => 'id'],
'billing_attr_value_l11n_title' => ['name' => 'billing_attr_value_l11n_title', 'type' => 'string', 'internal' => 'content', 'autocomplete' => true],
'billing_attr_value_l11n_value' => ['name' => 'billing_attr_value_l11n_value', 'type' => 'int', 'internal' => 'ref'],
'billing_attr_value_l11n_lang' => ['name' => 'billing_attr_value_l11n_lang', 'type' => 'string', 'internal' => 'language'],
];
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'billing_attr_value_l11n';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD = 'billing_attr_value_l11n_id';
/**
* Model to use by the mapper.
*
* @var class-string
* @since 1.0.0
*/
public const MODEL = BaseStringL11n::class;
}

View File

@ -0,0 +1,77 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
/**
* Bill mapper class.
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class BillAttributeValueMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'billing_attr_value_id' => ['name' => 'billing_attr_value_id', 'type' => 'int', 'internal' => 'id'],
'billing_attr_value_default' => ['name' => 'billing_attr_value_default', 'type' => 'bool', 'internal' => 'isDefault'],
'billing_attr_value_valueStr' => ['name' => 'billing_attr_value_valueStr', 'type' => 'string', 'internal' => 'valueStr'],
'billing_attr_value_valueInt' => ['name' => 'billing_attr_value_valueInt', 'type' => 'int', 'internal' => 'valueInt'],
'billing_attr_value_valueDec' => ['name' => 'billing_attr_value_valueDec', 'type' => 'float', 'internal' => 'valueDec'],
'billing_attr_value_valueDat' => ['name' => 'billing_attr_value_valueDat', 'type' => 'DateTime', 'internal' => 'valueDat'],
'billing_attr_value_unit' => ['name' => 'billing_attr_value_unit', 'type' => 'string', 'internal' => 'unit'],
'billing_attr_value_deptype' => ['name' => 'billing_attr_value_deptype', 'type' => 'int', 'internal' => 'dependingAttributeType'],
'billing_attr_value_depvalue' => ['name' => 'billing_attr_value_depvalue', 'type' => 'int', 'internal' => 'dependingAttributeValue'],
];
/**
* Has many relation.
*
* @var array<string, array{mapper:class-string, table:string, self?:?string, external?:?string, column?:string}>
* @since 1.0.0
*/
public const HAS_MANY = [
'l11n' => [
'mapper' => BillAttributeValueL11nMapper::class,
'table' => 'billing_attr_value_l11n',
'self' => 'billing_attr_value_l11n_value',
'external' => null,
],
];
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'billing_attr_value';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD = 'billing_attr_value_id';
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
/**
* Null model
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class NullBillAttribute extends BillAttribute
{
/**
* Constructor
*
* @param int $id Model id
*
* @since 1.0.0
*/
public function __construct(int $id = 0)
{
parent::__construct();
$this->id = $id;
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return ['id' => $this->id];
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
/**
* Null model
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class NullBillAttributeType extends BillAttributeType
{
/**
* Constructor
*
* @param int $id Model id
*
* @since 1.0.0
*/
public function __construct(int $id = 0)
{
$this->id = $id;
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return ['id' => $this->id];
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Attribute
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Attribute;
/**
* Null model
*
* @package Modules\Billing\Models\Attribute
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class NullBillAttributeValue extends BillAttributeValue
{
/**
* Constructor
*
* @param int $id Model id
*
* @since 1.0.0
*/
public function __construct(int $id = 0)
{
$this->id = $id;
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return ['id' => $this->id];
}
}

View File

@ -16,13 +16,13 @@ namespace Modules\Billing\Models;
use Modules\Admin\Models\Account;
use Modules\Admin\Models\NullAccount;
use Modules\Billing\Models\Attribute\BillAttribute;
use Modules\ClientManagement\Models\Client;
use Modules\Editor\Models\EditorDoc;
use Modules\Media\Models\Collection;
use Modules\Media\Models\Media;
use Modules\Media\Models\NullMedia;
use Modules\SupplierManagement\Models\Supplier;
use phpOMS\Localization\ISO3166TwoEnum;
use phpOMS\Localization\ISO4217CharEnum;
use phpOMS\Localization\ISO639x1Enum;
use phpOMS\Localization\Money;
@ -89,13 +89,21 @@ class Bill implements \JsonSerializable
*/
public \DateTimeImmutable $createdAt;
/**
* Bill created at.
*
* @var null|\DateTime
* @since 1.0.0
*/
public ?\DateTime $billDate = null;
/**
* Bill created at.
*
* @var \DateTime
* @since 1.0.0
*/
public \DateTime $performanceDate;
public ?\DateTime $performanceDate = null;
/**
* Bill send at.
@ -435,6 +443,14 @@ class Bill implements \JsonSerializable
*/
protected array $media = [];
/**
* Attributes.
*
* @var BillAttribute[]
* @since 1.0.0
*/
private array $attributes = [];
/**
* Constructor.
*
@ -452,7 +468,6 @@ class Bill implements \JsonSerializable
$this->grossDiscount = new Money(0);
$this->createdAt = new \DateTimeImmutable();
$this->performanceDate = new \DateTime();
$this->createdBy = new NullAccount();
$this->referral = new NullAccount();
$this->type = new NullBillType();
@ -514,6 +529,52 @@ class Bill implements \JsonSerializable
return $this->number;
}
/**
* Add attribute to client
*
* @param BillAttribute $attribute Attribute
*
* @return void
*
* @since 1.0.0
*/
public function addAttribute(BillAttribute $attribute) : void
{
$this->attributes[] = $attribute;
}
/**
* Get attributes
*
* @return BillAttribute[]
*
* @since 1.0.0
*/
public function getAttributes() : array
{
return $this->attributes;
}
/**
* Get attribute
*
* @param string $attrName Attribute name
*
* @return null|BillAttribute
*
* @since 1.0.0
*/
public function getAttribute(string $attrName) : ?BillAttribute
{
foreach ($this->attributes as $attribute) {
if ($attribute->type->name === $attrName) {
return $attribute->value;
}
}
return null;
}
/**
* Get status
*
@ -759,7 +820,7 @@ class Bill implements \JsonSerializable
public function getFileByType(int $type) : Media
{
foreach ($this->media as $file) {
if ($file->type->getId() === $type) {
if ($file->hasMediaTypeId($type)) {
return $file;
}
}

View File

@ -15,6 +15,7 @@ declare(strict_types=1);
namespace Modules\Billing\Models;
use Modules\Admin\Models\AccountMapper;
use Modules\Billing\Models\Attribute\BillAttributeMapper;
use Modules\ClientManagement\Models\ClientMapper;
use Modules\Editor\Models\EditorDocMapper;
use Modules\Media\Models\CollectionMapper;
@ -82,6 +83,7 @@ class BillMapper extends DataMapperFactory
'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],
'billing_bill_date' => ['name' => 'billing_bill_date', 'type' => 'DateTime', 'internal' => 'billDate'],
'billing_bill_performance_date' => ['name' => 'billing_bill_performance_date', 'type' => 'DateTime', 'internal' => 'performanceDate', 'readonly' => true],
'billing_bill_created_at' => ['name' => 'billing_bill_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true],
];
@ -153,6 +155,13 @@ class BillMapper extends DataMapperFactory
'mapper' => SupplierMapper::class,
'external' => 'billing_bill_supplier',
],
'attributes' => [
'mapper' => BillAttributeMapper::class,
'table' => 'billing_bill_attr',
'self' => 'billing_bill_attr_bill',
'conditional' => true,
'external' => null,
],
];
/**
@ -161,7 +170,7 @@ class BillMapper extends DataMapperFactory
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD ='billing_bill_id';
public const PRIMARYFIELD = 'billing_bill_id';
/**
* Primary table.

View File

@ -54,7 +54,7 @@ final class BillTypeL11nMapper extends DataMapperFactory
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD ='billing_type_l11n_id';
public const PRIMARYFIELD = 'billing_type_l11n_id';
/**
* Model to use by the mapper.

View File

@ -116,5 +116,5 @@ final class BillTypeMapper extends DataMapperFactory
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD ='billing_type_id';
public const PRIMARYFIELD = 'billing_type_id';
}

0
Models/NullBill.php Executable file → Normal file
View File

View File

@ -0,0 +1,47 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Price
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Price;
/**
* Null bill type class.
*
* @package Modules\Billing\Models\Price
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class NullPrice extends Price
{
/**
* Constructor
*
* @param int $id Model id
*
* @since 1.0.0
*/
public function __construct(int $id = 0)
{
$this->id = $id;
parent::__construct();
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return ['id' => $this->id];
}
}

144
Models/Price/Price.php Normal file
View File

@ -0,0 +1,144 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Price
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Price;
use Modules\ClientManagement\Models\Client;
use Modules\ClientManagement\Models\ClientAttributeValue;
use Modules\ClientManagement\Models\NullClient;
use Modules\ClientManagement\Models\NullClientAttributeValue;
use Modules\ItemManagement\Models\Item;
use Modules\ItemManagement\Models\ItemAttributeValue;
use Modules\ItemManagement\Models\NullItem;
use Modules\ItemManagement\Models\NullItemAttributeValue;
use Modules\SupplierManagement\Models\NullSupplier;
use Modules\SupplierManagement\Models\Supplier;
use phpOMS\Localization\ISO4217CharEnum;
/**
* Bill class.
*
* @package Modules\Billing\Models\Price
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
class Price implements \JsonSerializable
{
/**
* ID.
*
* @var int
* @since 1.0.0
*/
protected int $id = 0;
public string $name = '';
public string $promocode = '';
public Item $item;
public ItemAttributeValue $itemgroup;
public ItemAttributeValue $itemsegment;
public ItemAttributeValue $itemsection;
public ItemAttributeValue $itemtype;
public Client $client;
public ClientAttributeValue $clientgroup;
public ClientAttributeValue $clientsegment;
public ClientAttributeValue $clientsection;
public ClientAttributeValue $clienttype;
public ?string $clientcountry = null;
public Supplier $supplier;
public int $unit = 0;
public int $type = PriceType::SALES;
public int $quantity = 0;
public int $price = 0;
public int $priceNew = 0;
public int $discount = 0;
public int $discountPercentage = 0;
public int $bonus = 0;
public bool $multiply = false;
public string $currency = ISO4217CharEnum::_EUR;
public ?\DateTime $start = null;
public ?\DateTime $end = null;
public function __construct()
{
$this->item = new NullItem();
$this->itemgroup = new NullItemAttributeValue();
$this->itemsegment = new NullItemAttributeValue();
$this->itemsection = new NullItemAttributeValue();
$this->itemtype = new NullItemAttributeValue();
$this->client = new NullClient();
$this->clientgroup = new NullClientAttributeValue();
$this->clientsegment = new NullClientAttributeValue();
$this->clientsection = new NullClientAttributeValue();
$this->clienttype = new NullClientAttributeValue();
$this->supplier = new NullSupplier();
}
/**
* Get id.
*
* @return int Model id
*
* @since 1.0.0
*/
public function getId() : int
{
return $this->id;
}
/**
* {@inheritdoc}
*/
public function toArray() : array
{
return [
'id' => $this->id,
];
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return $this->toArray();
}
}

View File

@ -0,0 +1,179 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Price
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Price;
use Modules\ClientManagement\Models\ClientAttributeValueMapper;
use Modules\ClientManagement\Models\ClientMapper;
use Modules\ItemManagement\Models\ItemAttributeValueMapper;
use Modules\ItemManagement\Models\ItemMapper;
use Modules\SupplierManagement\Models\SupplierMapper;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
use phpOMS\Localization\Defaults\CountryMapper;
/**
* Billing mapper class.
*
* @package Modules\Billing\Models\Price
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class PriceMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'billing_price_id' => ['name' => 'billing_price_id', 'type' => 'int', 'internal' => 'id'],
'billing_price_name' => ['name' => 'billing_price_name', 'type' => 'string', 'internal' => 'name'],
'billing_price_promocode' => ['name' => 'billing_price_promocode', 'type' => 'string', 'internal' => 'promocode'],
'billing_price_item' => ['name' => 'billing_price_item', 'type' => 'int', 'internal' => 'item'],
'billing_price_itemgroup' => ['name' => 'billing_price_itemgroup', 'type' => 'int', 'internal' => 'itemgroup'],
'billing_price_itemsegment' => ['name' => 'billing_price_itemsegment', 'type' => 'int', 'internal' => 'itemsegment'],
'billing_price_itemsection' => ['name' => 'billing_price_itemsection', 'type' => 'int', 'internal' => 'itemsection'],
'billing_price_itemtype' => ['name' => 'billing_price_itemtype', 'type' => 'int', 'internal' => 'itemtype'],
'billing_price_client' => ['name' => 'billing_price_client', 'type' => 'int', 'internal' => 'client'],
'billing_price_clientgroup' => ['name' => 'billing_price_clientgroup', 'type' => 'int', 'internal' => 'clientgroup'],
'billing_price_clientsegment' => ['name' => 'billing_price_clientsegment', 'type' => 'int', 'internal' => 'clientsegment'],
'billing_price_clientsection' => ['name' => 'billing_price_clientsection', 'type' => 'int', 'internal' => 'clientsection'],
'billing_price_clienttype' => ['name' => 'billing_price_clienttype', 'type' => 'int', 'internal' => 'clienttype'],
'billing_price_clientcountry' => ['name' => 'billing_price_clientcountry', 'type' => 'string', 'internal' => 'clientcountry'],
'billing_price_supplier' => ['name' => 'billing_price_supplier', 'type' => 'int', 'internal' => 'supplier'],
'billing_price_unit' => ['name' => 'billing_price_unit', 'type' => 'int', 'internal' => 'unit'],
'billing_price_type' => ['name' => 'billing_price_type', 'type' => 'int', 'internal' => 'type'],
'billing_price_quantity' => ['name' => 'billing_price_quantity', 'type' => 'int', 'internal' => 'quantity'],
'billing_price_price' => ['name' => 'billing_price_price', 'type' => 'int', 'internal' => 'price'],
'billing_price_price_new' => ['name' => 'billing_price_price_new', 'type' => 'int', 'internal' => 'priceNew'],
'billing_price_discount' => ['name' => 'billing_price_discount', 'type' => 'int', 'internal' => 'discount'],
'billing_price_discountp' => ['name' => 'billing_price_discountp', 'type' => 'int', 'internal' => 'discountPercentage'],
'billing_price_bonus' => ['name' => 'billing_price_bonus', 'type' => 'int', 'internal' => 'bonus'],
'billing_price_multiply' => ['name' => 'billing_price_multiply', 'type' => 'bool', 'internal' => 'multiply'],
'billing_price_currency' => ['name' => 'billing_price_currency', 'type' => 'string', 'internal' => 'currency'],
'billing_price_start' => ['name' => 'billing_price_start', 'type' => 'DateTime', 'internal' => 'start'],
'billing_price_end' => ['name' => 'billing_price_end', 'type' => 'DateTime', 'internal' => 'end'],
];
/**
* Has one relation.
*
* @var array<string, array{mapper:class-string, external:string, by?:string, column?:string, conditional?:bool}>
* @since 1.0.0
*/
public const OWNS_ONE = [
'item' => [
'mapper' => ItemMapper::class,
'external' => 'billing_price_item',
],
'itemgroup' => [
'mapper' => ItemAttributeValueMapper::class,
'external' => 'billing_price_itemgroup',
],
'itemsegment' => [
'mapper' => ItemAttributeValueMapper::class,
'external' => 'billing_price_itemsegment',
],
'itemsection' => [
'mapper' => ItemAttributeValueMapper::class,
'external' => 'billing_price_itemsection',
],
'itemtype' => [
'mapper' => ItemAttributeValueMapper::class,
'external' => 'billing_price_itemtype',
],
'client' => [
'mapper' => ClientMapper::class,
'external' => 'billing_price_client',
],
'clientgroup' => [
'mapper' => ClientAttributeValueMapper::class,
'external' => 'billing_price_clientgroup',
],
'clientsegment' => [
'mapper' => ClientAttributeValueMapper::class,
'external' => 'billing_price_clientsegment',
],
'clientsection' => [
'mapper' => ClientAttributeValueMapper::class,
'external' => 'billing_price_clientsection',
],
'clienttype' => [
'mapper' => ClientAttributeValueMapper::class,
'external' => 'billing_price_clienttype',
],
'clientcountry' => [
'mapper' => CountryMapper::class,
'external' => 'billing_price_clientcountry',
'by' => 'code2',
'column' => 'code2',
'conditional' => true,
],
'supplier' => [
'mapper' => SupplierMapper::class,
'external' => 'billing_price_supplier',
],
];
/**
* Model to use by the mapper.
*
* @var class-string
* @since 1.0.0
*/
public const MODEL = Price::class;
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'billing_price';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD = 'billing_price_id';
public static function findClientPrice() : array
{
/*
select * from prices
where
(promoID = ? OR promoID = null)
AND (itemID = ? OR itemID = null)
AND (itemGroup = IN (?) OR itemGroup = null)
AND (itemSegment = ? OR itemSegment = null)
AND (itemSection = ? OR itemSection = null)
AND (productType = ? OR productType = null)
AND (customerID = ? OR customerID = null)
AND (customerGroup IN (?) OR customerGroup = null)
AND (customerCountry = IN (?) OR customerCountry = null)
AND (quantity < ? OR quantity = null)
AND (start <= ? OR start = null)
AND (end >= ? OR start = null)
AND (unit = ? OR unit = null)
*/
// @todo: allow nested where clause (already possible with the query builder, but not with the mappers)
return [];
}
}

View File

@ -4,7 +4,7 @@
*
* PHP Version 8.1
*
* @package Modules\Billing\Models
* @package Modules\Billing\Models\Price
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
@ -12,19 +12,19 @@
*/
declare(strict_types=1);
namespace Modules\Billing\Models;
namespace Modules\Billing\Models\Price;
use phpOMS\Stdlib\Base\Enum;
/**
* Module settings enum.
*
* @package Modules\Billing\Models
* @package Modules\Billing\Models\Price
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
abstract class PricingType extends Enum
abstract class PriceType extends Enum
{
public const SALES = 1;

View File

@ -1,94 +0,0 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
use phpOMS\DataStorage\Database\Query\Where;
/**
* Billing mapper class.
*
* @package Modules\Billing\Models
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class PricingMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'billing_type_id' => ['name' => 'billing_type_id', 'type' => 'int', 'internal' => 'id'],
'billing_type_name' => ['name' => 'billing_type_name', 'type' => 'string', 'internal' => 'name'],
'billing_type_number_format' => ['name' => 'billing_type_number_format', 'type' => 'string', 'internal' => 'numberFormat'],
'billing_type_transfer_type' => ['name' => 'billing_type_transfer_type', 'type' => 'int', 'internal' => 'transferType'],
'billing_type_default_template' => ['name' => 'billing_type_default_template', 'type' => 'int', 'internal' => 'defaultTemplate'],
'billing_type_transfer_stock' => ['name' => 'billing_type_transfer_stock', 'type' => 'bool', 'internal' => 'transferStock'],
'billing_type_is_template' => ['name' => 'billing_type_is_template', 'type' => 'bool', 'internal' => 'isTemplate'],
];
/**
* Model to use by the mapper.
*
* @var class-string
* @since 1.0.0
*/
public const MODEL = Pricing::class;
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'billing_price';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD ='billing_price_id';
public static function findClientPrice() : array
{
/*
select * from prices
where
(promoID = ? OR promoID = null)
AND (itemID = ? OR itemID = null)
AND (itemGroup = IN (?) OR itemGroup = null)
AND (itemSegment = ? OR itemSegment = null)
AND (itemSection = ? OR itemSection = null)
AND (productType = ? OR productType = null)
AND (customerID = ? OR customerID = null)
AND (customerGroup IN (?) OR customerGroup = null)
AND (customerCountry = IN (?) OR customerCountry = null)
AND (quantity < ? OR quantity = null)
AND (start <= ? OR start = null)
AND (end >= ? OR start = null)
AND (unit = ? OR unit = null)
*/
// @todo: allow nested where clause (already possible with the query builder, but not with the mappers)
return [];
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Tax
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Tax;
use phpOMS\Stdlib\Base\Enum;
/**
* Bill transfer type enum.
*
* @package Modules\Billing\Models\Tax
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
abstract class BillTaxType extends Enum
{
public const SALES = 1;
public const PURCHASE = 2;
}

View File

@ -0,0 +1,46 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Tax
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Tax;
/**
* Null model
*
* @package Modules\Billing\Models\Tax
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class NullTaxCombination extends TaxCombination
{
/**
* Constructor
*
* @param int $id Model id
*
* @since 1.0.0
*/
public function __construct(int $id = 0)
{
$this->id = $id;
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return ['id' => $this->id];
}
}

View File

@ -0,0 +1,100 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Tax
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Tax;
use Modules\ClientManagement\Models\ClientAttributeValue;
use Modules\ClientManagement\Models\NullClientAttributeValue;
use Modules\ItemManagement\Models\ItemAttributeValue;
use Modules\ItemManagement\Models\NullItemAttributeValue;
use Modules\SupplierManagement\Models\NullSupplierAttributeValue;
use Modules\SupplierManagement\Models\SupplierAttributeValue;
/**
* Billing class.
*
* @package Modules\Billing\Models\Tax
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
class TaxCombination implements \JsonSerializable
{
/**
* Article ID.
*
* @var int
* @since 1.0.0
*/
protected int $id = 0;
public ?ClientAttributeValue $clientCode = null;
public ?SupplierAttributeValue $supplierCode = null;
public ItemAttributeValue $itemCode;
public string $taxCode = '';
public int $taxType = BillTaxType::SALES;
public string $account = '';
public string $refundAccount = '';
public string $discountAccount = '';
public ?int $minPrice = null;
public ?int $maxPrice = null;
public ?\DateTime $start = null;
public ?\DateTime $end = null;
public function __construct()
{
$this->itemCode = new NullItemAttributeValue();
}
/**
* Get id
*
* @return int
*
* @since 1.0.0
*/
public function getId() : int
{
return $this->id;
}
/**
* {@inheritdoc}
*/
public function toArray() : array
{
return [
'id' => $this->id,
];
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return $this->toArray();
}
}

View File

@ -0,0 +1,98 @@
<?php
/**
* Karaka
*
* PHP Version 8.1
*
* @package Modules\Billing\Models\Tax
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models\Tax;
use Modules\ClientManagement\Models\ClientAttributeValueMapper;
use Modules\ItemManagement\Models\ItemAttributeValueMapper;
use Modules\SupplierManagement\Models\SupplierAttributeValueMapper;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
/**
* Billing mapper class.
*
* @package Modules\Billing\Models\Tax
* @license OMS License 1.0
* @link https://jingga.app
* @since 1.0.0
*/
final class TaxCombinationMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'billing_tax_id' => ['name' => 'billing_tax_id', 'type' => 'int', 'internal' => 'id'],
'billing_tax_client_code' => ['name' => 'billing_tax_client_code', 'type' => 'int', 'internal' => 'clientCode'],
'billing_tax_supplier_code' => ['name' => 'billing_tax_supplier_code', 'type' => 'int', 'internal' => 'supplierCode'],
'billing_tax_item_code' => ['name' => 'billing_tax_item_code', 'type' => 'int', 'internal' => 'itemCode'],
'billing_tax_code' => ['name' => 'billing_tax_code', 'type' => 'string', 'internal' => 'taxCode'],
'billing_tax_type' => ['name' => 'billing_tax_type', 'type' => 'int', 'internal' => 'taxType'],
'billing_tax_account' => ['name' => 'billing_tax_account', 'type' => 'string', 'internal' => 'account'],
'billing_tax_refund_account' => ['name' => 'billing_tax_refund_account', 'type' => 'string', 'internal' => 'refundAccount'],
'billing_tax_discount_account' => ['name' => 'billing_tax_discount_account', 'type' => 'string', 'internal' => 'discountAccount'],
'billing_tax_min_price' => ['name' => 'billing_tax_min_price', 'type' => 'int', 'internal' => 'minPrice'],
'billing_tax_max_price' => ['name' => 'billing_tax_max_price', 'type' => 'int', 'internal' => 'maxPrice'],
'billing_tax_start' => ['name' => 'billing_tax_start', 'type' => 'DateTime', 'internal' => 'start'],
'billing_tax_end' => ['name' => 'billing_tax_end', 'type' => 'DateTime', 'internal' => 'end'],
];
/**
* Has one relation.
*
* @var array<string, array{mapper:class-string, external:string, by?:string, column?:string, conditional?:bool}>
* @since 1.0.0
*/
public const OWNS_ONE = [
'clientCode' => [
'mapper' => ClientAttributeValueMapper::class,
'external' => 'billing_tax_client_code',
],
'supplierCode' => [
'mapper' => SupplierAttributeValueMapper::class,
'external' => 'billing_tax_supplier_code',
],
'itemCode' => [
'mapper' => ItemAttributeValueMapper::class,
'external' => 'billing_tax_item_code',
],
];
/**
* Model to use by the mapper.
*
* @var class-string
* @since 1.0.0
*/
public const MODEL = TaxCombination::class;
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'billing_tax';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD = 'billing_tax_id';
}

106
Models/billIdentifier.json Normal file
View File

@ -0,0 +1,106 @@
{
"type": {
"purchase_invoice": {
"en": [
"Invoice"
],
"de": [
"Rechnung"
]
},
"purchase_credit_note": {
"en": [
"Credit Note"
],
"de": [
"Rechnungskorrektur",
"Gutschrift"
]
},
"purchase_delivery_note": {
"en": [
"Delivery Note"
],
"de": [
"Lieferschein"
]
},
"purchase_order_confirmation": {
"en": [
"Order Confirmation"
],
"de": [
"Auftragsbestätigung"
]
},
"purchase_reverse_invoice": {
"en": [
"Credit Note"
],
"de": [
"Gutschrift"
]
}
},
"date_format": [
"Y-m-d",
"Y.m.d",
"Y/m/d",
"d-m-Y",
"d.m.Y",
"d/m/Y",
"m-d-Y",
"m.d.Y",
"m/d/Y",
"M. d.Y",
"M. d. Y",
"M. d Y",
"M. d,Y",
"M. d, Y",
"M d.Y",
"M d. Y",
"M d Y",
"M d,Y",
"M d, Y",
"M, d.Y",
"M, d. Y",
"M, d Y",
"M, d,Y",
"M, d, Y"
],
"bill_no": {
"en": [
"/(inv.*?)(no|\\s|,|:|\\.|#)+(?<bill_no>.*?)( |$)/i",
"/(#)(?<bill_no>.*?)( |$)/i"
],
"de": [
"/(rechnungsn.*?|beleg.*?)(?<bill_no>.*?)( |$)/i"
]
},
"bill_date": {
"en": [
"/(inv.*?)(date.*?)(\\s|,|:|\\.)+(?<bill_date>.*?)( |$)/i",
"/(date.*?)(\\s|,|:|\\.)+(?<bill_date>.*?)( |$)/i"
],
"de": [
"/(rechnungsdat.*?|belegdat.*?)(\\s|,|:|\\.)+(?<bill_date>.*?)( |$)/i"
]
},
"bill_due": {
"en": [
"/(due date.*?)(\\s|,|:|\\.)+(?<bill_due>.*?)( |$)/i",
"/(due.*?)(\\s|,|:|\\.)+(?<bill_due>.*?)( |$)/i"
],
"de": [
"/(fällig.*?)(\\s|,|:|\\.)+(?<bill_due>.*?)( |$)/i"
]
},
"total_gross": {
"en": [
"/(total.*?|gross.*?)(?<total_gross>([0-9]+,*\\.*)+)/i"
],
"de": [
"/(betrag.*?|gesamt.*?|brutto.*?)(?<total_gross>([0-9]+,*\\.*)+)/i"
]
}
}

View File

@ -17,4 +17,5 @@ return ['Navigation' => [
'Archive' => 'Archive',
'Bill' => 'Bill',
'Billing' => 'Billing',
'Upload' => 'Upload',
]];

View File

@ -89,6 +89,7 @@ return ['Billing' => [
'Type' => 'Type',
'Types' => 'Types',
'Upload' => 'Upload',
'Original' => 'Original',
'Value' => 'Value',
'Variation' => 'Variation',
'Zip' => 'Zip',

View File

@ -12,6 +12,8 @@
*/
declare(strict_types=1);
use phpOMS\Localization\ISO3166NameEnum;
use phpOMS\Localization\ISO3166TwoEnum;
use phpOMS\Uri\UriFactory;
$bills = $this->getData('bills') ?? [];
@ -126,7 +128,7 @@ echo $this->getData('nav')->render(); ?>
<label>
<i class="filter fa fa-filter"></i>
</label>
<td><?= $this->getHtml('Net'); ?>
<td><?= $this->getHtml('Gross'); ?>
<label for="billList-sort-7">
<input type="radio" name="billList-sort" id="billList-sort-7">
<i class="sort-asc fa fa-chevron-up"></i>
@ -138,7 +140,7 @@ echo $this->getData('nav')->render(); ?>
<label>
<i class="filter fa fa-filter"></i>
</label>
<td><?= $this->getHtml('Created'); ?>
<td><?= $this->getHtml('Date'); ?>
<label for="billList-sort-23">
<input type="radio" name="billList-sort" id="billList-sort-23">
<i class="sort-asc fa fa-chevron-up"></i>
@ -169,9 +171,13 @@ echo $this->getData('nav')->render(); ?>
?>"><?= $value->billAddress; ?></a>
<td><a href="<?= $url; ?>"><?= $value->billZip; ?></a>
<td><a href="<?= $url; ?>"><?= $value->billCity; ?></a>
<td><a href="<?= $url; ?>"><?= $value->billCountry; ?></a>
<td><a href="<?= $url; ?>"><?= $value->netSales->getCurrency(); ?></a>
<td><a href="<?= $url; ?>"><?= $value->createdAt->format('Y-m-d'); ?></a>
<td><a href="<?= $url; ?>"><?= !empty($value->billCountry)
? ISO3166NameEnum::getByName(
ISO3166TwoEnum::getName($value->billCountry)
)
: ''; ?></a>
<td><a href="<?= $url; ?>"><?= $value->grossCosts->getAmount(); ?></a>
<td><a href="<?= $url; ?>"><?= $value->billDate?->format('Y-m-d'); ?></a>
<?php endforeach; ?>
<?php if ($count === 0) : ?>
<tr><td colspan="12" class="empty"><?= $this->getHtml('Empty', '0', '0'); ?>

View File

View File

@ -0,0 +1,3 @@
<?php
echo \json_encode($this->getData('bill') ?? null, \JSON_PRETTY_PRINT);

View File

@ -1,6 +1,6 @@
{
"name": "karaka/module",
"description": "Module for Karaka.",
"description": "Module for Jingga.",
"authors": [
{
"name": "Dennis Eichhorn",

View File

@ -11,7 +11,7 @@
"phpOMS-db": "1.0.0"
},
"creator": {
"name": "Karaka",
"name": "Jingga",
"website": "jingga.app"
},
"description": "Accounting module.",