code fixes

This commit is contained in:
Dennis Eichhorn 2024-03-15 20:24:37 +00:00
parent 3cd6a79222
commit e70898dbd5
24 changed files with 967 additions and 281 deletions

View File

@ -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);
}

View File

@ -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",

View File

@ -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,

View File

@ -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)",

View File

@ -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);

View File

@ -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,7 +112,7 @@ 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
@ -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,7 +218,7 @@ 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)
@ -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;
$container = $request->hasData('container')
? new NullContainer((int) $request->getData('container'))
: null;
$attr = new NullAttribute();
if ($bill->type->transferType === BillTransferType::PURCHASE) {
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

View File

@ -46,7 +46,23 @@ 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();
@ -54,7 +70,7 @@ final class ApiPriceController extends Controller
// 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')
@ -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;

View File

@ -85,6 +85,8 @@ final class ApiPurchaseController extends Controller
* Method to create item attribute from request.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param array $data Generic data
*
* @return array
*

View File

@ -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;
@ -47,15 +48,25 @@ 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 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;
}
/**
* 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) {

View File

@ -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);

View File

@ -145,7 +145,7 @@ final class CliController extends Controller
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()
@ -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
*

View File

@ -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.
*
@ -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) {

View File

@ -123,6 +123,8 @@ class BillElement implements \JsonSerializable
public ?string $costobject = null;
public ?TaxCombination $taxCombination = null;
/**
* Tax amount
*
@ -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,11 +295,25 @@ 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;
@ -284,6 +324,13 @@ class BillElement implements \JsonSerializable
&& \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)
@ -333,9 +401,10 @@ 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 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
{
@ -361,6 +430,7 @@ class BillElement implements \JsonSerializable
$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,

View File

@ -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;
@ -65,6 +66,7 @@ final class BillElementMapper extends DataMapperFactory
'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_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'],
@ -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',
],
];
/**

View File

@ -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,7 +74,7 @@ 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);
$countryCurrency = ISO4217CharEnum::currencyFromCountry($bill->billCountry);
@ -73,7 +83,7 @@ class InvoiceRecognition
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();
@ -275,7 +285,6 @@ class InvoiceRecognition
$totalGross += $element->totalSalesPriceGross->value;
}
$bill->grossSales = new FloatInt($totalGross);
$bill->netCosts = new FloatInt($totalNet);
$bill->netSales = $bill->netCosts;
@ -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
*
@ -680,8 +689,6 @@ class InvoiceRecognition
$bestDiscount = 0;
$found = [];
$discountLine = 0;
foreach ($matches['total_discount'][$language] as $match) {
foreach ($lines as $idx => $line) {
if ($idx < $lineStart) {
@ -822,8 +829,6 @@ class InvoiceRecognition
$bestSurcharge = 0;
$found = [];
$surchargeLine = 0;
foreach ($matches['total_surcharge'][$language] as $match) {
foreach ($lines as $idx => $line) {
if ($idx < $lineStart) {
@ -849,7 +854,6 @@ class InvoiceRecognition
if ($surcharge > $bestSurcharge) {
$bestSurcharge = $surcharge;
$surchargeLine = $idx;
break;
}
@ -981,7 +985,7 @@ class InvoiceRecognition
{
if ((!empty($supplierFormat))) {
$dt = \DateTime::createFromFormat(
$supplierFormat ?? '',
$supplierFormat,
$date
);
@ -1221,7 +1225,8 @@ 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);
$formatLen = \strlen($format);
@ -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']);
@ -1286,6 +1297,15 @@ 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();
@ -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;
}

View File

@ -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']))

View File

@ -83,7 +83,6 @@ final class TaxCombinationMapper extends DataMapperFactory
'mapper' => TaxCodeMapper::class,
'external' => 'billing_tax_code',
'by' => 'abbr',
'column' => 'abbr',
],
];

View File

@ -85,7 +85,6 @@ 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.',

View File

@ -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>

View File

@ -336,7 +336,7 @@ final class InvoiceRecognitionTest extends \PHPUnit\Framework\TestCase
$element = [
__DIR__ . '/bills/' . \implode('', $parts) . '.json',
$content
$content,
];
self::$billList[] = $element;