_customer)) { $this->_customer = new ReceiptCustomer(); } return $this->_customer; } /** * Устанавливает информацию о плательщике. * * @param ReceiptCustomer|array|null $customer * * @return self */ public function setCustomer(mixed $customer = null): self { $this->_customer = $this->validatePropertyValue('_customer', $customer); return $this; } /** * Возвращает список позиций в текущем чеке. * * @return ReceiptItemInterface[]|ListObjectInterface Список товаров в заказе */ public function getItems(): ListObjectInterface { if ($this->_items === null) { $this->_items = new ListObject(ReceiptItem::class); } return $this->_items; } /** * Устанавливает список позиций в чеке. * * Если до этого в чеке уже были установлены значения, они удаляются и полностью заменяются переданным списком * позиций. Все передаваемые значения в массиве позиций должны быть объектами класса, реализующего интерфейс * ReceiptItemInterface, в противном случае будет выброшено исключение InvalidPropertyValueTypeException. * * @param array|ListObjectInterface $items Список товаров в заказе * * @throws EmptyPropertyValueException Выбрасывается если передали пустой массив значений * @throws InvalidPropertyValueTypeException Выбрасывается если в качестве значения был передан не массив и не * итератор, либо если одно из переданных значений не реализует интерфейс ReceiptItemInterface * * @return self */ public function setItems(mixed $items): self { $items = $this->validatePropertyValue('_items', $items); $this->_items = null; $this->_shippingItems = null; foreach ($items->getItems() as $val) { $this->addItem($val); } return $this; } /** * Обнуляет список позиций в чеке. * * Если до этого в чеке уже были установлены значения, они удаляются. * @return self */ public function removeItems(): self { $this->getItems()->clear(); return $this; } /** * Добавляет товар в чек. * * @param ReceiptItemInterface[]|ListObjectInterface $value Объект добавляемой в чек позиции */ public function addItem(mixed $value): void { if ($value->isShipping()) { $this->getShippingItems()->add($value); } $this->getItems()->add($value); } /** * Возвращает массив оплат, обеспечивающих выдачу товара. * * @return SettlementInterface[]|ListObjectInterface Массив оплат, обеспечивающих выдачу товара */ public function getSettlements(): ListObjectInterface { if ($this->_settlements === null) { $this->_settlements = new ListObject(Settlement::class); } return $this->_settlements; } /** * Возвращает массив оплат, обеспечивающих выдачу товара. * * @param ListObjectInterface|array|null $settlements * * @return self */ public function setSettlements(mixed $settlements = null): self { $this->_settlements = $this->validatePropertyValue('_settlements', $settlements); return $this; } /** * Добавляет оплату в чек. * * @param SettlementInterface $value Оплата * * @return self */ public function addSettlement(SettlementInterface $value): self { $this->getSettlements()->add($value); return $this; } /** * Возвращает список товаров в заказе, являющихся доставкой * * @return ReceiptItemInterface[]|ListObjectInterface Список товаров в заказе, являющихся доставкой */ public function getShippingItems(): ListObjectInterface { if ($this->_shippingItems === null) { $this->_shippingItems = new ListObject(ReceiptItem::class); } return $this->_shippingItems; } /** * Возвращает код системы налогообложения. * * @return int|null Код системы налогообложения. Число 1-6. */ public function getTaxSystemCode(): ?int { return $this->_tax_system_code; } /** * Устанавливает код системы налогообложения. * * @param int|null $tax_system_code Код системы налогообложения. Число 1-6 * * @return self */ public function setTaxSystemCode(?int $tax_system_code): self { $this->_tax_system_code = $this->validatePropertyValue('_tax_system_code', $tax_system_code); return $this; } /** * Возвращает дополнительный реквизит пользователя. * * @return AdditionalUserProps|null Дополнительный реквизит пользователя */ public function getAdditionalUserProps(): ?AdditionalUserProps { return $this->_additional_user_props; } /** * Устанавливает дополнительный реквизит пользователя. * * @param AdditionalUserProps|array $additional_user_props Дополнительный реквизит пользователя * * @return self */ public function setAdditionalUserProps(mixed $additional_user_props = null): self { $this->_additional_user_props = $this->validatePropertyValue('_additional_user_props', $additional_user_props); return $this; } /** * Возвращает отраслевой реквизит чека. * * @return IndustryDetails[]|ListObjectInterface Отраслевой реквизит чека */ public function getReceiptIndustryDetails(): ListObjectInterface { if ($this->_receipt_industry_details === null) { $this->_receipt_industry_details = new ListObject(IndustryDetails::class); } return $this->_receipt_industry_details; } /** * Устанавливает отраслевой реквизит чека. * * @param array|ListObjectInterface|null $receipt_industry_details Отраслевой реквизит чека * * @throws InvalidPropertyValueTypeException Выбрасывается если переданный аргумент - не массив * * @return self */ public function setReceiptIndustryDetails(mixed $receipt_industry_details = null): self { $this->_receipt_industry_details = $this->validatePropertyValue('_receipt_industry_details', $receipt_industry_details); return $this; } /** * Добавляет отраслевой реквизит чека. * * @param IndustryDetails|array $value Отраслевой реквизит чека. * * @return self */ public function addReceiptIndustryDetails(mixed $value): self { $this->getReceiptIndustryDetails()->add($value); return $this; } /** * Возвращает операционный реквизит чека. * * @return OperationalDetails|null Операционный реквизит чека */ public function getReceiptOperationalDetails(): ?OperationalDetails { return $this->_receipt_operational_details; } /** * Устанавливает операционный реквизит чека. * * @param array|OperationalDetails|null $receipt_operational_details Операционный реквизит чека * * @throws InvalidPropertyValueTypeException Выбрасывается если переданный аргумент - не массив * * @return self */ public function setReceiptOperationalDetails(mixed $receipt_operational_details = null): self { $this->_receipt_operational_details = $this->validatePropertyValue('_receipt_operational_details', $receipt_operational_details); return $this; } /** * Проверяет есть ли в чеке хотя бы одна позиция. * * @return bool True если чек не пуст, false если в чеке нет ни одной позиции */ public function notEmpty(): bool { return !$this->getItems()->isEmpty(); } /** * Возвращает стоимость заказа исходя из состава чека. * * @param bool $withShipping Добавить ли к стоимости заказа стоимость доставки * * @return int Общая стоимость заказа в центах/копейках */ public function getAmountValue(bool $withShipping = true): int { $result = 0; foreach ($this->getItems() as $item) { if ($withShipping || !$item->isShipping()) { $result += $item->getAmount(); } } return $result; } /** * Возвращает стоимость доставки исходя из состава чека. * * @return int Стоимость доставки из состава чека в центах/копейках */ public function getShippingAmountValue(): int { $result = 0; foreach ($this->getItems() as $item) { if ($item->isShipping()) { $result += $item->getAmount(); } } return $result; } /** * Подгоняет стоимость товаров в чеке к общей цене заказа. * * @param AmountInterface $orderAmount Общая стоимость заказа * @param bool $withShipping Поменять ли заодно и цену доставки */ public function normalize(AmountInterface $orderAmount, bool $withShipping = false): void { $amount = $orderAmount->getIntegerValue(); if (!$withShipping && !$this->getShippingItems()->isEmpty()) { if ($amount > $this->getShippingAmountValue()) { $amount -= $this->getShippingAmountValue(); } else { $withShipping = true; } } $realAmount = $this->getAmountValue($withShipping); if ($realAmount !== $amount) { $coefficient = (float) $amount / (float) $realAmount; $items = []; $realAmount = 0; foreach ($this->getItems() as $item) { if ($withShipping || !$item->isShipping()) { $price = round($coefficient * $item->getPrice()->getIntegerValue()); if ($price < 1.0) { if ($item->getPrice()->getIntegerValue() > 1) { $item->getPrice()->setValue(0.01); } $amount -= $item->getAmount(); } else { $items[] = $item; $realAmount += $item->getAmount(); } } } uasort($items, static function (ReceiptItemInterface $a, ReceiptItemInterface $b) { if ($a->getPrice()->getIntegerValue() > $b->getPrice()->getIntegerValue()) { return -1; } if ($a->getPrice()->getIntegerValue() < $b->getPrice()->getIntegerValue()) { return 1; } return 0; }); $coefficient = (float) $amount / (float) $realAmount; $realAmount = 0; $aloneId = null; foreach ($items as $index => $item) { if ($withShipping || !$item->isShipping()) { $item->applyDiscountCoefficient($coefficient); $realAmount += $item->getAmount(); if (null === $aloneId && 1.0 === $item->getQuantity() && !$item->isShipping()) { $aloneId = $index; } } } if (null === $aloneId) { foreach ($this->getItems() as $index => $item) { if (!$item->isShipping()) { $aloneId = $index; break; } } } if (null === $aloneId) { $aloneId = 0; } $diff = $amount - $realAmount; if (abs($diff) >= 0.1) { if (1.0 === $this->_items[$aloneId]->getQuantity()) { $this->_items[$aloneId]->increasePrice($diff / 100.0); } elseif ($this->_items[$aloneId]->getQuantity() > 1.0) { $item = $this->_items[$aloneId]->fetchItem(1); $item->increasePrice($diff / 100.0); $items = $this->getItems()->toArray(); array_splice($items, $aloneId + 1, 0, [$item]); $this->_items = new ListObject(ReceiptItem::class, $items); } else { $item = $this->_items[$aloneId]->fetchItem($this->_items[$aloneId]->getQuantity() / 2); $item->increasePrice($diff / 100.0); $items = $this->getItems()->toArray(); array_splice($items, $aloneId + 1, 0, [$item]); $this->_items = new ListObject(ReceiptItem::class, $items); } } } } /** * Возвращает Id объекта чека. * * @return null|string Id объекта чека */ public function getObjectId(): ?string { return null; } #[ReturnTypeWillChange] /** * Возвращает ассоциативный массив со свойствами текущего объекта для его дальнейшей JSON сериализации. * * @return array Ассоциативный массив со свойствами текущего объекта */ public function jsonSerialize(): array { $data = parent::jsonSerialize(); unset($data['amount_value'], $data['shipping_amount_value']); return $data; } }