mirror of
https://github.com/Karaka-Management/oms-Billing.git
synced 2026-01-11 15:18:42 +00:00
code fixes
This commit is contained in:
parent
3cd6a79222
commit
e70898dbd5
|
|
@ -145,7 +145,7 @@ $pdf->MultiCell(
|
|||
);
|
||||
$pdf->Ln();
|
||||
|
||||
$tempY = $pdf->getY();
|
||||
$tempY = $pdf->getY();
|
||||
$height = 0;
|
||||
$pdf->setY($tempY - 20);
|
||||
|
||||
|
|
@ -211,7 +211,7 @@ foreach($bill->elements as $line) {
|
|||
if ($line->quantity->value === 0) {
|
||||
$pdf->MultiCell($w[1] + $w[2] + $w[3], $height, '', 0, 'L', $fill, 0, 15 + $w[0], $tempY, true, 0, false, true, 0, 'M', true);
|
||||
} else {
|
||||
$pdf->MultiCell($w[1], $height, (string) $line->quantity->getAmount($line->container->quantityDecimals), 0, 'L', $fill, 0, 15 + $w[0], $tempY, true, 0, false, true, 0, 'M', true);
|
||||
$pdf->MultiCell($w[1], $height, (string) $line->quantity->getAmount($line->container?->quantityDecimals ?? 0), 0, 'L', $fill, 0, 15 + $w[0], $tempY, true, 0, false, true, 0, 'M', true);
|
||||
$pdf->MultiCell($w[2], $height, $singleListPriceNet->getCurrency(2, symbol: ''), 0, 'L', $fill, 0, 15 + $w[0] + $w[1], $tempY, true, 0, false, true, 0, 'M', true);
|
||||
$pdf->MultiCell($w[3], $height, $totalSalesPriceNet->getCurrency(2, symbol: ''), 0, 'L', $fill, 1, 15 + $w[0] + $w[1] + $w[2], $tempY, true, 0, false, true, 0, 'M', true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,184 @@
|
|||
[
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "EU",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "AT",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "BE",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "BG",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "HR",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "CY",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "CZ",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "DK",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "EE",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "FI",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "FR",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "DE",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "GR",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "HU",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "IE",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "IT",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "LV",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "LT",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "LU",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "MT",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "NL",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "PL",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "PT",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "RO",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "SK",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "SI",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "ES",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "SE",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "GB",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "INT",
|
||||
"tax_code": "SBIZ_0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "SOFTWARE",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,207 @@
|
|||
[
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "EU",
|
||||
"tax_code": "EU_S0",
|
||||
"account": "8400"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "AT",
|
||||
"tax_code": "AT_S20"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "BE",
|
||||
"tax_code": "BE_S21"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "BG",
|
||||
"tax_code": "BG_S20"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "HR",
|
||||
"tax_code": "HR_S25"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "CY",
|
||||
"tax_code": "CY_S19"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "CZ",
|
||||
"tax_code": "CZ_S21"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "DK",
|
||||
"tax_code": "DK_S25"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "EE",
|
||||
"tax_code": "EE_S20"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "FI",
|
||||
"tax_code": "FI_S24"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "FR",
|
||||
"tax_code": "FR_S20"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "DE",
|
||||
"tax_code": "DE_M19",
|
||||
"account": "8400"
|
||||
},
|
||||
{
|
||||
"type": 2,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "DE",
|
||||
"tax_code": "DE_V19",
|
||||
"account": "3400"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "DE_0",
|
||||
"tax_code": "SBIZ_0",
|
||||
"account": "8195"
|
||||
},
|
||||
{
|
||||
"type": 2,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "DE_0",
|
||||
"tax_code": "SBIZ_0",
|
||||
"account": "3349"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "GR",
|
||||
"tax_code": "GR_S24"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "HU",
|
||||
"tax_code": "HU_S27"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "IE",
|
||||
"tax_code": "IE_S23"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "IT",
|
||||
"tax_code": "IT_S22"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "LV",
|
||||
"tax_code": "LV_S21"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "LT",
|
||||
"tax_code": "LT_S21"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "LU",
|
||||
"tax_code": "LU_S17"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "MT",
|
||||
"tax_code": "MT_S18"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "NL",
|
||||
"tax_code": "NL_S21"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "PL",
|
||||
"tax_code": "PL_S23"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "PT",
|
||||
"tax_code": "PT_S23"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "RO",
|
||||
"tax_code": "RO_S19"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "SK",
|
||||
"tax_code": "SK_S20"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "SI",
|
||||
"tax_code": "SI_S22"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "ES",
|
||||
"tax_code": "ES_S21"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "SE",
|
||||
"tax_code": "SE_S25"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "GB",
|
||||
"tax_code": "GB_S20"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "GENERAL",
|
||||
"account_code": "INT",
|
||||
"tax_code": "S0"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "SOFTWARE",
|
||||
|
|
@ -69,7 +272,29 @@
|
|||
"type": 1,
|
||||
"item_code": "SOFTWARE",
|
||||
"account_code": "DE",
|
||||
"tax_code": "DE_S19"
|
||||
"tax_code": "DE_M19",
|
||||
"account": "8400"
|
||||
},
|
||||
{
|
||||
"type": 2,
|
||||
"item_code": "SOFTWARE",
|
||||
"account_code": "DE",
|
||||
"tax_code": "DE_V19",
|
||||
"account": "3400"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "SOFTWARE",
|
||||
"account_code": "DE_0",
|
||||
"tax_code": "SBIZ_0",
|
||||
"account": "8195"
|
||||
},
|
||||
{
|
||||
"type": 2,
|
||||
"item_code": "SOFTWARE",
|
||||
"account_code": "DE_0",
|
||||
"tax_code": "SBIZ_0",
|
||||
"account": "3349"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
|
@ -249,7 +474,29 @@
|
|||
"type": 1,
|
||||
"item_code": "SERVICE",
|
||||
"account_code": "DE",
|
||||
"tax_code": "DE_S19"
|
||||
"tax_code": "DE_M19",
|
||||
"account": "8400"
|
||||
},
|
||||
{
|
||||
"type": 2,
|
||||
"item_code": "SERVICE",
|
||||
"account_code": "DE",
|
||||
"tax_code": "DE_V19",
|
||||
"account": "3400"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"item_code": "SERVICE",
|
||||
"account_code": "DE_0",
|
||||
"tax_code": "SBIZ_0",
|
||||
"account": "8195"
|
||||
},
|
||||
{
|
||||
"type": 2,
|
||||
"item_code": "SERVICE",
|
||||
"account_code": "DE_0",
|
||||
"tax_code": "SBIZ_0",
|
||||
"account": "3349"
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
|
|
|
|||
|
|
@ -1152,6 +1152,14 @@
|
|||
"null": true,
|
||||
"default": null
|
||||
},
|
||||
"billing_bill_element_tax_combination": {
|
||||
"name": "billing_bill_element_tax_combination",
|
||||
"type": "INT",
|
||||
"null": true,
|
||||
"default": null,
|
||||
"foreignTable": "billing_tax",
|
||||
"foreignKey": "billing_tax_id"
|
||||
},
|
||||
"billing_bill_element_tax_type": {
|
||||
"name": "billing_bill_element_tax_type",
|
||||
"type": "VARCHAR(10)",
|
||||
|
|
|
|||
|
|
@ -311,6 +311,7 @@ final class Installer extends InstallerAbstract
|
|||
$request->setData('tax_code', $tax['tax_code']);
|
||||
$request->setData('item_code', $itemValue->id);
|
||||
$request->setData('account_code', $accountValue->id);
|
||||
$request->setData('account', $tax['account'] ?? null);
|
||||
|
||||
$module->apiTaxCombinationCreate($request, $response);
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,17 @@ final class ApiBillController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
public function apiBillEmail(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
|
||||
/**
|
||||
* Create email for/from bill
|
||||
*
|
||||
* @param RequestAbstract $request Request
|
||||
* @param array $data Data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function apiBillEmail(RequestAbstract $request, array $data = []) : void
|
||||
{
|
||||
$bill = $data['bill'] ?? BillMapper::get()
|
||||
->with('type')
|
||||
|
|
@ -102,12 +112,12 @@ final class ApiBillController extends Controller
|
|||
->where('id', $request->getDataInt('bill') ?? 0)
|
||||
->execute();
|
||||
|
||||
$media = $data['media'] ?? $bill->getFileByTypeName('internal');;
|
||||
$media = $data['media'] ?? $bill->getFileByTypeName('internal');
|
||||
|
||||
if ($bill->status === BillStatus::ARCHIVED
|
||||
&& $bill->type->email
|
||||
) {
|
||||
$email = $request->getDataString('email');
|
||||
$email = $request->getDataString('email');
|
||||
$billingTemplate = null;
|
||||
|
||||
if (!empty($email)) {
|
||||
|
|
@ -134,7 +144,7 @@ final class ApiBillController extends Controller
|
|||
|
||||
if ($client->getAttribute('bill_emails')->value->getValue() === 1) {
|
||||
// @todo should this really be a string or an ID for a contact element?
|
||||
$email ??= empty($tmp = $client->getAttribute('bill_email_address')->value->getValue())
|
||||
$email ??= empty($tmp = $client->getAttribute('bill_email_address')->value->valueStr)
|
||||
? $client->account->email
|
||||
: (string) $tmp;
|
||||
}
|
||||
|
|
@ -156,18 +166,34 @@ final class ApiBillController extends Controller
|
|||
|
||||
if ($supplier->getAttribute('bill_emails')->value->getValue() === 1) {
|
||||
// @todo should this really be a string or an ID for a contact element?
|
||||
$email ??= empty($tmp = $supplier->getAttribute('bill_email_address')->value->getValue())
|
||||
$email ??= empty($tmp = $supplier->getAttribute('bill_email_address')->value->valueStr)
|
||||
? $supplier->account->email
|
||||
: (string) $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($email)) {
|
||||
if (!empty($email) && $billingTemplate !== null) {
|
||||
$this->sendBillEmail($media, $email, (int) $billingTemplate->content, $bill->language);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Api method to finalize a bill
|
||||
*
|
||||
* Finalization creates an archive and possibly sends the bill via email.
|
||||
* Additionally, it triggers the event Billing-bill-finalize event which also finalizes the stock changes and possibly accounting postings
|
||||
*
|
||||
* @param RequestAbstract $request Request
|
||||
* @param ResponseAbstract $response Response
|
||||
* @param array $data Generic data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function apiBillFinalize(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
|
||||
{
|
||||
if (!$this->app->accountManager->get($request->header->account)->hasPermission(
|
||||
|
|
@ -192,13 +218,13 @@ final class ApiBillController extends Controller
|
|||
}
|
||||
|
||||
// Archive bill
|
||||
/** @var \Modules\Billing\Models\Bill $bill */
|
||||
/** @var \Modules\Billing\Models\Bill $old */
|
||||
$old = BillMapper::get()
|
||||
->with('type')
|
||||
->where('id', $request->getDataInt('bill') ?? 0)
|
||||
->execute();
|
||||
|
||||
$new = clone $old;
|
||||
$new = clone $old;
|
||||
$new->status = BillStatus::ARCHIVED;
|
||||
|
||||
$this->updateModel($request->header->account, $old, $new, BillMapper::class, 'bill', $request->getOrigin());
|
||||
|
|
@ -218,7 +244,7 @@ final class ApiBillController extends Controller
|
|||
]);
|
||||
|
||||
// Send bill via email
|
||||
$this->apiBillEmail($request, $response, ['bill' => $new, 'media' => $media]);
|
||||
$this->apiBillEmail($request, ['bill' => $new, 'media' => $media]);
|
||||
|
||||
$this->createStandardUpdateResponse($request, $response, $new);
|
||||
}
|
||||
|
|
@ -440,6 +466,15 @@ final class ApiBillController extends Controller
|
|||
return $bill;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find bill language.
|
||||
*
|
||||
* @param Client|Supplier $account Account (with attributes!!!)
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function findBillLanguage(Client|Supplier $account) : string
|
||||
{
|
||||
/** @var \Model\Setting $settings */
|
||||
|
|
@ -516,10 +551,13 @@ final class ApiBillController extends Controller
|
|||
$attr->type = $attrType;
|
||||
$attr->value = $attrValue;
|
||||
|
||||
$container = $request->hasData('container') ? new NullContainer($request->getDataInt('container')) : null;
|
||||
$attr = new NullAttribute();
|
||||
$container = $request->hasData('container')
|
||||
? new NullContainer((int) $request->getData('container'))
|
||||
: null;
|
||||
|
||||
if ($bill->type->transferType === BillTransferType::PURCHASE) {
|
||||
$attr = new NullAttribute();
|
||||
|
||||
if ($bill->type->transferType === BillTransferType::PURCHASE && $bill->supplier !== null) {
|
||||
$bill->supplier->attributes[] = $attr;
|
||||
|
||||
if ($container === null) {
|
||||
|
|
@ -534,7 +572,7 @@ final class ApiBillController extends Controller
|
|||
->execute();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} elseif ($bill->client !== null) {
|
||||
$bill->client->attributes[] = $attr;
|
||||
|
||||
if ($container === null) {
|
||||
|
|
@ -552,7 +590,7 @@ final class ApiBillController extends Controller
|
|||
}
|
||||
|
||||
$container = $container === null && $attr->id !== 0
|
||||
? new NullContainer($attr->value->getValue())
|
||||
? new NullContainer($attr->value->valueInt ?? 0)
|
||||
: $container;
|
||||
|
||||
$taxCombination = $this->app->moduleManager->get('Billing', 'ApiTax')
|
||||
|
|
@ -561,8 +599,8 @@ final class ApiBillController extends Controller
|
|||
$element = BillElement::fromItem(
|
||||
$item,
|
||||
$taxCombination,
|
||||
FloatInt::toInt($request->getDataString('quantity') ?? '1'),
|
||||
$bill,
|
||||
FloatInt::toInt($request->getDataString('quantity') ?? '1'),
|
||||
$container
|
||||
);
|
||||
|
||||
|
|
@ -1278,7 +1316,7 @@ final class ApiBillController extends Controller
|
|||
->with('type')
|
||||
->with('type/l11n')
|
||||
->where('id', $request->getDataInt('bill') ?? 0)
|
||||
->where('type/l11n/language', new ColumnName(BillMapper::getColumnByMember('language')))
|
||||
->where('type/l11n/language', new ColumnName(BillMapper::getColumnByMember('language') ?? ''))
|
||||
->execute();
|
||||
|
||||
// Handle PDF generation
|
||||
|
|
@ -1444,7 +1482,7 @@ final class ApiBillController extends Controller
|
|||
|
||||
$media->setPath(\Modules\Media\Controller\ApiController::normalizeDbPath($pdfDir . '/' . $billFileName));
|
||||
$media->setVirtualPath($path);
|
||||
$media->size = \filesize($media->getAbsolutePath());
|
||||
$media->size = (int) \filesize($media->getAbsolutePath());
|
||||
|
||||
$this->updateModel($request->header->account, $oldFile, $media, MediaMapper::class, 'media', $request->getOrigin());
|
||||
}
|
||||
|
|
@ -1457,7 +1495,7 @@ final class ApiBillController extends Controller
|
|||
*
|
||||
* @param Media $media Media to send
|
||||
* @param string $email Email address
|
||||
* @param string $template Email template
|
||||
* @param int $template Email template
|
||||
* @param string $language Message language
|
||||
*
|
||||
* @return void
|
||||
|
|
|
|||
|
|
@ -46,15 +46,31 @@ use phpOMS\Stdlib\Base\FloatInt;
|
|||
*/
|
||||
final class ApiPriceController extends Controller
|
||||
{
|
||||
public function findBestPrice(RequestAbstract $request, ?Item $item = null, ?Client $client = null, ?Supplier $supplier = null)
|
||||
/**
|
||||
* Find the best price for a client/supplier and an item
|
||||
*
|
||||
* @param RequestAbstract $request Request
|
||||
* @param null|Item $item Item to find the price for (alternatively, price_item in request)
|
||||
* @param null|Client $client Client to find the price for (alternatively, client in request)
|
||||
* @param null|Supplier $supplier Supplier to find the price for (alternatively, supplier in request)
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function findBestPrice(
|
||||
RequestAbstract $request,
|
||||
?Item $item = null,
|
||||
?Client $client = null, ?Supplier $supplier = null
|
||||
) : array
|
||||
{
|
||||
$item ??= new NullItem();
|
||||
$client ??= new NullClient();
|
||||
$item ??= new NullItem();
|
||||
$client ??= new NullClient();
|
||||
$supplier ??= new NullSupplier();
|
||||
|
||||
// Get item
|
||||
if ($item->id === 0 && $request->hasData('price_item')) {
|
||||
/** @var null|\Modules\ItemManagement\Models\Item $item */
|
||||
/** @var \Modules\ItemManagement\Models\Item $item */
|
||||
$item = ItemMapper::get()
|
||||
->with('attributes')
|
||||
->with('attributes/type')
|
||||
|
|
@ -84,7 +100,7 @@ final class ApiPriceController extends Controller
|
|||
->execute();
|
||||
}
|
||||
|
||||
$quantity = new FloatInt($request->getDataString('price_quantity') ?? FloatInt::DIVISOR);
|
||||
$quantity = new FloatInt($request->getDataString('price_quantity') ?? FloatInt::DIVISOR);
|
||||
$quantity->value = $quantity->value === 0 ? FloatInt::DIVISOR : $quantity->value;
|
||||
|
||||
// Get all relevant prices
|
||||
|
|
@ -442,7 +458,7 @@ final class ApiPriceController extends Controller
|
|||
&& $old->priceNew->value !== $new->priceNew->value
|
||||
) {
|
||||
/** @var \Modules\ItemManagement\Models\Item $item */
|
||||
$item = ItemMapper::get()->where('id', $new->item)->execute();
|
||||
$item = ItemMapper::get()->where('id', $new->item)->execute();
|
||||
$itemNew = clone $item;
|
||||
|
||||
if ($new->type === PriceType::SALES) {
|
||||
|
|
@ -492,14 +508,16 @@ final class ApiPriceController extends Controller
|
|||
|
||||
$new->supplier = $request->hasData('supplier') ? new NullSupplier((int) $request->getData('supplier')) : $new->supplier;
|
||||
$new->unit = $request->getDataInt('unit') ?? $new->unit;
|
||||
$new->quantity = $request->getDataInt('quantity') ?? $new->quantity;
|
||||
$new->price = $new->priceNew;
|
||||
$new->priceNew = $request->hasData('price_new') ? new FloatInt((int) $request->getData('price_new')) :
|
||||
$new->discount = $request->getDataInt('discount') ?? $new->discount;
|
||||
$new->discountPercentage = $request->getDataInt('discountPercentage') ?? $new->discountPercentage;
|
||||
$new->bonus = $request->getDataInt('bonus') ?? $new->bonus;
|
||||
|
||||
$new->quantity = new FloatInt($request->getDataString('quantity') ?? $new->quantity->value);
|
||||
$new->price = new FloatInt($request->getDataString('price') ?? $new->price->value);
|
||||
$new->priceNew = new FloatInt($request->getDataString('price_new') ?? $new->priceNew->value);
|
||||
$new->discount = new FloatInt($request->getDataString('discount') ?? $new->discount->value);
|
||||
$new->discountPercentage = new FloatInt($request->getDataString('discountPercentage') ?? $new->discountPercentage->value);
|
||||
$new->bonus = new FloatInt($request->getDataString('bonus') ?? $new->bonus->value);
|
||||
|
||||
$new->multiply = $request->getDataBool('multiply') ?? $new->multiply;
|
||||
$new->currency = $request->getDataString('currency') ?? $new->currency;
|
||||
$new->currency = ISO4217CharEnum::tryFromValue($request->getDataString('currency')) ?? $new->currency;
|
||||
$new->start = $request->getDataDateTime('start') ?? $new->start;
|
||||
$new->end = $request->getDataDateTime('end') ?? $new->end;
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,9 @@ final class ApiPurchaseController extends Controller
|
|||
/**
|
||||
* Method to create item attribute from request.
|
||||
*
|
||||
* @param RequestAbstract $request Request
|
||||
* @param RequestAbstract $request Request
|
||||
* @param ResponseAbstract $response Response
|
||||
* @param array $data Generic data
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
|
|
@ -123,7 +125,7 @@ final class ApiPurchaseController extends Controller
|
|||
|
||||
$this->app->moduleManager->get('Billing', 'ApiBill')->apiBillCreate($billRequest, $billResponse, $data);
|
||||
|
||||
$billId = $billResponse->getDataArray('')['response']->id;
|
||||
$billId = $billResponse->getDataArray('')['response']->id;
|
||||
$bills[] = $billId;
|
||||
|
||||
// Upload and assign document to bill
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@ namespace Modules\Billing\Controller;
|
|||
|
||||
use Modules\Attribute\Models\AttributeValue;
|
||||
use Modules\Attribute\Models\NullAttributeValue;
|
||||
use Modules\Billing\Models\Tax\NullTaxCombination;
|
||||
use Modules\Billing\Models\Tax\TaxCombination;
|
||||
use Modules\Billing\Models\Tax\TaxCombinationMapper;
|
||||
use Modules\ClientManagement\Models\Attribute\ClientAttributeTypeMapper;
|
||||
use Modules\ClientManagement\Models\Client;
|
||||
use Modules\Finance\Models\TaxCode;
|
||||
use Modules\Finance\Models\NullTaxCode;
|
||||
use Modules\Finance\Models\TaxCodeMapper;
|
||||
use Modules\ItemManagement\Models\Item;
|
||||
use Modules\Organization\Models\UnitMapper;
|
||||
|
|
@ -46,16 +47,26 @@ final class ApiTaxController extends Controller
|
|||
/**
|
||||
* Get tax code from client and item.
|
||||
*
|
||||
* @param Item $item Item to get tax code from
|
||||
* @param Client $client Client to get tax code from
|
||||
* @param string $defaultCountry default country to use if no valid tax code could be found and if the unit country code shouldn't be used
|
||||
* @param Item $item Item to get tax code from
|
||||
* @param null|Client $client Client to get tax code from
|
||||
* @param null|Supplier $supplier Supplier to get tax code from
|
||||
* @param string $defaultCountry Default country to use if no valid tax code could be found
|
||||
* and if the unit country code shouldn't be used
|
||||
*
|
||||
* @return TaxCombination
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function getTaxForPerson(Item $item, ?Client $client = null, ?Supplier $supplier = null, string $defaultCountry = '') : TaxCombination
|
||||
public function getTaxForPerson(
|
||||
Item $item,
|
||||
?Client $client = null, ?Supplier $supplier = null,
|
||||
string $defaultCountry = ''
|
||||
) : TaxCombination
|
||||
{
|
||||
if ($client === null && $supplier === null) {
|
||||
return new NullTaxCombination();
|
||||
}
|
||||
|
||||
// @todo define default sales tax code if none available?!
|
||||
$itemCode = 0;
|
||||
$accountCode = 0;
|
||||
|
|
@ -146,6 +157,7 @@ final class ApiTaxController extends Controller
|
|||
$tax->taxType = $request->getDataInt('tax_type') ?? 1;
|
||||
$tax->taxCode->abbr = (string) $request->getData('tax_code');
|
||||
$tax->itemCode = new NullAttributeValue((int) $request->getData('item_code'));
|
||||
$tax->account = $request->getDataString('account') ?? '';
|
||||
|
||||
if ($tax->taxType === 1) {
|
||||
$tax->clientCode = new NullAttributeValue((int) $request->getData('account_code'));
|
||||
|
|
@ -217,25 +229,23 @@ final class ApiTaxController extends Controller
|
|||
} elseif (\in_array($taxOfficeAddress->country, ISO3166TwoEnum::getRegion('eu'))) {
|
||||
// None EU company but we are EU company
|
||||
return $codes->getDefaultByValue('INT');
|
||||
} else {
|
||||
// None EU company and we are also none EU company
|
||||
return $codes->getDefaultByValue('INT');
|
||||
}
|
||||
|
||||
return $taxCode;
|
||||
// None EU company and we are also none EU company
|
||||
return $codes->getDefaultByValue('INT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client's tax code based on their country and tax office address
|
||||
* Get the supplier's tax code based on their country and tax office address
|
||||
*
|
||||
* @param Supplier $client The client to get the tax code for
|
||||
* @param Supplier $supplier The supplier to get the tax code for
|
||||
* @param Address $taxOfficeAddress The tax office address used to determine the tax code
|
||||
*
|
||||
* @return AttributeValue The client's tax code
|
||||
* @return AttributeValue The supplier's tax code
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function getSupplierTaxCode(Supplier $client, Address $taxOfficeAddress) : AttributeValue
|
||||
public function getSupplierTaxCode(Supplier $supplier, Address $taxOfficeAddress) : AttributeValue
|
||||
{
|
||||
/** @var \Modules\Attribute\Models\AttributeType $codes */
|
||||
$codes = SupplierAttributeTypeMapper::get()
|
||||
|
|
@ -247,28 +257,26 @@ final class ApiTaxController extends Controller
|
|||
|
||||
// @todo need to consider own tax id as well
|
||||
// @todo consider delivery & invoice location (Reihengeschaeft)
|
||||
if ($taxOfficeAddress->country === $client->mainAddress->country) {
|
||||
if ($taxOfficeAddress->country === $supplier->mainAddress->country) {
|
||||
// Same country as we (= local tax code)
|
||||
return $codes->getDefaultByValue($client->mainAddress->country);
|
||||
return $codes->getDefaultByValue($supplier->mainAddress->country);
|
||||
} elseif (\in_array($taxOfficeAddress->country, ISO3166TwoEnum::getRegion('eu'))
|
||||
&& \in_array($client->mainAddress->country, ISO3166TwoEnum::getRegion('eu'))
|
||||
&& \in_array($supplier->mainAddress->country, ISO3166TwoEnum::getRegion('eu'))
|
||||
) {
|
||||
if (!empty($client->getAttribute('vat_id')->value->getValue())) {
|
||||
if (!empty($supplier->getAttribute('vat_id')->value->getValue())) {
|
||||
// Is EU company and we are EU company
|
||||
return $codes->getDefaultByValue('EU');
|
||||
} else {
|
||||
// Is EU private customer and we are EU company
|
||||
return $codes->getDefaultByValue($client->mainAddress->country);
|
||||
return $codes->getDefaultByValue($supplier->mainAddress->country);
|
||||
}
|
||||
} elseif (\in_array($taxOfficeAddress->country, ISO3166TwoEnum::getRegion('eu'))) {
|
||||
// None EU company but we are EU company
|
||||
return $codes->getDefaultByValue('INT');
|
||||
} else {
|
||||
// None EU company and we are also none EU company
|
||||
return $codes->getDefaultByValue('INT');
|
||||
}
|
||||
|
||||
return $taxCode;
|
||||
// None EU company and we are also none EU company
|
||||
return $codes->getDefaultByValue('INT');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -399,7 +407,7 @@ final class ApiTaxController extends Controller
|
|||
public function updateTaxCombinationFromRequest(RequestAbstract $request, TaxCombination $new) : TaxCombination
|
||||
{
|
||||
$new->taxType = $request->getDataInt('tax_type') ?? $new->taxType;
|
||||
$new->taxCode = $request->getDataString('tax_code') ?? $new->taxCode;
|
||||
$new->taxCode = $request->hasData('tax_code') ? new NullTaxCode((int) $request->getData('tax_code')) : $new->taxCode;
|
||||
$new->itemCode = $request->hasData('item_code') ? new NullAttributeValue((int) $request->getData('item_code')) : $new->itemCode;
|
||||
|
||||
if ($new->taxType === 1) {
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ final class BackendController extends Controller
|
|||
PermissionCategory::BILL_LOG,
|
||||
)
|
||||
) {
|
||||
/** @var \Modules\Auditor\Models\Audit[] $logsBill */
|
||||
/** @var \Modules\Auditor\Models\Audit[] $logs */
|
||||
$logs = AuditMapper::getAll()
|
||||
->with('createdBy')
|
||||
->where('module', 'Billing')
|
||||
|
|
@ -212,8 +212,6 @@ final class BackendController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
$view->data['logs'] = $logs;
|
||||
$view->data['media-upload'] = new \Modules\Media\Theme\Backend\Components\Upload\BaseView($this->app->l11nManager, $request, $response);
|
||||
|
||||
|
|
|
|||
|
|
@ -138,14 +138,14 @@ final class CliController extends Controller
|
|||
$bill->billCountry = InvoiceRecognition::findCountry($lines, $identifiers, $language);
|
||||
}
|
||||
|
||||
$currency = InvoiceRecognition::findCurrency($lines);
|
||||
$currency = InvoiceRecognition::findCurrency($lines);
|
||||
$countryCurrency = ISO4217CharEnum::currencyFromCountry($bill->billCountry);
|
||||
|
||||
// Identified currency has to be country currency or one of the top globally used currencies
|
||||
if ($currency !== \in_array($currency, [
|
||||
$countryCurrency, ISO4217CharEnum::_USD, ISO4217CharEnum::_EUR, ISO4217CharEnum::_JPY,
|
||||
ISO4217CharEnum::_GBP, ISO4217CharEnum::_AUD, ISO4217CharEnum::_CAD, ISO4217CharEnum::_CHF,
|
||||
ISO4217CharEnum::_CNH, ISO4217CharEnum::_CNY
|
||||
ISO4217CharEnum::_CNH, ISO4217CharEnum::_CNY,
|
||||
])
|
||||
) {
|
||||
$currency = $countryCurrency;
|
||||
|
|
@ -156,7 +156,7 @@ final class CliController extends Controller
|
|||
$rd = -FloatInt::MAX_DECIMALS + ISO4217DecimalEnum::getByName('_' . $bill->currency);
|
||||
|
||||
/* Type */
|
||||
$type = $this->findSupplierInvoiceType($content, $identifiers['type'], $language);
|
||||
$type = InvoiceRecognition::findSupplierInvoiceType($content, $identifiers['type'], $language);
|
||||
|
||||
/** @var \Modules\Billing\Models\BillType $billType */
|
||||
$billType = BillTypeMapper::get()
|
||||
|
|
@ -166,7 +166,7 @@ final class CliController extends Controller
|
|||
$bill->type = new NullBillType($billType->id);
|
||||
|
||||
/* Number */
|
||||
$billNumber = InvoiceRecognition::findBillNumber($lines, $identifiers['bill_no'][$language]);
|
||||
$billNumber = InvoiceRecognition::findBillNumber($lines, $identifiers['bill_no'][$language]);
|
||||
$bill->external = $billNumber;
|
||||
|
||||
/* Reference / PO */
|
||||
|
|
@ -185,7 +185,7 @@ final class CliController extends Controller
|
|||
|
||||
/* Total */
|
||||
$totalGross = InvoiceRecognition::findBillGross($lines, $identifiers['total_gross'][$language]);
|
||||
$totalNet = InvoiceRecognition::findBillNet($lines, $identifiers['total_net'][$language]);
|
||||
$totalNet = InvoiceRecognition::findBillNet($lines, $identifiers['total_net'][$language]);
|
||||
|
||||
// The number format needs to be corrected:
|
||||
// Languages don't always respect the l11n number format
|
||||
|
|
@ -194,11 +194,11 @@ final class CliController extends Controller
|
|||
|
||||
if ($format !== null) {
|
||||
$l11n->thousands = $format['thousands'];
|
||||
$l11n->decimal = $format['decimal'];
|
||||
$l11n->decimal = $format['decimal'];
|
||||
}
|
||||
|
||||
$bill->grossSales = new FloatInt($totalGross, $l11n->thousands, $l11n->decimal);
|
||||
$bill->netSales = new FloatInt($totalNet, $l11n->thousands, $l11n->decimal);
|
||||
$bill->netSales = new FloatInt($totalNet, $l11n->thousands, $l11n->decimal);
|
||||
|
||||
/* Total Tax */
|
||||
// @todo taxes depend on local tax id (if company in Germany but invoice from US -> only gross amount important, there is no net)
|
||||
|
|
@ -238,7 +238,7 @@ final class CliController extends Controller
|
|||
foreach ($itemLines as $line => $itemLine) {
|
||||
$itemLineEnd = $line;
|
||||
|
||||
$billElement = new BillElement();
|
||||
$billElement = new BillElement();
|
||||
$billElement->bill = $bill;
|
||||
|
||||
$billElement->taxR->value = $taxRates;
|
||||
|
|
@ -255,14 +255,14 @@ final class CliController extends Controller
|
|||
if (isset($itemLine['price'])) {
|
||||
$billElement->singleListPriceNet = new FloatInt($itemLine['price'], $l11n->thousands, $l11n->decimal);
|
||||
|
||||
$billElement->singleSalesPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->singleSalesPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->singlePurchasePriceNet = $billElement->singleSalesPriceNet;
|
||||
|
||||
if ($billElement->taxR->value > 0) {
|
||||
$billElement->singleListPriceGross->value = $billElement->singleListPriceNet->value + ((int) \round($billElement->singleSalesPriceNet->value * $billElement->taxR->value / (FloatInt::DIVISOR * 100), $rd));
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
} else {
|
||||
$billElement->singleListPriceGross = $billElement->singleListPriceNet;
|
||||
$billElement->singleListPriceGross = $billElement->singleListPriceNet;
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
}
|
||||
}
|
||||
|
|
@ -271,14 +271,14 @@ final class CliController extends Controller
|
|||
if (isset($itemLine['total'])) {
|
||||
$billElement->totalListPriceNet = new FloatInt($itemLine['total'], $l11n->thousands, $l11n->decimal);
|
||||
|
||||
$billElement->totalSalesPriceNet = $billElement->totalListPriceNet;
|
||||
$billElement->totalSalesPriceNet = $billElement->totalListPriceNet;
|
||||
$billElement->totalPurchasePriceNet = $billElement->totalSalesPriceNet;
|
||||
|
||||
if ($billElement->taxR->value > 0) {
|
||||
$billElement->totalListPriceGross->value = $billElement->totalListPriceNet->value + ((int) \round($billElement->totalSalesPriceNet->value * $billElement->taxR->value / (FloatInt::DIVISOR * 100), $rd));
|
||||
$billElement->totalSalesPriceGross = $billElement->totalListPriceGross;
|
||||
$billElement->totalSalesPriceGross = $billElement->totalListPriceGross;
|
||||
} else {
|
||||
$billElement->totalListPriceGross = $billElement->totalListPriceNet;
|
||||
$billElement->totalListPriceGross = $billElement->totalListPriceNet;
|
||||
$billElement->totalSalesPriceGross = $billElement->totalListPriceGross;
|
||||
}
|
||||
}
|
||||
|
|
@ -305,21 +305,21 @@ final class CliController extends Controller
|
|||
|
||||
$key = \str_replace('total_', '', $key);
|
||||
|
||||
$billElement = new BillElement();
|
||||
$billElement = new BillElement();
|
||||
$billElement->bill = $bill;
|
||||
|
||||
$billElement->taxR->value = $taxRates;
|
||||
|
||||
$internalRequest = new HttpRequest();
|
||||
$internalRequest = new HttpRequest();
|
||||
$internalResponse = new HttpResponse();
|
||||
|
||||
$internalRequest->header->account = $request->header->account;
|
||||
$internalRequest->header->l11n = $request->header->l11n;
|
||||
$internalRequest->header->l11n = $request->header->l11n;
|
||||
|
||||
$internalRequest->setData('search', $key);
|
||||
$internalRequest->setData('limit', 1);
|
||||
|
||||
$internalResponse->header->l11n = clone $response->header->l11n;
|
||||
$internalResponse->header->l11n = clone $response->header->l11n;
|
||||
$internalResponse->header->l11n->language = $bill->language;
|
||||
|
||||
$this->app->moduleManager->get('ItemManagement', 'Api')->apiItemFind($internalRequest, $internalResponse);
|
||||
|
|
@ -328,9 +328,9 @@ final class CliController extends Controller
|
|||
$billElement->itemName = $key;
|
||||
|
||||
if ($item->id !== 0) {
|
||||
$billElement->item = $item;
|
||||
$billElement->item = $item;
|
||||
$billElement->itemNumber = $item->number;
|
||||
$billElement->itemName = $item->getL11n('name1')->content;
|
||||
$billElement->itemName = $item->getL11n('name1')->content;
|
||||
}
|
||||
|
||||
$billElement->quantity->value = FloatInt::DIVISOR;
|
||||
|
|
@ -338,23 +338,23 @@ final class CliController extends Controller
|
|||
// Unit
|
||||
$billElement->singleListPriceNet = new FloatInt($amount, $l11n->thousands, $l11n->decimal);
|
||||
|
||||
$billElement->singleSalesPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->singleSalesPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->singlePurchasePriceNet = $billElement->singleSalesPriceNet;
|
||||
|
||||
if ($billElement->taxR->value > 0) {
|
||||
$billElement->singleListPriceGross->value = $billElement->singleListPriceNet->value + ((int) \round($billElement->singleSalesPriceNet->value * $billElement->taxR->value / (FloatInt::DIVISOR * 100), $rd));
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
} else {
|
||||
$billElement->singleListPriceGross = $billElement->singleListPriceNet;
|
||||
$billElement->singleListPriceGross = $billElement->singleListPriceNet;
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
}
|
||||
|
||||
// Total
|
||||
$billElement->totalListPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->totalSalesPriceNet = $billElement->singleSalesPriceNet;
|
||||
$billElement->totalListPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->totalSalesPriceNet = $billElement->singleSalesPriceNet;
|
||||
$billElement->totalPurchasePriceNet = $billElement->singlePurchasePriceNet;
|
||||
$billElement->totalListPriceGross = $billElement->singleListPriceGross;
|
||||
$billElement->totalSalesPriceGross = $billElement->singleSalesPriceGross;
|
||||
$billElement->totalListPriceGross = $billElement->singleListPriceGross;
|
||||
$billElement->totalSalesPriceGross = $billElement->singleSalesPriceGross;
|
||||
|
||||
$billElement->taxP->value = $billElement->totalSalesPriceGross->value - $billElement->totalSalesPriceNet->value;
|
||||
|
||||
|
|
@ -367,40 +367,40 @@ final class CliController extends Controller
|
|||
|
||||
if (!empty($bill->elements)) {
|
||||
// Calculate totals from elements
|
||||
$totalNet = 0;
|
||||
$totalNet = 0;
|
||||
$totalGross = 0;
|
||||
foreach ($bill->elements as $element) {
|
||||
$totalNet += $element->totalSalesPriceNet->value;
|
||||
$totalNet += $element->totalSalesPriceNet->value;
|
||||
$totalGross += $element->totalSalesPriceGross->value;
|
||||
}
|
||||
|
||||
$bill->grossSales = new FloatInt($totalGross);
|
||||
$bill->netCosts = new FloatInt($totalNet);
|
||||
$bill->netSales = $bill->netCosts;
|
||||
$bill->netCosts = new FloatInt($totalNet);
|
||||
$bill->netSales = $bill->netCosts;
|
||||
}
|
||||
|
||||
$bill->taxP->value = $bill->grossSales->value - $bill->netSales->value;
|
||||
|
||||
// No elements could be identified -> make total a bill element
|
||||
if (empty($itemLines) && empty($bill->elements)) {
|
||||
$billElement = new BillElement();
|
||||
$billElement = new BillElement();
|
||||
$billElement->bill = $bill;
|
||||
|
||||
// List price
|
||||
$billElement->singleListPriceNet->value = $bill->netSales->value;
|
||||
$billElement->totalListPriceNet->value = $bill->netSales->value;
|
||||
$billElement->totalListPriceNet->value = $bill->netSales->value;
|
||||
|
||||
$billElement->singleListPriceGross->value = $bill->grossSales->value;
|
||||
$billElement->totalListPriceGross->value = $bill->grossSales->value;
|
||||
$billElement->totalListPriceGross->value = $bill->grossSales->value;
|
||||
|
||||
// Unit price
|
||||
$billElement->singleSalesPriceNet->value = $bill->netSales->value;
|
||||
$billElement->singleSalesPriceNet->value = $bill->netSales->value;
|
||||
$billElement->singlePurchasePriceNet->value = $bill->netSales->value;
|
||||
|
||||
$billElement->singleSalesPriceGross->value = $bill->grossSales->value;
|
||||
|
||||
// Total
|
||||
$billElement->totalSalesPriceNet->value = $bill->netSales->value;
|
||||
$billElement->totalSalesPriceNet->value = $bill->netSales->value;
|
||||
$billElement->totalPurchasePriceNet->value = $bill->netSales->value;
|
||||
|
||||
$billElement->totalSalesPriceGross->value = $bill->grossSales->value;
|
||||
|
|
@ -415,16 +415,16 @@ final class CliController extends Controller
|
|||
}
|
||||
|
||||
// Re-calculate totals from elements due to change
|
||||
$totalNet = 0;
|
||||
$totalNet = 0;
|
||||
$totalGross = 0;
|
||||
foreach ($bill->elements as $element) {
|
||||
$totalNet += $element->totalSalesPriceNet->value;
|
||||
$totalNet += $element->totalSalesPriceNet->value;
|
||||
$totalGross += $element->totalSalesPriceGross->value;
|
||||
}
|
||||
|
||||
$bill->grossSales = new FloatInt($totalGross);
|
||||
$bill->netCosts = new FloatInt($totalNet);
|
||||
$bill->netSales = $bill->netCosts;
|
||||
$bill->netCosts = new FloatInt($totalNet);
|
||||
$bill->netSales = $bill->netCosts;
|
||||
|
||||
$bill->taxP->value = $bill->grossSales->value - $bill->netSales->value;
|
||||
|
||||
|
|
@ -442,37 +442,6 @@ final class CliController extends Controller
|
|||
return $view;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detect the supplier bill type
|
||||
*
|
||||
* @param string $content String to analyze
|
||||
* @param array $types Possible bill types
|
||||
* @param string $language Bill language
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find possible supplier id
|
||||
*
|
||||
|
|
|
|||
|
|
@ -411,6 +411,8 @@ class Bill implements \JsonSerializable
|
|||
|
||||
public ?string $fiAccount = null;
|
||||
|
||||
// @todo Implement reason for bill (especially useful for credit notes, warehouse bookings)
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
|
|
@ -418,12 +420,12 @@ class Bill implements \JsonSerializable
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->netProfit = new FloatInt(0);
|
||||
$this->netCosts = new FloatInt(0);
|
||||
$this->netSales = new FloatInt(0);
|
||||
$this->grossSales = new FloatInt(0);
|
||||
$this->netDiscount = new FloatInt(0);
|
||||
$this->taxP = new FloatInt(0);
|
||||
$this->netProfit = new FloatInt(0);
|
||||
$this->netCosts = new FloatInt(0);
|
||||
$this->netSales = new FloatInt(0);
|
||||
$this->grossSales = new FloatInt(0);
|
||||
$this->netDiscount = new FloatInt(0);
|
||||
$this->taxP = new FloatInt(0);
|
||||
|
||||
$this->billDate = new \DateTime('now');
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
|
|
@ -530,7 +532,15 @@ class Bill implements \JsonSerializable
|
|||
$this->netDiscount->value += $element->totalDiscountP->value;
|
||||
}
|
||||
|
||||
// @todo also consider rounding similarly to recalculatePrices in elements
|
||||
/**
|
||||
* Validate the correctness of the bill
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @todo also consider rounding similarly to recalculatePrices in elements
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function isValid() : bool
|
||||
{
|
||||
return $this->validateTaxAmountElements()
|
||||
|
|
@ -542,6 +552,13 @@ class Bill implements \JsonSerializable
|
|||
&& $this->areElementsValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the bill elements
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function areElementsValid() : bool
|
||||
{
|
||||
foreach ($this->elements as $element) {
|
||||
|
|
@ -553,21 +570,49 @@ class Bill implements \JsonSerializable
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the net and gross values
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateNetGross() : bool
|
||||
{
|
||||
return $this->netSales->value <= $this->grossSales->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the profit
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateProfit() : bool
|
||||
{
|
||||
return $this->netSales->value - $this->netCosts->value === $this->netProfit->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the taxes
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateTax() : bool
|
||||
{
|
||||
return \abs($this->netSales->value + $this->taxP->value - $this->grossSales->value) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the taxes for the elements
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateTaxAmountElements() : bool
|
||||
{
|
||||
$taxes = 0;
|
||||
|
|
@ -578,6 +623,13 @@ class Bill implements \JsonSerializable
|
|||
return $taxes === $this->taxP->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the net of the elements
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateNetElements() : bool
|
||||
{
|
||||
$net = 0;
|
||||
|
|
@ -588,6 +640,13 @@ class Bill implements \JsonSerializable
|
|||
return $net === $this->netSales->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the gross of the elements
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateGrossElements()
|
||||
{
|
||||
$gross = 0;
|
||||
|
|
@ -598,6 +657,13 @@ class Bill implements \JsonSerializable
|
|||
return $gross === $this->grossSales->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the quantities and total price
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validatePriceQuantityElements()
|
||||
{
|
||||
foreach ($this->elements as $element) {
|
||||
|
|
@ -620,7 +686,7 @@ class Bill implements \JsonSerializable
|
|||
return [
|
||||
'id' => $this->id,
|
||||
'number' => $this->number,
|
||||
'external' => $this->external,
|
||||
'external' => $this->external,
|
||||
'type' => $this->type,
|
||||
'shipTo' => $this->shipTo,
|
||||
'shipFAO' => $this->shipFAO,
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ class BillElement implements \JsonSerializable
|
|||
|
||||
public ?string $costobject = null;
|
||||
|
||||
public ?TaxCombination $taxCombination = null;
|
||||
|
||||
/**
|
||||
* Tax amount
|
||||
*
|
||||
|
|
@ -187,11 +189,11 @@ class BillElement implements \JsonSerializable
|
|||
$this->totalSalesPriceNet = new FloatInt();
|
||||
$this->totalSalesPriceGross = new FloatInt();
|
||||
|
||||
$this->singlePurchasePriceNet = new FloatInt();
|
||||
$this->totalPurchasePriceNet = new FloatInt();
|
||||
$this->singlePurchasePriceNet = new FloatInt();
|
||||
$this->totalPurchasePriceNet = new FloatInt();
|
||||
|
||||
$this->singleProfitNet = new FloatInt();
|
||||
$this->totalProfitNet = new FloatInt();
|
||||
$this->singleProfitNet = new FloatInt();
|
||||
$this->totalProfitNet = new FloatInt();
|
||||
|
||||
$this->singleDiscountP = new FloatInt();
|
||||
$this->totalDiscountP = new FloatInt();
|
||||
|
|
@ -213,15 +215,24 @@ class BillElement implements \JsonSerializable
|
|||
*/
|
||||
public function setQuantity(int $quantity) : void
|
||||
{
|
||||
if ($this->quantity === $quantity) {
|
||||
if ($this->quantity->value === $quantity) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->quantity = $quantity;
|
||||
$this->quantity->value = $quantity;
|
||||
|
||||
$this->recalculatePrices();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-calculate prices.
|
||||
*
|
||||
* This function is very important to call after changing any prices/quantities
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function recalculatePrices() : void
|
||||
{
|
||||
$rd = -FloatInt::MAX_DECIMALS + ISO4217DecimalEnum::getByName('_' . $this->bill->currency);
|
||||
|
|
@ -249,7 +260,15 @@ class BillElement implements \JsonSerializable
|
|||
$this->effectiveSingleSalesPriceNet->value = (int) \round($this->totalSalesPriceNet->value / ($this->quantity->value / FloatInt::DIVISOR), $rd);
|
||||
}
|
||||
|
||||
// @todo also consider rounding similarly to recalculatePrices
|
||||
/**
|
||||
* Validate the correctness of the element
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @todo also consider rounding similarly to recalculatePrices
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function isValid() : bool
|
||||
{
|
||||
return $this->validateNetGross()
|
||||
|
|
@ -261,6 +280,13 @@ class BillElement implements \JsonSerializable
|
|||
&& $this->validateTotalPrice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the net and gross values
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateNetGross() : bool
|
||||
{
|
||||
return $this->singleListPriceNet->value <= $this->singleListPriceGross->value
|
||||
|
|
@ -269,21 +295,42 @@ class BillElement implements \JsonSerializable
|
|||
&& $this->totalSalesPriceNet->value <= $this->totalSalesPriceGross->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the profit
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateProfit() : bool
|
||||
{
|
||||
return $this->totalSalesPriceNet->value - $this->totalPurchasePriceNet->value === $this->totalProfitNet->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the taxes
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateTax() : bool
|
||||
{
|
||||
$paidQuantity = $this->quantity->value - $this->discountQ->value;
|
||||
|
||||
return \abs($this->singleListPriceNet->value + ((int) \round($this->taxP->value / ($paidQuantity / FloatInt::DIVISOR), 0)) - $this->singleListPriceGross->value) === 0
|
||||
&& \abs($this->singleSalesPriceNet->value + ((int) \round($this->taxP->value / ($paidQuantity / FloatInt::DIVISOR), 0)) - $this->singleSalesPriceGross->value) === 0
|
||||
&& \abs($this->totalListPriceNet->value +$this->taxP->value - $this->totalListPriceGross->value) === 0
|
||||
&& \abs($this->totalListPriceNet->value + $this->taxP->value - $this->totalListPriceGross->value) === 0
|
||||
&& \abs($this->totalSalesPriceNet->value + $this->taxP->value - $this->totalSalesPriceGross->value) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the tax rate
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateTaxRate() : bool
|
||||
{
|
||||
return (($this->taxP->value === 0 && $this->taxR->value === 0)
|
||||
|
|
@ -291,6 +338,13 @@ class BillElement implements \JsonSerializable
|
|||
&& \abs($this->totalSalesPriceGross->value / $this->totalSalesPriceNet->value - 1.0 - $this->taxR->value / (FloatInt::DIVISOR * 100)) < 0.001);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of single and total prices
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateSingleTotal() : bool
|
||||
{
|
||||
$paidQuantity = $this->quantity->value - $this->discountQ->value;
|
||||
|
|
@ -301,11 +355,25 @@ class BillElement implements \JsonSerializable
|
|||
&& ((int) \round($this->singleDiscountP->value * ($this->quantity->value / FloatInt::DIVISOR), 0)) === $this->totalDiscountP->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the effective price
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateEffectiveSinglePrice() : bool
|
||||
{
|
||||
return $this->effectiveSingleSalesPriceNet->value === (int) \round($this->totalSalesPriceNet->value / ($this->quantity->value / FloatInt::DIVISOR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the correctness of the total price
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function validateTotalPrice() : bool
|
||||
{
|
||||
return ((int) \round($this->singleListPriceNet->value * ($this->quantity->value / FloatInt::DIVISOR)
|
||||
|
|
@ -332,10 +400,11 @@ class BillElement implements \JsonSerializable
|
|||
/**
|
||||
* Create element from item
|
||||
*
|
||||
* @param Item $item Item
|
||||
* @param TaxCode $taxCode Tax code used for gross amount calculation
|
||||
* @param int $quantity Quantity
|
||||
* @param Bill $bill Bill
|
||||
* @param Item $item Item
|
||||
* @param TaxCombination $taxCombination Tax combination
|
||||
* @param Bill $bill Bill
|
||||
* @param int $quantity Quantity (1.0 = 10000)
|
||||
* @param null|Container $container Item container
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
|
|
@ -344,8 +413,8 @@ class BillElement implements \JsonSerializable
|
|||
public static function fromItem(
|
||||
Item $item,
|
||||
TaxCombination $taxCombination,
|
||||
Bill $bill,
|
||||
int $quantity = FloatInt::DIVISOR,
|
||||
Bill $bill = null,
|
||||
?Container $container = null
|
||||
) : self
|
||||
{
|
||||
|
|
@ -358,9 +427,10 @@ class BillElement implements \JsonSerializable
|
|||
$element->itemDescription = $item->getL11n('description_short')->content;
|
||||
$element->quantity->value = $quantity;
|
||||
|
||||
$element->taxR = new FloatInt($taxCombination->taxCode->percentageInvoice);
|
||||
$element->taxCode = $taxCombination->taxCode->abbr;
|
||||
$element->fiAccount = $taxCombination->account;
|
||||
$element->taxR = new FloatInt($taxCombination->taxCode->percentageInvoice);
|
||||
$element->taxCode = $taxCombination->taxCode->abbr;
|
||||
$element->fiAccount = $taxCombination->account;
|
||||
$element->taxCombination = $taxCombination;
|
||||
|
||||
// @todo the purchase price is based on lot/sn/avg prices if available
|
||||
$element->singlePurchasePriceNet->value = $item->purchasePrice->value;
|
||||
|
|
@ -391,7 +461,7 @@ class BillElement implements \JsonSerializable
|
|||
return [
|
||||
'id' => $this->id,
|
||||
'order' => $this->order,
|
||||
'item' => $this->item->id,
|
||||
'item' => $this->item?->id,
|
||||
'itemNumber' => $this->itemNumber,
|
||||
'itemName' => $this->itemName,
|
||||
'itemDescription' => $this->itemDescription,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Modules\Billing\Models;
|
||||
|
||||
use Modules\Billing\Models\Tax\TaxCombinationMapper;
|
||||
use Modules\ItemManagement\Models\ContainerMapper;
|
||||
use Modules\ItemManagement\Models\ItemMapper;
|
||||
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
|
||||
|
|
@ -58,16 +59,17 @@ final class BillElementMapper extends DataMapperFactory
|
|||
'billing_bill_element_total_netsalesprice' => ['name' => 'billing_bill_element_total_netsalesprice', 'type' => 'Serializable', 'internal' => 'totalSalesPriceNet'],
|
||||
'billing_bill_element_total_grosssalesprice' => ['name' => 'billing_bill_element_total_grosssalesprice', 'type' => 'Serializable', 'internal' => 'totalSalesPriceGross'],
|
||||
|
||||
'billing_bill_element_single_netprofit' => ['name' => 'billing_bill_element_single_netprofit', 'type' => 'Serializable', 'internal' => 'singleProfitNet'],
|
||||
'billing_bill_element_total_netprofit' => ['name' => 'billing_bill_element_total_netprofit', 'type' => 'Serializable', 'internal' => 'totalProfitNet'],
|
||||
'billing_bill_element_single_netprofit' => ['name' => 'billing_bill_element_single_netprofit', 'type' => 'Serializable', 'internal' => 'singleProfitNet'],
|
||||
'billing_bill_element_total_netprofit' => ['name' => 'billing_bill_element_total_netprofit', 'type' => 'Serializable', 'internal' => 'totalProfitNet'],
|
||||
|
||||
'billing_bill_element_single_netpurchaseprice' => ['name' => 'billing_bill_element_single_netpurchaseprice', 'type' => 'Serializable', 'internal' => 'singlePurchasePriceNet'],
|
||||
'billing_bill_element_total_netpurchaseprice' => ['name' => 'billing_bill_element_total_netpurchaseprice', 'type' => 'Serializable', 'internal' => 'totalPurchasePriceNet'],
|
||||
'billing_bill_element_bill' => ['name' => 'billing_bill_element_bill', 'type' => 'int', 'internal' => 'bill'],
|
||||
'billing_bill_element_single_netpurchaseprice' => ['name' => 'billing_bill_element_single_netpurchaseprice', 'type' => 'Serializable', 'internal' => 'singlePurchasePriceNet'],
|
||||
'billing_bill_element_total_netpurchaseprice' => ['name' => 'billing_bill_element_total_netpurchaseprice', 'type' => 'Serializable', 'internal' => 'totalPurchasePriceNet'],
|
||||
'billing_bill_element_bill' => ['name' => 'billing_bill_element_bill', 'type' => 'int', 'internal' => 'bill'],
|
||||
|
||||
'billing_bill_element_tax_type' => ['name' => 'billing_bill_element_tax_type', 'type' => 'string', 'internal' => 'taxCode'],
|
||||
'billing_bill_element_tax_price' => ['name' => 'billing_bill_element_tax_price', 'type' => 'Serializable', 'internal' => 'taxP'],
|
||||
'billing_bill_element_tax_percentage' => ['name' => 'billing_bill_element_tax_percentage', 'type' => 'Serializable', 'internal' => 'taxR'],
|
||||
'billing_bill_element_tax_combination' => ['name' => 'billing_bill_element_tax_combination', 'type' => 'int', 'internal' => 'taxCombination'],
|
||||
'billing_bill_element_tax_type' => ['name' => 'billing_bill_element_tax_type', 'type' => 'string', 'internal' => 'taxCode'],
|
||||
'billing_bill_element_tax_price' => ['name' => 'billing_bill_element_tax_price', 'type' => 'Serializable', 'internal' => 'taxP'],
|
||||
'billing_bill_element_tax_percentage' => ['name' => 'billing_bill_element_tax_percentage', 'type' => 'Serializable', 'internal' => 'taxR'],
|
||||
|
||||
'billing_bill_element_segment' => ['name' => 'billing_bill_element_segment', 'type' => 'int', 'internal' => 'itemSegment'],
|
||||
'billing_bill_element_section' => ['name' => 'billing_bill_element_section', 'type' => 'int', 'internal' => 'itemSection'],
|
||||
|
|
@ -112,6 +114,10 @@ final class BillElementMapper extends DataMapperFactory
|
|||
'mapper' => ContainerMapper::class,
|
||||
'external' => 'billing_bill_element_container',
|
||||
],
|
||||
'taxCombination' => [
|
||||
'mapper' => TaxCombinationMapper::class,
|
||||
'external' => 'billing_bill_element_tax_combination',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class BillMapper extends DataMapperFactory
|
|||
'billing_bill_id' => ['name' => 'billing_bill_id', 'type' => 'int', 'internal' => 'id'],
|
||||
'billing_bill_sequence' => ['name' => 'billing_bill_sequence', 'type' => 'int', 'internal' => 'sequence'],
|
||||
'billing_bill_number' => ['name' => 'billing_bill_number', 'type' => 'string', 'internal' => 'number'],
|
||||
'billing_bill_external' => ['name' => 'billing_bill_external', 'type' => 'string', 'internal' => 'external'],
|
||||
'billing_bill_external' => ['name' => 'billing_bill_external', 'type' => 'string', 'internal' => 'external'],
|
||||
'billing_bill_type' => ['name' => 'billing_bill_type', 'type' => 'int', 'internal' => 'type'],
|
||||
'billing_bill_template' => ['name' => 'billing_bill_template', 'type' => 'bool', 'internal' => 'isTemplate'],
|
||||
'billing_bill_archived' => ['name' => 'billing_bill_archived', 'type' => 'bool', 'internal' => 'isArchived'],
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ final class BillTypeMapper extends DataMapperFactory
|
|||
'billing_type_transfer_stock' => ['name' => 'billing_type_transfer_stock', 'type' => 'bool', 'internal' => 'transferStock'],
|
||||
'billing_type_accounting' => ['name' => 'billing_type_accounting', 'type' => 'bool', 'internal' => 'isAccounting'],
|
||||
'billing_type_transfer_sign' => ['name' => 'billing_type_transfer_sign', 'type' => 'int', 'internal' => 'sign'],
|
||||
'billing_type_email' => ['name' => 'billing_type_email', 'type' => 'bool', 'internal' => 'email'],
|
||||
'billing_type_email' => ['name' => 'billing_type_email', 'type' => 'bool', 'internal' => 'email'],
|
||||
'billing_type_is_template' => ['name' => 'billing_type_is_template', 'type' => 'bool', 'internal' => 'isTemplate'],
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,19 @@ use phpOMS\Validation\Finance\IbanEnum;
|
|||
*/
|
||||
class InvoiceRecognition
|
||||
{
|
||||
public static function detect(Bill $bill, string $content)
|
||||
/**
|
||||
* Detect bill components
|
||||
*
|
||||
* @param Bill $bill Bill
|
||||
* @param string $content Bill content
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function detect(Bill $bill, string $content) : void
|
||||
{
|
||||
$content = \strtolower($content ?? '');
|
||||
$content = \strtolower($content);
|
||||
$lines = \explode("\n", $content);
|
||||
foreach ($lines as $line => $value) {
|
||||
if (empty(\trim($value))) {
|
||||
|
|
@ -64,16 +74,16 @@ class InvoiceRecognition
|
|||
/** @var array $identifiers */
|
||||
$identifiers = \json_decode($identifierContent, true);
|
||||
|
||||
$bill->billCountry = InvoiceRecognition::findCountry($lines, $identifiers, $language);
|
||||
$bill->billCountry = self::findCountry($lines, $identifiers, $language);
|
||||
|
||||
$currency = self::findCurrency($lines);
|
||||
$currency = self::findCurrency($lines);
|
||||
$countryCurrency = ISO4217CharEnum::currencyFromCountry($bill->billCountry);
|
||||
|
||||
// Identified currency has to be country currency or one of the top globally used currencies
|
||||
if ($currency !== \in_array($currency, [
|
||||
$countryCurrency, ISO4217CharEnum::_USD, ISO4217CharEnum::_EUR, ISO4217CharEnum::_JPY,
|
||||
ISO4217CharEnum::_GBP, ISO4217CharEnum::_AUD, ISO4217CharEnum::_CAD, ISO4217CharEnum::_CHF,
|
||||
ISO4217CharEnum::_CNH, ISO4217CharEnum::_CNY
|
||||
ISO4217CharEnum::_CNH, ISO4217CharEnum::_CNY,
|
||||
])
|
||||
) {
|
||||
$currency = $countryCurrency;
|
||||
|
|
@ -86,8 +96,8 @@ class InvoiceRecognition
|
|||
/* Type */
|
||||
$type = self::findSupplierInvoiceType($content, $identifiers['type'], $language);
|
||||
|
||||
/** @var \Modules\Billing\Models\BillType $billType */
|
||||
/*
|
||||
@var \Modules\Billing\Models\BillType $billType
|
||||
$billType = BillTypeMapper::get()
|
||||
->where('name', $type)
|
||||
->execute();
|
||||
|
|
@ -96,7 +106,7 @@ class InvoiceRecognition
|
|||
*/
|
||||
|
||||
/* Number */
|
||||
$billNumber = self::findBillNumber($lines, $identifiers['bill_no'][$language]);
|
||||
$billNumber = self::findBillNumber($lines, $identifiers['bill_no'][$language]);
|
||||
$bill->external = $billNumber;
|
||||
|
||||
/* Reference / PO */
|
||||
|
|
@ -115,7 +125,7 @@ class InvoiceRecognition
|
|||
|
||||
/* Total */
|
||||
$totalGross = self::findBillGross($lines, $identifiers['total_gross'][$language]);
|
||||
$totalNet = self::findBillNet($lines, $identifiers['total_net'][$language]);
|
||||
$totalNet = self::findBillNet($lines, $identifiers['total_net'][$language]);
|
||||
|
||||
// The number format needs to be corrected:
|
||||
// Languages don't always respect the l11n number format
|
||||
|
|
@ -124,11 +134,11 @@ class InvoiceRecognition
|
|||
|
||||
if ($format !== null) {
|
||||
$l11n->thousands = $format['thousands'];
|
||||
$l11n->decimal = $format['decimal'];
|
||||
$l11n->decimal = $format['decimal'];
|
||||
}
|
||||
|
||||
$bill->grossSales = new FloatInt($totalGross, $l11n->thousands, $l11n->decimal);
|
||||
$bill->netSales = new FloatInt($totalNet, $l11n->thousands, $l11n->decimal);
|
||||
$bill->netSales = new FloatInt($totalNet, $l11n->thousands, $l11n->decimal);
|
||||
|
||||
/* Total Tax */
|
||||
// @todo taxes depend on local tax id (if company in Germany but invoice from US -> only gross amount important, there is no net)
|
||||
|
|
@ -166,7 +176,7 @@ class InvoiceRecognition
|
|||
foreach ($itemLines as $line => $itemLine) {
|
||||
$itemLineEnd = $line;
|
||||
|
||||
$billElement = new BillElement();
|
||||
$billElement = new BillElement();
|
||||
$billElement->bill = $bill;
|
||||
|
||||
$billElement->taxR->value = $taxRates;
|
||||
|
|
@ -183,14 +193,14 @@ class InvoiceRecognition
|
|||
if (isset($itemLine['price'])) {
|
||||
$billElement->singleListPriceNet = new FloatInt($itemLine['price'], $l11n->thousands, $l11n->decimal);
|
||||
|
||||
$billElement->singleSalesPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->singleSalesPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->singlePurchasePriceNet = $billElement->singleSalesPriceNet;
|
||||
|
||||
if ($billElement->taxR->value > 0) {
|
||||
$billElement->singleListPriceGross->value = $billElement->singleListPriceNet->value + ((int) \round($billElement->singleSalesPriceNet->value * $billElement->taxR->value / (FloatInt::DIVISOR * 100), $rd));
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
} else {
|
||||
$billElement->singleListPriceGross = $billElement->singleListPriceNet;
|
||||
$billElement->singleListPriceGross = $billElement->singleListPriceNet;
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
}
|
||||
}
|
||||
|
|
@ -199,14 +209,14 @@ class InvoiceRecognition
|
|||
if (isset($itemLine['total'])) {
|
||||
$billElement->totalListPriceNet = new FloatInt($itemLine['total'], $l11n->thousands, $l11n->decimal);
|
||||
|
||||
$billElement->totalSalesPriceNet = $billElement->totalListPriceNet;
|
||||
$billElement->totalSalesPriceNet = $billElement->totalListPriceNet;
|
||||
$billElement->totalPurchasePriceNet = $billElement->totalSalesPriceNet;
|
||||
|
||||
if ($billElement->taxR->value > 0) {
|
||||
$billElement->totalListPriceGross->value = $billElement->totalListPriceNet->value + ((int) \round($billElement->totalSalesPriceNet->value * $billElement->taxR->value / (FloatInt::DIVISOR * 100), $rd));
|
||||
$billElement->totalSalesPriceGross = $billElement->totalListPriceGross;
|
||||
$billElement->totalSalesPriceGross = $billElement->totalListPriceGross;
|
||||
} else {
|
||||
$billElement->totalListPriceGross = $billElement->totalListPriceNet;
|
||||
$billElement->totalListPriceGross = $billElement->totalListPriceNet;
|
||||
$billElement->totalSalesPriceGross = $billElement->totalListPriceGross;
|
||||
}
|
||||
}
|
||||
|
|
@ -231,7 +241,7 @@ class InvoiceRecognition
|
|||
|
||||
$key = \str_replace('total_', '', $key);
|
||||
|
||||
$billElement = new BillElement();
|
||||
$billElement = new BillElement();
|
||||
$billElement->bill = $bill;
|
||||
|
||||
$billElement->taxR->value = $taxRates;
|
||||
|
|
@ -241,23 +251,23 @@ class InvoiceRecognition
|
|||
// Unit
|
||||
$billElement->singleListPriceNet = new FloatInt($amount, $l11n->thousands, $l11n->decimal);
|
||||
|
||||
$billElement->singleSalesPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->singleSalesPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->singlePurchasePriceNet = $billElement->singleSalesPriceNet;
|
||||
|
||||
if ($billElement->taxR->value > 0) {
|
||||
$billElement->singleListPriceGross->value = $billElement->singleListPriceNet->value + ((int) \round($billElement->singleSalesPriceNet->value * $billElement->taxR->value / (FloatInt::DIVISOR * 100), $rd));
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
} else {
|
||||
$billElement->singleListPriceGross = $billElement->singleListPriceNet;
|
||||
$billElement->singleListPriceGross = $billElement->singleListPriceNet;
|
||||
$billElement->singleSalesPriceGross = $billElement->singleListPriceGross;
|
||||
}
|
||||
|
||||
// Total
|
||||
$billElement->totalListPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->totalSalesPriceNet = $billElement->singleSalesPriceNet;
|
||||
$billElement->totalListPriceNet = $billElement->singleListPriceNet;
|
||||
$billElement->totalSalesPriceNet = $billElement->singleSalesPriceNet;
|
||||
$billElement->totalPurchasePriceNet = $billElement->singlePurchasePriceNet;
|
||||
$billElement->totalListPriceGross = $billElement->singleListPriceGross;
|
||||
$billElement->totalSalesPriceGross = $billElement->singleSalesPriceGross;
|
||||
$billElement->totalListPriceGross = $billElement->singleListPriceGross;
|
||||
$billElement->totalSalesPriceGross = $billElement->singleSalesPriceGross;
|
||||
|
||||
$billElement->taxP->value = $billElement->totalSalesPriceGross->value - $billElement->totalSalesPriceNet->value;
|
||||
|
||||
|
|
@ -268,41 +278,40 @@ class InvoiceRecognition
|
|||
|
||||
if (!empty($bill->elements)) {
|
||||
// Calculate totals from elements
|
||||
$totalNet = 0;
|
||||
$totalNet = 0;
|
||||
$totalGross = 0;
|
||||
foreach ($bill->elements as $element) {
|
||||
$totalNet += $element->totalSalesPriceNet->value;
|
||||
$totalNet += $element->totalSalesPriceNet->value;
|
||||
$totalGross += $element->totalSalesPriceGross->value;
|
||||
}
|
||||
|
||||
|
||||
$bill->grossSales = new FloatInt($totalGross);
|
||||
$bill->netCosts = new FloatInt($totalNet);
|
||||
$bill->netSales = $bill->netCosts;
|
||||
$bill->netCosts = new FloatInt($totalNet);
|
||||
$bill->netSales = $bill->netCosts;
|
||||
}
|
||||
|
||||
$bill->taxP->value = $bill->grossSales->value - $bill->netSales->value;
|
||||
|
||||
// No elements could be identified -> make total a bill element
|
||||
if (empty($bill->elements)) {
|
||||
$billElement = new BillElement();
|
||||
$billElement = new BillElement();
|
||||
$billElement->bill = $bill;
|
||||
|
||||
// List price
|
||||
$billElement->singleListPriceNet->value = $bill->netSales->value;
|
||||
$billElement->totalListPriceNet->value = $bill->netSales->value;
|
||||
$billElement->totalListPriceNet->value = $bill->netSales->value;
|
||||
|
||||
$billElement->singleListPriceGross->value = $bill->grossSales->value;
|
||||
$billElement->totalListPriceGross->value = $bill->grossSales->value;
|
||||
$billElement->totalListPriceGross->value = $bill->grossSales->value;
|
||||
|
||||
// Unit price
|
||||
$billElement->singleSalesPriceNet->value = $bill->netSales->value;
|
||||
$billElement->singleSalesPriceNet->value = $bill->netSales->value;
|
||||
$billElement->singlePurchasePriceNet->value = $bill->netSales->value;
|
||||
|
||||
$billElement->singleSalesPriceGross->value = $bill->grossSales->value;
|
||||
|
||||
// Total
|
||||
$billElement->totalSalesPriceNet->value = $bill->netSales->value;
|
||||
$billElement->totalSalesPriceNet->value = $bill->netSales->value;
|
||||
$billElement->totalPurchasePriceNet->value = $bill->netSales->value;
|
||||
|
||||
$billElement->totalSalesPriceGross->value = $bill->grossSales->value;
|
||||
|
|
@ -315,16 +324,16 @@ class InvoiceRecognition
|
|||
}
|
||||
|
||||
// Re-calculate totals from elements due to change
|
||||
$totalNet = 0;
|
||||
$totalNet = 0;
|
||||
$totalGross = 0;
|
||||
foreach ($bill->elements as $element) {
|
||||
$totalNet += $element->totalSalesPriceNet->value;
|
||||
$totalNet += $element->totalSalesPriceNet->value;
|
||||
$totalGross += $element->totalSalesPriceGross->value;
|
||||
}
|
||||
|
||||
$bill->grossSales = new FloatInt($totalGross);
|
||||
$bill->netCosts = new FloatInt($totalNet);
|
||||
$bill->netSales = $bill->netCosts;
|
||||
$bill->netCosts = new FloatInt($totalNet);
|
||||
$bill->netSales = $bill->netCosts;
|
||||
|
||||
$bill->taxP->value = $bill->grossSales->value - $bill->netSales->value;
|
||||
}
|
||||
|
|
@ -572,7 +581,7 @@ class InvoiceRecognition
|
|||
* @param string[] $lines Bill lines
|
||||
* @param array $matches Net match patterns
|
||||
*
|
||||
* @return int
|
||||
* @return string
|
||||
*
|
||||
* @bug Issue with net/discount/gross in one line
|
||||
*
|
||||
|
|
@ -582,7 +591,7 @@ class InvoiceRecognition
|
|||
*/
|
||||
public static function findBillNet(array $lines, array $matches) : string
|
||||
{
|
||||
$bestMatch = 0;
|
||||
$bestMatch = 0;
|
||||
$bestMatchStr = '';
|
||||
|
||||
$found = [];
|
||||
|
|
@ -605,7 +614,7 @@ class InvoiceRecognition
|
|||
: FloatInt::DIVISOR);
|
||||
|
||||
if ($net > $bestMatch) {
|
||||
$bestMatch = $net;
|
||||
$bestMatch = $net;
|
||||
$bestMatchStr = $temp;
|
||||
}
|
||||
}
|
||||
|
|
@ -629,7 +638,7 @@ class InvoiceRecognition
|
|||
*/
|
||||
public static function findBillGross(array $lines, array $matches) : string
|
||||
{
|
||||
$bestMatch = 0;
|
||||
$bestMatch = 0;
|
||||
$bestMatchStr = '';
|
||||
|
||||
$found = [];
|
||||
|
|
@ -652,7 +661,7 @@ class InvoiceRecognition
|
|||
: FloatInt::DIVISOR);
|
||||
|
||||
if ($gross > $bestMatch) {
|
||||
$bestMatch = $gross;
|
||||
$bestMatch = $gross;
|
||||
$bestMatchStr = $temp;
|
||||
}
|
||||
}
|
||||
|
|
@ -678,9 +687,7 @@ class InvoiceRecognition
|
|||
{
|
||||
// Find discounts
|
||||
$bestDiscount = 0;
|
||||
$found = [];
|
||||
|
||||
$discountLine = 0;
|
||||
$found = [];
|
||||
|
||||
foreach ($matches['total_discount'][$language] as $match) {
|
||||
foreach ($lines as $idx => $line) {
|
||||
|
|
@ -715,7 +722,7 @@ class InvoiceRecognition
|
|||
|
||||
// Find shipping
|
||||
$bestShipping = 0;
|
||||
$found = [];
|
||||
$found = [];
|
||||
|
||||
$shippingLine = 0;
|
||||
|
||||
|
|
@ -750,7 +757,7 @@ class InvoiceRecognition
|
|||
|
||||
// Find customs
|
||||
$bestCustoms = 0;
|
||||
$found = [];
|
||||
$found = [];
|
||||
|
||||
$customsLine = 0;
|
||||
|
||||
|
|
@ -785,7 +792,7 @@ class InvoiceRecognition
|
|||
|
||||
// Find insurance
|
||||
$bestInsurance = 0;
|
||||
$found = [];
|
||||
$found = [];
|
||||
|
||||
$insuranceLine = 0;
|
||||
|
||||
|
|
@ -820,9 +827,7 @@ class InvoiceRecognition
|
|||
|
||||
// Find surcharge
|
||||
$bestSurcharge = 0;
|
||||
$found = [];
|
||||
|
||||
$surchargeLine = 0;
|
||||
$found = [];
|
||||
|
||||
foreach ($matches['total_surcharge'][$language] as $match) {
|
||||
foreach ($lines as $idx => $line) {
|
||||
|
|
@ -849,7 +854,6 @@ class InvoiceRecognition
|
|||
|
||||
if ($surcharge > $bestSurcharge) {
|
||||
$bestSurcharge = $surcharge;
|
||||
$surchargeLine = $idx;
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -858,9 +862,9 @@ class InvoiceRecognition
|
|||
}
|
||||
|
||||
return [
|
||||
'total_discount' => -1 * $bestDiscount,
|
||||
'total_shipping' => $bestShipping,
|
||||
'total_customs' => $bestCustoms,
|
||||
'total_discount' => -1 * $bestDiscount,
|
||||
'total_shipping' => $bestShipping,
|
||||
'total_customs' => $bestCustoms,
|
||||
'total_insurance' => $bestInsurance,
|
||||
'total_surcharge' => $bestSurcharge,
|
||||
];
|
||||
|
|
@ -926,9 +930,9 @@ class InvoiceRecognition
|
|||
$rows = [];
|
||||
|
||||
// Get item list until end of item list/table is reached
|
||||
$found = [];
|
||||
$found = [];
|
||||
$structureCount = \count($headlineStructure);
|
||||
$linesSkipped = 0;
|
||||
$linesSkipped = 0;
|
||||
|
||||
foreach ($lines as $l => $line) {
|
||||
// @todo find better way to identify end of item table
|
||||
|
|
@ -950,7 +954,7 @@ class InvoiceRecognition
|
|||
$linesSkipped = 0;
|
||||
|
||||
$temp = [];
|
||||
$c = 0;
|
||||
$c = 0;
|
||||
foreach ($headlineStructure as $idx => $_) {
|
||||
$subFound = [];
|
||||
|
||||
|
|
@ -970,8 +974,8 @@ class InvoiceRecognition
|
|||
/**
|
||||
* Create DateTime from date string
|
||||
*
|
||||
* @param string $date Date string
|
||||
* @param string[] $formats Date formats
|
||||
* @param string $date Date string
|
||||
* @param string[] $formats Date formats
|
||||
*
|
||||
* @return null|\DateTime
|
||||
*
|
||||
|
|
@ -981,14 +985,14 @@ class InvoiceRecognition
|
|||
{
|
||||
if ((!empty($supplierFormat))) {
|
||||
$dt = \DateTime::createFromFormat(
|
||||
$supplierFormat ?? '',
|
||||
$supplierFormat,
|
||||
$date
|
||||
);
|
||||
|
||||
return $dt === false ? new \DateTime('1970-01-01') : $dt;
|
||||
}
|
||||
|
||||
$now = new \DateTime('now');
|
||||
$now = new \DateTime('now');
|
||||
$bestMatch = null;
|
||||
|
||||
foreach ($formats as $format) {
|
||||
|
|
@ -1137,8 +1141,8 @@ class InvoiceRecognition
|
|||
if (\stripos($bestMatch, 'S') > 1
|
||||
|| \stripos($bestMatch, 'O') > 1
|
||||
) {
|
||||
$subIban = \substr($bestMatch, 2);
|
||||
$subIban = \str_replace(['S', 'O'], ['5', '0'], $subIban);
|
||||
$subIban = \substr($bestMatch, 2);
|
||||
$subIban = \str_replace(['S', 'O'], ['5', '0'], $subIban);
|
||||
$bestMatch = \substr($bestMatch, 0, 2) . $subIban;
|
||||
}
|
||||
|
||||
|
|
@ -1221,9 +1225,10 @@ class InvoiceRecognition
|
|||
if (\stripos($bestMatch, 'S') > 1
|
||||
|| \stripos($bestMatch, 'O') > 1
|
||||
) {
|
||||
$format = IbanEnum::getByName('_' . \substr($bestMatch, 0, 2));
|
||||
/** @var string $format */
|
||||
$format = IbanEnum::getByName('_' . \substr($bestMatch, 0, 2)) ?? '';
|
||||
|
||||
$len = \strlen($bestMatch);
|
||||
$len = \strlen($bestMatch);
|
||||
$formatLen = \strlen($format);
|
||||
|
||||
for ($i = 0; $i < $len; ++$i) {
|
||||
|
|
@ -1245,12 +1250,18 @@ class InvoiceRecognition
|
|||
$bestMatch[$i] = '5';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return \trim($bestMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find country from bill
|
||||
*
|
||||
* @param string[] $lines Lines
|
||||
* @param array $matches Match patterns
|
||||
* @param string $language Bill language
|
||||
*/
|
||||
public static function findCountry(array $lines, array $matches, string $language) : string
|
||||
{
|
||||
$iban = self::findIban($lines, $matches['iban']);
|
||||
|
|
@ -1267,7 +1278,7 @@ class InvoiceRecognition
|
|||
return \strtoupper(\substr($vatId, 0, 2));
|
||||
}
|
||||
|
||||
$email = self::findEmail($lines, $matches['email']);
|
||||
$email = self::findEmail($lines, $matches['email']);
|
||||
$country = \strtoupper(\substr($email, \strrpos($email, '.') + 1));
|
||||
|
||||
if (ISO3166TwoEnum::isValidValue($country)) {
|
||||
|
|
@ -1286,9 +1297,18 @@ class InvoiceRecognition
|
|||
return empty($countries) ? 'US' : \reset($countries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find currency
|
||||
*
|
||||
* @param string[] $lines Lines
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function findCurrency(array $lines) : string
|
||||
{
|
||||
$symbols = ISO4217SymbolEnum::getConstants();
|
||||
$symbols = ISO4217SymbolEnum::getConstants();
|
||||
$currency = '';
|
||||
|
||||
foreach ($lines as $line) {
|
||||
|
|
@ -1299,8 +1319,11 @@ class InvoiceRecognition
|
|||
}
|
||||
|
||||
if (\strpos($line, $match) !== false) {
|
||||
/** @var string $currency */
|
||||
$currency = ISO4217SymbolEnum::getName($symbol);
|
||||
$currency = ISO4217CharEnum::getByName($currency);
|
||||
|
||||
/** @var string $currency */
|
||||
$currency = ISO4217CharEnum::getByName($currency) ?? '';
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ final class PriceMapper extends DataMapperFactory
|
|||
'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_status' => ['name' => 'billing_price_status', 'type' => 'int', 'internal' => 'status'],
|
||||
'billing_price_status' => ['name' => 'billing_price_status', 'type' => 'int', 'internal' => 'status'],
|
||||
'billing_price_quantity' => ['name' => 'billing_price_quantity', 'type' => 'Serializable', 'internal' => 'quantity'],
|
||||
'billing_price_price' => ['name' => 'billing_price_price', 'type' => 'Serializable', 'internal' => 'price'],
|
||||
'billing_price_price_new' => ['name' => 'billing_price_price_new', 'type' => 'Serializable', 'internal' => 'priceNew'],
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getSalesBeforePivot(
|
||||
mixed $pivot,
|
||||
|
|
@ -58,6 +59,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getSalesAfterPivot(
|
||||
mixed $pivot,
|
||||
|
|
@ -77,6 +79,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getSalesByItemId(int $id, \DateTime $start, \DateTime $end) : FloatInt
|
||||
{
|
||||
|
|
@ -98,6 +101,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getSalesByClientId(int $id, \DateTime $start, \DateTime $end) : FloatInt
|
||||
{
|
||||
|
|
@ -117,6 +121,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemAvgSalesPrice(int $item, \DateTime $start, \DateTime $end) : FloatInt
|
||||
{
|
||||
|
|
@ -133,7 +138,7 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return isset($result[0]['net_count'])
|
||||
? new FloatInt((int) ($result[0]['net_sales'] ?? 0) / ($result[0]['net_count']))
|
||||
|
|
@ -142,6 +147,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getLastOrderDateByItemId(int $id) : ?\DateTimeImmutable
|
||||
{
|
||||
|
|
@ -164,6 +170,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getLastOrderDateByClientId(int $id) : ?\DateTimeImmutable
|
||||
{
|
||||
|
|
@ -184,6 +191,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemRetentionRate(int $id, \DateTime $start, \DateTime $end) : float
|
||||
{
|
||||
|
|
@ -192,6 +200,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemLivetimeValue(int $id, \DateTime $start, \DateTime $end) : FloatInt
|
||||
{
|
||||
|
|
@ -200,6 +209,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getNewestItemInvoices(int $id, int $limit = 10) : array
|
||||
{
|
||||
|
|
@ -221,6 +231,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getNewestClientInvoices(int $id, int $limit = 10) : array
|
||||
{
|
||||
|
|
@ -240,6 +251,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemTopClients(int $id, \DateTime $start, \DateTime $end, int $limit = 10) : array
|
||||
{
|
||||
|
|
@ -280,6 +292,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemBills(int $id, \DateTime $start, \DateTime $end) : array
|
||||
{
|
||||
|
|
@ -308,6 +321,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getClientBills(int $id, string $language, \DateTime $start, \DateTime $end) : array
|
||||
{
|
||||
|
|
@ -323,6 +337,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getClientItem(int $client, \DateTime $start, \DateTime $end) : array
|
||||
{
|
||||
|
|
@ -336,6 +351,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemCountrySales(int $id, \DateTime $start, \DateTime $end, int $limit = 10) : array
|
||||
{
|
||||
|
|
@ -361,6 +377,7 @@ final class SalesBillMapper extends BillMapper
|
|||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemMonthlySalesCosts(array $items, \DateTime $start, \DateTime $end) : array
|
||||
{
|
||||
|
|
@ -388,11 +405,15 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return $result ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemMonthlySalesQuantity(array $items, \DateTime $start, \DateTime $end) : array
|
||||
{
|
||||
if (empty($items)) {
|
||||
|
|
@ -418,13 +439,14 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return $result ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getClientMonthlySalesCosts(int $client, \DateTime $start, \DateTime $end) : array
|
||||
{
|
||||
|
|
@ -444,11 +466,15 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return $result ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemNetSales(int $item, \DateTime $start, \DateTime $end) : FloatInt
|
||||
{
|
||||
$sql = <<<SQL
|
||||
|
|
@ -462,11 +488,15 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return new FloatInt((int) ($result[0]['net_sales'] ?? 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getILVHistoric(int $item) : FloatInt
|
||||
{
|
||||
$sql = <<<SQL
|
||||
|
|
@ -477,16 +507,24 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return new FloatInt((int) ($result[0]['net_sales'] ?? 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemMRR() : FloatInt
|
||||
{
|
||||
return new FloatInt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getItemLastOrder(int $item) : ?\DateTime
|
||||
{
|
||||
$sql = <<<SQL
|
||||
|
|
@ -499,13 +537,17 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return isset($result[0]['billing_bill_created_at'])
|
||||
? new \DateTime(($result[0]['billing_bill_created_at']))
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getClientNetSales(int $client, \DateTime $start, \DateTime $end) : FloatInt
|
||||
{
|
||||
$sql = <<<SQL
|
||||
|
|
@ -518,11 +560,15 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return new FloatInt((int) ($result[0]['net_sales'] ?? 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getCLVHistoric(int $client) : FloatInt
|
||||
{
|
||||
$sql = <<<SQL
|
||||
|
|
@ -532,16 +578,24 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return new FloatInt((int) ($result[0]['net_sales'] ?? 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getClientMRR() : FloatInt
|
||||
{
|
||||
return new FloatInt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder
|
||||
* @todo Implement
|
||||
*/
|
||||
public static function getClientLastOrder(int $client) : ?\DateTime
|
||||
{
|
||||
$sql = <<<SQL
|
||||
|
|
@ -553,7 +607,7 @@ final class SalesBillMapper extends BillMapper
|
|||
SQL;
|
||||
|
||||
$query = new Builder(self::$db);
|
||||
$result = $query->raw($sql)->execute()->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$result = $query->raw($sql)->execute()?->fetchAll(\PDO::FETCH_ASSOC) ?? [];
|
||||
|
||||
return isset($result[0]['billing_bill_created_at'])
|
||||
? new \DateTime(($result[0]['billing_bill_created_at']))
|
||||
|
|
|
|||
|
|
@ -40,24 +40,24 @@ final class TaxCombinationMapper extends DataMapperFactory
|
|||
* @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_tax1_account' => ['name' => 'billing_tax_tax1_account', 'type' => 'string', 'internal' => 'taxAccount1'],
|
||||
'billing_tax_tax2_account' => ['name' => 'billing_tax_tax2_account', 'type' => 'string', 'internal' => 'taxAccount2'],
|
||||
'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_cashback_account' => ['name' => 'billing_tax_cashback_account', 'type' => 'string', 'internal' => 'cashbackAccount'],
|
||||
'billing_tax_overpayment_account' => ['name' => 'billing_tax_overpayment_account', 'type' => 'string', 'internal' => 'overpaymentAccount'],
|
||||
'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_tax1_account' => ['name' => 'billing_tax_tax1_account', 'type' => 'string', 'internal' => 'taxAccount1'],
|
||||
'billing_tax_tax2_account' => ['name' => 'billing_tax_tax2_account', 'type' => 'string', 'internal' => 'taxAccount2'],
|
||||
'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_cashback_account' => ['name' => 'billing_tax_cashback_account', 'type' => 'string', 'internal' => 'cashbackAccount'],
|
||||
'billing_tax_overpayment_account' => ['name' => 'billing_tax_overpayment_account', 'type' => 'string', 'internal' => 'overpaymentAccount'],
|
||||
'billing_tax_underpayment_account' => ['name' => 'billing_tax_underpayment_account', 'type' => 'string', 'internal' => 'underpaymentAccount'],
|
||||
'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'],
|
||||
'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'],
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -83,7 +83,6 @@ final class TaxCombinationMapper extends DataMapperFactory
|
|||
'mapper' => TaxCodeMapper::class,
|
||||
'external' => 'billing_tax_code',
|
||||
'by' => 'abbr',
|
||||
'column' => 'abbr',
|
||||
],
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ return ['Billing' => [
|
|||
'AlreadyPaid' => 'Bereits bezahlt',
|
||||
'Amount' => 'Betrag',
|
||||
'Archive' => 'Archiev',
|
||||
'Internal' => 'Intern',
|
||||
'Internal' => 'Intern',
|
||||
'Error' => 'Fehler',
|
||||
'Billing' => 'Rechnungsstellung',
|
||||
'External' => 'Extern',
|
||||
'External' => 'Extern',
|
||||
'Bills' => 'Rechnungen',
|
||||
'Bonus' => 'Bonus',
|
||||
'Cashback' => 'Kennzeichnen',
|
||||
|
|
@ -54,7 +54,7 @@ return ['Billing' => [
|
|||
'MoneyTransfer' => 'Überweisung',
|
||||
'Name' => 'Name',
|
||||
'Net' => 'Netz',
|
||||
'Parse' => 'Rechnungserkennung',
|
||||
'Parse' => 'Rechnungserkennung',
|
||||
'Offer' => 'Angebot',
|
||||
'Original' => 'Original',
|
||||
'Payment' => 'Zahlung',
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ return ['Billing' => [
|
|||
'AlreadyPaid' => 'Already Paid',
|
||||
'Amount' => 'Amount',
|
||||
'Archive' => 'Archive',
|
||||
'Internal' => 'Internal',
|
||||
'Error' => 'Error',
|
||||
'Internal' => 'Internal',
|
||||
'Error' => 'Error',
|
||||
'Billing' => 'Billing',
|
||||
'External' => 'External',
|
||||
'External' => 'External',
|
||||
'Bills' => 'Bills',
|
||||
'Bonus' => 'Bonus',
|
||||
'Cashback' => 'Cash Back',
|
||||
|
|
@ -85,10 +85,9 @@ return ['Billing' => [
|
|||
'ShippingTerms' => 'Shipping Terms',
|
||||
'PaymentTerm' => 'Payment Term',
|
||||
'ShippingTerm' => 'Shipping Term',
|
||||
'ShippingTerm' => 'Shipping Term',
|
||||
'E_bill_items' => 'There is an issue with your bill items.',
|
||||
'E_bill_taxes' => 'The total tax amount doesn\'t match the tax amount of the elements. Potential issue with tax rates or additional items.',
|
||||
'E_bill_net' => 'The total net amount doesn\'t match the net amount of the elements. Potential issue with tax rates or additional items.',
|
||||
'E_bill_net' => 'The total net amount doesn\'t match the net amount of the elements. Potential issue with tax rates or additional items.',
|
||||
'E_bill_gross' => 'The total gross amount doesn\'t match the gross amount of the elements. Potential issue with tax rates or additional items.',
|
||||
'E_bill_unit' => 'Unit price doesn\'t match total price. Potential issues with price, quantity or discounts.',
|
||||
'E_bill_unit' => 'Unit price doesn\'t match total price. Potential issues with price, quantity or discounts.',
|
||||
]];
|
||||
|
|
|
|||
|
|
@ -441,7 +441,7 @@ echo $this->data['nav']->render(); ?>
|
|||
action="<?= UriFactory::build('{/api}bill/parse?id=' . $bill->id . '&async=0'); ?>"
|
||||
method="post"
|
||||
data-redirect="<?= UriFactory::build('{%}'); ?>">
|
||||
<input type="submit" value="<?= $this->getHtml('Parse') ?>">
|
||||
<input type="submit" value="<?= $this->getHtml('Parse'); ?>">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ final class InvoiceRecognitionTest extends \PHPUnit\Framework\TestCase
|
|||
|
||||
$element = [
|
||||
__DIR__ . '/bills/' . \implode('', $parts) . '.json',
|
||||
$content
|
||||
$content,
|
||||
];
|
||||
|
||||
self::$billList[] = $element;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user