112 lines
3.0 KiB
PHP
112 lines
3.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MonetaPhpShop\receipt;
|
|
|
|
use InvalidArgumentException;
|
|
use MonetaPhpShop\Money;
|
|
|
|
/**
|
|
* Êîëè÷åñòâî ïîçèöèè ÷åêà ñ òî÷íîñòüþ êàññû — òðè çíàêà ïîñëå òî÷êè (`kassaspecification.pdf`,
|
|
* â ñõåìå Check URL `multipleOf 0.001`). Õðàíèòñÿ öåëûì ÷èñëîì òûñÿ÷íûõ, êàê êîïåéêè â {@see Money}:
|
|
* Êîðçèíà ÿäðà õðàíèò êîëè÷åñòâî êàê åñòü.
|
|
*/
|
|
final class Quantity
|
|
{
|
|
private const SCALE = 1000;
|
|
|
|
/** Ïîòîëîê êàññû: 99999.999. */
|
|
public const MAX_THOUSANDTHS = 99999999;
|
|
|
|
/** @var int */
|
|
private $thousandths;
|
|
|
|
private function __construct(int $thousandths)
|
|
{
|
|
if (($thousandths < 1) || ($thousandths > self::MAX_THOUSANDTHS)) {
|
|
throw new InvalidArgumentException('Êîëè÷åñòâî ïîçèöèè ÷åêà âíå ãðàíèö êàññû.');
|
|
}
|
|
|
|
$this->thousandths = $thousandths;
|
|
}
|
|
|
|
public static function pieces(int $pieces): self
|
|
{
|
|
return new self($pieces * self::SCALE);
|
|
}
|
|
|
|
public static function fromThousandths(int $thousandths): self
|
|
{
|
|
return new self($thousandths);
|
|
}
|
|
|
|
/**
|
|
* Êîëè÷åñòâî èç êîðçèíû çàêàçà (`num`): ÷èñëî èëè ÷èñëîâàÿ ñòðîêà, îêðóãëÿåòñÿ äî òûñÿ÷íûõ.
|
|
* Íîëü, îòðèöàòåëüíîå, íå ÷èñëî èëè áîëüøå ïîòîëêà êàññû — null.
|
|
*
|
|
* @param mixed $raw
|
|
*/
|
|
public static function fromCart($raw): ?self
|
|
{
|
|
if (is_string($raw)) {
|
|
$raw = trim($raw);
|
|
}
|
|
if (!is_int($raw) && !is_float($raw) && !(is_string($raw) && is_numeric($raw))) {
|
|
return null;
|
|
}
|
|
$thousandths = (int) round((float) $raw * self::SCALE);
|
|
if (($thousandths < 1) || ($thousandths > self::MAX_THOUSANDTHS)) {
|
|
return null;
|
|
}
|
|
|
|
return new self($thousandths);
|
|
}
|
|
|
|
public function getThousandths(): int
|
|
{
|
|
return $this->thousandths;
|
|
}
|
|
|
|
/**
|
|
* Öåëîå ëè êîëè÷åñòâî: òîëüêî òàêóþ ñòðîêó ìîæíî ðåçàòü íà äâå ñ ðàçíèöåé öåí â êîïåéêó.
|
|
*/
|
|
public function isWhole(): bool
|
|
{
|
|
return $this->thousandths % self::SCALE === 0;
|
|
}
|
|
|
|
/**
|
|
* Öåëîå ÷èñëî øòóê; òîëüêî äëÿ {@see self::isWhole()}.
|
|
*/
|
|
public function getPieces(): int
|
|
{
|
|
return intdiv($this->thousandths, self::SCALE);
|
|
}
|
|
|
|
/**
|
|
* Äåñÿòè÷íàÿ çàïèñü áåç ëèøíèõ íóëåé: `2`, `2.5`, `0.125`.
|
|
*/
|
|
public function toDecimal(): string
|
|
{
|
|
if ($this->isWhole()) {
|
|
return (string) $this->getPieces();
|
|
}
|
|
|
|
return rtrim(
|
|
sprintf('%d.%03d', intdiv($this->thousandths, self::SCALE), $this->thousandths % self::SCALE),
|
|
'0',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Ñóììà ïîçèöèè: öåíà x êîëè÷åñòâî, îêðóãëåíèå äî êîïåéêè (ïîëîâèíà — ââåðõ).
|
|
*/
|
|
public function totalOf(Money $price): Money
|
|
{
|
|
return Money::fromMinorUnits(
|
|
intdiv(($price->toMinorUnits() * $this->thousandths) + intdiv(self::SCALE, 2), self::SCALE),
|
|
);
|
|
}
|
|
}
|