Skip to content
Code-Schnipsel Gruppen Projekte
Commit c2eed069 erstellt von Henning Leutz's avatar Henning Leutz :martial_arts_uniform:
Dateien durchsuchen

refactor: money, discount, tests

Übergeordneter 2f6e45a1
Keine zugehörigen Branchen gefunden
Keine zugehörigen Tags gefunden
Keine zugehörigen Merge Requests gefunden
<?php
/**
* This file contains package_quiqqer_invoice_ajax_invoices_temporary_product_validatePrice
*/
/**
* Validate the price and return a validated price
*
* @param string|int|float $value
* @return string
*/
QUI::$Ajax->registerFunction(
'package_quiqqer_erp_ajax_money_validatePrice',
function ($value) {
return \QUI\ERP\Money\Price::validatePrice($value);
},
array('value'),
'Permission::checkAdminUser'
);
/**
* @module package/quiqqer/erp/bin/backend/utils/Discount
*/
define('package/quiqqer/erp/bin/backend/utils/Discount', function () {
"use strict";
return {
/**
* Unserialize a discount
*
* @param {String|Object} discount
* @return {Object|null}
*/
unserialize: function (discount) {
if (typeOf(discount) === 'number') {
return {
value: discount,
type : 2
};
}
if (!discount) {
return null;
}
if (discount === '') {
return null;
}
if (typeOf(discount) === 'object') {
if ("value" in discount && "type" in discount) {
return discount;
}
return null;
}
if (discount.toString().match('{')) {
try {
return this.unserialize(JSON.decode(discount));
} catch (e) {
console.error(e);
}
}
if (discount.toString().match('%')) {
return {
value: discount,
type : 1
};
}
return null;
},
/**
* Return the discount as string representation
*
* @param {Object} discount
* @return {String}
*/
parseToString: function (discount) {
if (!discount) {
return null;
}
if (!("value" in discount) || !("type" in discount)) {
return '';
}
if (parseInt(discount.type) === 1) {
return discount.value + '%';
}
return discount.value;
}
};
});
\ No newline at end of file
/**
* @module package/quiqqer/erp/bin/backend/utils/Money
*/
define('package/quiqqer/erp/bin/backend/utils/Money', function () {
"use strict";
return {
/**
* Validate the price and return a validated price
*
* @param {String|Number} value
* @return {Promise}
*/
validatePrice: function (value) {
return new Promise(function (resolve) {
require(['Ajax'], function (QUIAjax) {
QUIAjax.get('package_quiqqer_erp_ajax_money_validatePrice', resolve, {
'package': 'quiqqer/erp',
value : value
});
});
});
}
};
});
\ No newline at end of file
......@@ -27,44 +27,78 @@ class Article implements ArticleInterface
protected $calculated = false;
/**
* @var
* @var float|int
*/
protected $price;
/**
* @var
* @var float|int
*/
protected $basisPrice;
/**
* @var
* @var float|int
*/
protected $sum;
/**
* @var
* The calculated netto sum with quantity and discount
* @var float|int
*/
protected $nettoSum;
/**
* @var
* Sum from the article, without discount and with quantity
*
* @var float|int
*/
protected $nettoSubSum;
/**
* The article netto price, without discount, without quantity
* comes from article
*
* @var float|int
*/
protected $nettoPrice;
/**
* The article netto price, without discount, without quantity
* comes from calc
*
* @var float|int
*/
protected $nettoBasisPrice;
/**
* @var array
*/
protected $vatArray;
/**
* @var
* @var bool
*/
protected $isEuVat;
/**
* @var
* @var bool
*/
protected $isNetto;
/**
* @var ArticleDiscount|null
*/
protected $Discount = null;
/**
* @var null|QUI\Interfaces\Users\User
*/
protected $User = null;
/**
* Article constructor.
*
* @param array $attributes - (id, articleNo, title, description, unitPrice, quantity)
* @param array $attributes - (id, articleNo, title, description, unitPrice, quantity, discount)
*/
public function __construct($attributes = array())
{
......@@ -96,6 +130,10 @@ public function __construct($attributes = array())
$this->attributes['vat'] = $attributes['vat'];
}
if (isset($attributes['discount'])) {
$this->Discount = ArticleDiscount::unserialize($attributes['discount']);
}
if (isset($attributes['calculated'])) {
$calc = $attributes['calculated'];
......@@ -103,10 +141,14 @@ public function __construct($attributes = array())
$this->price = $calc['price'];
$this->basisPrice = $calc['basisPrice'];
$this->sum = $calc['sum'];
$this->nettoSum = $calc['nettoSum'];
$this->vatArray = $calc['vatArray'];
$this->isEuVat = $calc['isEuVat'];
$this->isNetto = $calc['isNetto'];
$this->nettoPrice = $calc['nettoPrice'];
$this->nettoBasisPrice = $calc['nettoBasisPrice'];
$this->nettoSum = $calc['nettoSum'];
$this->vatArray = $calc['vatArray'];
$this->isEuVat = $calc['isEuVat'];
$this->isNetto = $calc['isNetto'];
}
}
......@@ -216,12 +258,21 @@ public function getVat()
}
/**
* @return null
* @todo implement
* @return null|QUI\Interfaces\Users\User
*/
public function getUser()
{
return null;
return $this->User;
}
/**
* Set the user to the product, this user will be used for the calculation
*
* @param QUI\Interfaces\Users\User $User
*/
public function setUser(QUI\Interfaces\Users\User $User)
{
$this->User = $User;
}
/**
......@@ -253,11 +304,57 @@ public function getPrice()
);
}
//region Discounts
/**
* @param null|Calc $Calc
* Set a discount to the article
*
* @param int $discount
* @param int $discountType - default = complement
*
* @todo überdenken, ganzer artikel ist eigentlich nicht änderbar
*/
public function setDiscount($discount, $discountType = Calc::CALCULATION_COMPLEMENT)
{
switch ($discountType) {
case Calc::CALCULATION_PERCENTAGE:
case Calc::CALCULATION_COMPLEMENT:
break;
default:
$discountType = Calc::CALCULATION_COMPLEMENT;
}
$this->Discount = new ArticleDiscount($discount, $discountType);
}
/**
* Return the current discount
*
* @return ArticleDiscount|null
*/
public function getDiscount()
{
return $this->Discount;
}
/**
* Has the article a discount
*
* @return bool
*/
public function hasDiscount()
{
return !!$this->getDiscount();
}
//endregion
/**
* @param null|Calc|QUI\ERP\User $Instance
* @return self
*/
public function calc($Calc = null)
public function calc($Instance = null)
{
if ($this->calculated) {
return $this;
......@@ -265,18 +362,27 @@ public function calc($Calc = null)
$self = $this;
if (!$Calc) {
$Calc = Calc::getInstance($this->getUser());
if ($Instance instanceof QUI\ERP\User) {
$Calc = Calc::getInstance($Instance);
} elseif ($Instance instanceof Calc) {
$Calc = $Instance;
} else {
$Calc = Calc::getInstance();
}
$Calc->calcArticlePrice($this, function ($data) use ($self) {
$self->price = $data['price'];
$self->basisPrice = $data['basisPrice'];
$self->sum = $data['sum'];
$self->nettoSum = $data['nettoSum'];
$self->vatArray = $data['vatArray'];
$self->isEuVat = $data['isEuVat'];
$self->isNetto = $data['isNetto'];
$self->nettoPrice = $data['nettoPrice'];
$self->nettoBasisPrice = $data['nettoBasisPrice'];
$self->nettoSubSum = $data['nettoSubSum'];
$self->nettoSum = $data['nettoSum'];
$self->vatArray = $data['vatArray'];
$self->isEuVat = $data['isEuVat'];
$self->isNetto = $data['isNetto'];
$self->calculated = true;
});
......@@ -291,28 +397,39 @@ public function calc($Calc = null)
*/
public function toArray()
{
$vat = false;
$vat = false;
$discount = '';
if (isset($this->attributes['vat'])) {
$vat = (int)$this->attributes['vat'];
}
if ($this->hasDiscount()) {
$discount = $this->Discount->toJSON();
}
return array(
'title' => $this->getTitle(),
'articleNo' => $this->getArticleNo(),
'description' => $this->getDescription(),
'unitPrice' => $this->getUnitPrice(),
'quantity' => $this->getQuantity(),
'sum' => $this->getSum(),
'vat' => $vat,
'calculated_basisPrice' => $this->basisPrice,
'calculated_price' => $this->price,
'calculated_sum' => $this->sum,
'calculated_nettoSum' => $this->nettoSum,
'calculated_isEuVat' => $this->isEuVat,
'calculated_isNetto' => $this->isNetto,
'calculated_vatArray' => $this->vatArray
// article data
'title' => $this->getTitle(),
'articleNo' => $this->getArticleNo(),
'description' => $this->getDescription(),
'unitPrice' => $this->getUnitPrice(),
'quantity' => $this->getQuantity(),
'sum' => $this->getSum(),
'vat' => $vat,
'discount' => $discount,
// calculated data
'calculated_basisPrice' => $this->basisPrice,
'calculated_price' => $this->price,
'calculated_sum' => $this->sum,
'calculated_nettoBasisPrice' => $this->nettoBasisPrice,
'calculated_nettoPrice' => $this->nettoPrice,
'calculated_nettoSubSum' => $this->nettoSubSum,
'calculated_nettoSum' => $this->nettoSum,
'calculated_isEuVat' => $this->isEuVat,
'calculated_isNetto' => $this->isNetto,
'calculated_vatArray' => $this->vatArray
);
}
}
<?php
/**
* This file contains QUI\ERP\Accounting\ArticleDiscount
*/
namespace QUI\ERP\Accounting;
use QUI;
use QUI\ERP\Currency\Currency;
use QUI\ERP\Currency\Handler as CurrencyHandler;
/**
* Class ArticleDiscount
*
* @package QUI\ERP\Accounting
*/
class ArticleDiscount
{
/**
* Calculation type, % or complement (currency value)
*
* @var int
*/
protected $type = Calc::CALCULATION_COMPLEMENT;
/**
* Discount value
*
* @var int
*/
protected $value = 0;
/**
* Return the currency
*
* @var null|Currency
*/
protected $Currency = null;
/**
* ArticleDiscount constructor.
*
* @param int $discount
* @param int $type
*/
public function __construct($discount, $type)
{
switch ($type) {
case Calc::CALCULATION_PERCENTAGE:
case Calc::CALCULATION_COMPLEMENT:
break;
default:
$type = Calc::CALCULATION_COMPLEMENT;
}
$this->type = $type;
$this->value = $discount;
}
/**
* Unserialize a discount string
*
* The string can be in the following format:
* - 10%
* - 10€
* - 10
* - {"value": 10, "type": 1}
*
* @param string $string
* @return null|ArticleDiscount
*/
public static function unserialize($string)
{
$data = array();
if (is_numeric($string)) {
// number, float, int -> 5.99
$data['value'] = QUI\ERP\Money\Price::validatePrice($string);
$data['type'] = Calc::CALCULATION_COMPLEMENT;
} elseif (strpos($string, '{') !== false || strpos($string, '[') !== false) {
// json string
$data = json_decode($string, true);
if (!is_array($data)) {
return null;
}
} else {
// is normal string 5% or 5.99 €
if (strpos($string, '%') !== false) {
$data['value'] = (int)str_replace('%', '', $string);
$data['type'] = Calc::CALCULATION_PERCENTAGE;
} else {
$data['value'] = QUI\ERP\Money\Price::validatePrice($string);
$data['type'] = Calc::CALCULATION_COMPLEMENT;
}
}
if (!isset($data['value']) || !isset($data['type'])) {
return null;
}
$Discount = new self($data['value'], $data['type']);
// discount
if (isset($data['currency']) && isset($data['currency']['code'])) {
try {
$Discount->setCurrency(
CurrencyHandler::getCurrency($data['currency']['code'])
);
} catch (QUI\Exception $Exception) {
}
}
return $Discount;
}
/**
* Return the discount type, % or complement (currency value)
*
* Calc::CALCULATION_COMPLEMENT => 2 = €
* Calc::CALCULATION_PERCENTAGE => 1 = %
*
* @return int
*/
public function getDiscountType()
{
return $this->type;
}
/**
* Alias for getDiscountType()
* For better understanding in the calculation API
*
* @return int
*/
public function getCalculation()
{
return $this->getDiscountType();
}
/**
* Return the discount value
*
* @return int
*/
public function getValue()
{
return $this->value;
}
/**
* Return the Discount Currency
*
* @return Currency
*/
public function getCurrency()
{
if ($this->Currency === null) {
return QUI\ERP\Defaults::getCurrency();
}
return $this->Currency;
}
/**
* Set the currency
*
* @param Currency $Currency
*/
public function setCurrency(Currency $Currency)
{
$this->Currency = $Currency;
}
/**
* Parse the discount to an array
*
* @return array
*/
public function toArray()
{
return array(
'value' => $this->value,
'type' => $this->type,
'currency' => $this->getCurrency()->toArray()
);
}
/**
* Parse the discount to a json representation
*
* @return string
*/
public function toJSON()
{
return json_encode($this->toArray());
}
}
\ No newline at end of file
......@@ -106,9 +106,7 @@ public function __construct(array $attributes = array())
*/
public function setUser(QUI\Interfaces\Users\User $User)
{
if (QUI::getUsers()->isUser($User)) {
$this->User = $User;
}
$this->User = $User;
}
/**
......@@ -188,6 +186,10 @@ public function calc($Calc = null)
public function addArticle(Article $Article)
{
$this->articles[] = $Article;
if ($this->User) {
$Article->setUser($this->User);
}
}
/**
......
......@@ -76,10 +76,10 @@ public function __construct($User = false)
/**
* Static instance create
*
* @param UserInterface|bool $User - optional
* @param UserInterface|null $User - optional
* @return Calc
*/
public static function getInstance($User = false)
public static function getInstance($User = null)
{
if (!$User && QUI::isBackend()) {
$User = QUI::getUsers()->getSystemUser();
......@@ -215,7 +215,7 @@ public function calcArticleList(ArticleList $List, $callback = false)
}
$callback(array(
'sum' => $isNetto ? $nettoSum : $bruttoSum,
'sum' => $bruttoSum,
'subSum' => $subSum,
'nettoSum' => $nettoSum,
'nettoSubSum' => $nettoSubSum,
......@@ -251,6 +251,27 @@ public function calcArticlePrice(Article $Article, $callback = false)
$nettoPrice = $Article->getUnitPrice();
$vat = $Article->getVat();
$basisNettoPrice = $nettoPrice;
$nettoSubSum = $this->round($nettoPrice * $Article->getQuantity());
// discounts
$Discount = $Article->getDiscount();
if ($Discount) {
switch ($Discount->getCalculation()) {
// einfache Zahl, Währung --- kein Prozent
case Calc::CALCULATION_COMPLEMENT:
$nettoPrice = $nettoPrice + ($Discount->getValue() / $Article->getQuantity());
// $Discount->setNettoSum($this, $Discount->getValue());
break;
// Prozent Angabe
case Calc::CALCULATION_PERCENTAGE:
$percentage = $Discount->getValue() / 100 * $nettoPrice;
$nettoPrice = $nettoPrice + $percentage;
// $Discount->setNettoSum($this, $percentage);
break;
}
}
$vatSum = $nettoPrice * ($vat / 100);
$bruttoPrice = $this->round($nettoPrice + $vatSum);
......@@ -270,35 +291,31 @@ public function calcArticlePrice(Article $Article, $callback = false)
'text' => $this->getVatText($vat, $this->getUser())
);
QUI\ERP\Debug::getInstance()->log(
'Kalkulierter Artikel Preis ' . $Article->getId(),
'quiqqer/erp'
);
QUI\ERP\Debug::getInstance()->log(array(
'basisPrice' => $basisPrice,
'price' => $price,
'sum' => $sum,
'nettoSum' => $nettoSum,
'vatArray' => $vatArray,
'isEuVat' => $isEuVatUser,
'isNetto' => $isNetto,
'currencyData' => $this->getCurrency()->toArray()
), 'quiqqer/erp');
$data = array(
'basisPrice' => $basisPrice,
'price' => $price,
'sum' => $sum,
'nettoBasisPrice' => $basisNettoPrice,
'nettoPrice' => $nettoPrice,
'nettoSubSum' => $nettoSubSum,
'nettoSum' => $nettoSum,
$callback(array(
'basisPrice' => $basisPrice,
'price' => $price,
'sum' => $sum,
'nettoSum' => $nettoSum,
'currencyData' => $this->getCurrency()->toArray(),
'vatArray' => $vatArray,
'vatText' => $vatArray['text'],
'isEuVat' => $isEuVatUser,
'isNetto' => $isNetto,
'currencyData' => $this->getCurrency()->toArray()
));
'isNetto' => $isNetto
);
QUI\ERP\Debug::getInstance()->log($data, 'quiqqer/erp');
$callback($data);
return $Article->getPrice();
}
......
......@@ -33,4 +33,14 @@ public static function getArea()
/* @var $Area QUI\ERP\Areas\Area */
return $Area;
}
/**
* Return the default currency
*
* @return Currency\Currency
*/
public static function getCurrency()
{
return QUI\ERP\Currency\Handler::getDefaultCurrency();
}
}
\ No newline at end of file
......@@ -96,9 +96,6 @@ public static function onUserSave(QUI\Interfaces\Users\User $User)
// netto brutto user status
$User->setAttribute('quiqqer.erp.isNettoUser', false); // reset status
QUI\System\Log::write($User->getName());
QUI\System\Log::write(QUI\ERP\Utils\User::getBruttoNettoUserStatus($User));
$User->setAttribute(
'quiqqer.erp.isNettoUser',
QUI\ERP\Utils\User::getBruttoNettoUserStatus($User)
......
......@@ -46,12 +46,12 @@ class Price
/**
* @var string
*/
protected $decimalSeparator = ',';
protected static $decimalSeparator = ',';
/**
* @var string
*/
protected $thousandsSeparator = '.';
protected static $thousandsSeparator = '.';
/**
* Price constructor.
......@@ -208,45 +208,50 @@ public function isStartingPrice()
* @param number|string $value
* @return float|double|int|null
*/
protected function validatePrice($value)
public static function validatePrice($value)
{
if (is_float($value)) {
return round($value, 4);
}
$value = (string)$value;
$value = (string)$value;
$isNegative = substr($value, 0, 1) === '-';
// value cleanup
$value = preg_replace('#[^\d,.]#i', '', $value);
if (trim($value) === '') {
return null;
}
$decimal = mb_strpos($value, $this->decimalSeparator);
$thousands = mb_strpos($value, $this->thousandsSeparator);
$negativeTurn = 1;
if ($isNegative) {
$negativeTurn = -1;
}
$decimal = mb_strpos($value, self::$decimalSeparator);
$thousands = mb_strpos($value, self::$thousandsSeparator);
if ($thousands === false && $decimal === false) {
return round(floatval($value), 4);
return round(floatval($value), 4) * $negativeTurn;
}
if ($thousands !== false && $decimal === false) {
if (mb_substr($value, -4, 1) === $this->thousandsSeparator) {
$value = str_replace($this->thousandsSeparator, '', $value);
if (mb_substr($value, -4, 1) === self::$thousandsSeparator) {
$value = str_replace(self::$thousandsSeparator, '', $value);
}
}
if ($thousands === false && $decimal !== false) {
$value = str_replace(
$this->decimalSeparator,
'.',
$value
);
$value = str_replace(self::$decimalSeparator, '.', $value);
}
if ($thousands !== false && $decimal !== false) {
$value = str_replace($this->thousandsSeparator, '', $value);
$value = str_replace($this->decimalSeparator, '.', $value);
$value = str_replace(self::$thousandsSeparator, '', $value);
$value = str_replace(self::$decimalSeparator, '.', $value);
}
return round(floatval($value), 4);
return round(floatval($value), 4) * $negativeTurn;
}
}
<?php
namespace QUITests\ERP\Money;
use QUI;
/**
* Class BruttoUserTest
*/
class PriceTest extends \PHPUnit_Framework_TestCase
{
public function testValidatePrice()
{
$this->assertEquals(
10,
QUI\ERP\Money\Price::validatePrice(10)
);
$this->assertEquals(
-10,
QUI\ERP\Money\Price::validatePrice(-10)
);
$this->assertEquals(
'10.00',
QUI\ERP\Money\Price::validatePrice(10)
);
$this->assertEquals(
'10.10',
QUI\ERP\Money\Price::validatePrice(10.1)
);
$this->assertEquals(
'5.99',
QUI\ERP\Money\Price::validatePrice(5.99)
);
$this->assertEquals(
'-5.99',
QUI\ERP\Money\Price::validatePrice(-5.99)
);
$this->assertEquals(
'5.9999',
QUI\ERP\Money\Price::validatePrice(5.9999)
);
}
}
\ No newline at end of file
<?php
// include quiqqer bootstrap for tests
require dirname(dirname(dirname(__FILE__))) . '/quiqqer/tests/bootstrap.php';
0% Lade oder .
You are about to add 0 people to the discussion. Proceed with caution.
Bearbeitung dieser Nachricht zuerst beenden!
Bitte registrieren oder zum Kommentieren