Bitrix-D7  20.5.0
shipment.php
См. документацию.
1 <?php
2 
3 namespace Bitrix\Sale;
4 
6 use Bitrix\Main;
11 use \Bitrix\Sale\Delivery\Requests;
12 
13 Loc::loadMessages(__FILE__);
14 
15 /**
16  * Class Shipment
17  * @package Bitrix\Sale
18  */
20 {
21  /** @var array ShipmentItemCollection */
23 
24  /** @var Delivery\Services\Base */
25  protected $service = null;
26 
27  protected $extraServices = null;
28 
29  protected $storeId = null;
30 
31  /** @var int */
32  protected $internalId = 0;
33 
34  protected static $idShipment = 0;
35 
36  /**
37  * @return string|void
38  */
39  public static function getRegistryEntity()
40  {
42  }
43 
44  protected function __construct(array $fields = [])
45  {
46  $priceFields = ['BASE_PRICE_DELIVERY', 'PRICE_DELIVERY', 'DISCOUNT_PRICE'];
47 
48  foreach ($priceFields as $code)
49  {
50  if (isset($fields[$code]))
51  {
53  }
54  }
55 
57  }
58 
59  /**
60  * @return int
61  */
62  public function getShipmentCode()
63  {
64  if ($this->internalId === 0)
65  {
66  if ($this->getId() > 0)
67  {
68  $this->internalId = $this->getId();
69  }
70  else
71  {
72  static::$idShipment++;
73  $this->internalId = static::$idShipment;
74  }
75  }
76  return $this->internalId;
77  }
78 
79  /**
80  * @return array
81  */
82  public static function getAvailableFields()
83  {
84  return [
85  "STATUS_ID",
86  "BASE_PRICE_DELIVERY",
87  "PRICE_DELIVERY",
88  "EXPECTED_PRICE_DELIVERY",
89  "ALLOW_DELIVERY",
90  "DATE_ALLOW_DELIVERY",
91  "EMP_ALLOW_DELIVERY_ID",
92  "DEDUCTED",
93  "DATE_DEDUCTED",
94  "EMP_DEDUCTED_ID",
95  "REASON_UNDO_DEDUCTED",
96  "DELIVERY_ID",
97  "DELIVERY_DOC_NUM",
98  "DELIVERY_DOC_DATE",
99  "TRACKING_NUMBER",
100  "XML_ID",
101  "PARAMS",
102  "DELIVERY_NAME",
103  "COMPANY_ID",
104  "MARKED",
105  "WEIGHT",
106  "DATE_MARKED",
107  "EMP_MARKED_ID",
108  "REASON_MARKED",
109  "CANCELED",
110  "DATE_CANCELED",
111  "EMP_CANCELED_ID",
112  "RESPONSIBLE_ID",
113  "DATE_RESPONSIBLE_ID",
114  "EMP_RESPONSIBLE_ID",
115  "COMMENTS",
116  "CURRENCY",
117  "CUSTOM_PRICE_DELIVERY",
118  "UPDATED_1C",
119  "EXTERNAL_DELIVERY",
120  "VERSION_1C","ID_1C",
121  "TRACKING_STATUS",
122  "TRACKING_LAST_CHECK",
123  "TRACKING_DESCRIPTION",
124  "ACCOUNT_NUMBER",
125  'DISCOUNT_PRICE'
126  ];
127  }
128 
129  /**
130  * @return array
131  */
132  public static function getCustomizableFields() : array
133  {
134  return ['PRICE_DELIVERY' => 'PRICE_DELIVERY', 'WEIGHT' => 'WEIGHT'];
135  }
136 
137  /**
138  * @param array $values
139  * @return array
140  */
141  protected function onBeforeSetFields(array $values)
142  {
143  if (isset($values['DEDUCTED']))
144  {
145  if ($this->getField('DEDUCTED') === 'Y')
146  {
147  if ($values['DEDUCTED'] === 'N')
148  {
149  $values = ['DEDUCTED' => $values['DEDUCTED']] + $values;
150  }
151  }
152  else
153  {
154  if ($values['DEDUCTED'] === 'Y')
155  {
156  // move to the end of array
157  unset($values['DEDUCTED']);
158  $values['DEDUCTED'] = 'Y';
159  }
160  }
161  }
162 
163  return $values;
164  }
165 
166  /**
167  * @return array
168  */
169  protected static function getMeaningfulFields()
170  {
171  return array('BASE_PRICE_DELIVERY', 'DELIVERY_ID');
172  }
173 
174  /**
175  * @param Delivery\Services\Base $service
176  * @throws Main\ArgumentOutOfRangeException
177  * @throws Main\NotSupportedException
178  * @throws \Exception
179  */
180  public function setDeliveryService(Delivery\Services\Base $service)
181  {
182  $this->service = $service;
183 
184  $result = $this->setField("DELIVERY_ID", $service->getId());
185  if ($result->isSuccess())
186  {
187  $this->setField("DELIVERY_NAME", $service->getName());
188  }
189  }
190 
191  /**
192  * @param ShipmentCollection $collection
193  * @param Delivery\Services\Base|null $service
194  * @return mixed
195  * @throws Main\ArgumentException
196  * @throws Main\SystemException
197  */
198  public static function create(ShipmentCollection $collection, Delivery\Services\Base $service = null)
199  {
201  $fields = [
202  'DATE_INSERT' => new Main\Type\DateTime(),
203  'DELIVERY_ID' => $emptyService['ID'],
204  'DELIVERY_NAME' => $emptyService['NAME'],
205  'ALLOW_DELIVERY' => 'N',
206  'DEDUCTED' => 'N',
207  'CUSTOM_PRICE_DELIVERY' => 'N',
208  'MARKED' => 'N',
209  'CANCELED' => 'N',
210  'SYSTEM' => 'N',
211  'XML_ID' => static::generateXmlId(),
212  'RESERVED' => 'N'
213  ];
214 
215  $registry = Registry::getInstance(static::getRegistryType());
216 
217  /** @var DeliveryStatus $deliveryStatusClassName */
218  $deliveryStatusClassName = $registry->getDeliveryStatusClassName();
219  $fields['STATUS_ID'] = $deliveryStatusClassName::getInitialStatus();
220 
221  $shipment = static::createShipmentObject();
222  $shipment->setFieldsNoDemand($fields);
223  $shipment->setCollection($collection);
224 
225  if ($service !== null)
226  {
227  $shipment->setDeliveryService($service);
228  }
229 
230  return $shipment;
231  }
232 
233  /**
234  * @return string
235  */
236  protected static function generateXmlId()
237  {
238  return uniqid('bx_');
239  }
240 
241  /**
242  * @param array $fields
243  * @return mixed
244  * @throws Main\ArgumentException
245  */
246  private static function createShipmentObject(array $fields = array())
247  {
248  $registry = Registry::getInstance(static::getRegistryType());
249  $shipmentClassName = $registry->getShipmentClassName();
250 
251  return new $shipmentClassName($fields);
252  }
253 
254  /**
255  * @return string
256  */
257  public static function getRegistryType()
258  {
260  }
261 
262  /**
263  * @internal
264  *
265  * @return bool
266  * @throws Main\ArgumentNullException
267  * @throws Main\ObjectNotFoundException
268  */
269  public function needReservation()
270  {
272 
273  if ($condition === Configuration::RESERVE_ON_CREATE)
274  {
275  return true;
276  }
277 
278  if ($condition === Configuration::RESERVE_ON_PAY
279  || $condition === Configuration::RESERVE_ON_FULL_PAY)
280  {
281  $order = $this->getOrder();
282  if ($condition === Configuration::RESERVE_ON_FULL_PAY)
283  {
284  return $order->isPaid();
285  }
286 
287  /** @var PaymentCollection $paymentCollection */
288  if (!$paymentCollection = $order->getPaymentCollection())
289  {
290  throw new Main\ObjectNotFoundException('Entity "PaymentCollection" not found');
291  }
292 
293  return $paymentCollection->hasPaidPayment();
294  }
295 
296  if ($this->isSystem())
297  {
298  return false;
299  }
300 
301  return (($condition === Configuration::RESERVE_ON_ALLOW_DELIVERY) && $this->isAllowDelivery()
302  || ($condition === Configuration::RESERVE_ON_SHIP) && $this->isShipped());
303  }
304 
305  /**
306  * @param ShipmentItem $sourceItem
307  * @param $quantity
308  *
309  * @return Result
310  * @throws Main\ArgumentException
311  * @throws Main\ArgumentOutOfRangeException
312  * @throws Main\NotSupportedException
313  * @throws Main\ObjectNotFoundException
314  */
315  private function transferItem2SystemShipment(ShipmentItem $sourceItem, $quantity)
316  {
317  /** @var ShipmentItemCollection $sourceItemCollection */
318  $sourceItemCollection = $sourceItem->getCollection();
319  if ($this !== $sourceItemCollection->getShipment())
320  throw new Main\ArgumentException("item");
321 
322  $quantity = floatval($quantity);
323 
324  /** @var ShipmentCollection $shipmentCollection */
325  if (!$shipmentCollection = $this->getCollection())
326  {
327  throw new Main\ObjectNotFoundException('Entity "ShipmentCollection" not found');
328  }
329 
330  /** @var Shipment $systemShipment */
331  if (!$systemShipment = $shipmentCollection->getSystemShipment())
332  {
333  throw new Main\ObjectNotFoundException('Entity "Shipment" not found');
334  }
335 
336  /** @var BasketItem $basketItem */
337  if (!$basketItem = $sourceItem->getBasketItem())
338  {
339  throw new Main\ObjectNotFoundException('Entity "BasketItem" not found');
340  }
341 
342  /** @var Basket $basket */
343  if (!$basket = $basketItem->getCollection())
344  {
345  throw new Main\ObjectNotFoundException('Entity "Basket" not found');
346  }
347 
348  /** @var Order $order */
349  if (!$order = $basket->getOrder())
350  {
351  throw new Main\ObjectNotFoundException('Entity "Order" not found');
352  }
353 
354  $shipmentItemCode = $sourceItem->getBasketCode();
355 
356  if ($quantity === 0)
357  return new Result();
358 
359  /** @var ShipmentItemCollection $systemShipmentItemCollection */
360  if (!$systemShipmentItemCollection = $systemShipment->getShipmentItemCollection())
361  {
362  throw new Main\ObjectNotFoundException('Entity "System ShipmentItemCollection" not found');
363  }
364 
365  $systemShipmentItem = $systemShipmentItemCollection->getItemByBasketCode($shipmentItemCode);
366  if (is_null($systemShipmentItem))
367  $systemShipmentItem = $systemShipmentItemCollection->createItem($basketItem);
368 
369  $newSystemShipmentItemQuantity = $systemShipmentItem->getQuantity() + $quantity;
370  if ($newSystemShipmentItemQuantity < 0)
371  {
372  $result = new Result();
373  $result->addError(
374  new ResultError(
375  str_replace(
376  ["#NAME#", "#QUANTITY#"],
377  [$sourceItem->getBasketItem()->getField("NAME"), abs($quantity)],
378  Loc::getMessage('SALE_SHIPMENT_QUANTITY_MISMATCH')
379  ),
380  'SALE_SHIPMENT_QUANTITY_MISMATCH'
381  )
382  );
383  return $result;
384  }
385 
386  $systemShipmentItem->setFieldNoDemand('QUANTITY', $newSystemShipmentItemQuantity);
387  if ($newSystemShipmentItemQuantity === 0)
388  {
389  $systemShipmentItem->delete();
390  }
391 
392  $affectedQuantity = 0;
393 
394  if ($quantity > 0) // transfer to system shipment
395  {
396  if ($sourceItem->getReservedQuantity() > 0)
397  {
398  $affectedQuantity = $quantity;
399  $originalQuantity = $sourceItem->getQuantity() + $quantity;
400  if ($sourceItem->getReservedQuantity() < $originalQuantity)
401  $affectedQuantity -= $originalQuantity - $sourceItem->getReservedQuantity();
402  }
403  }
404  elseif ($quantity < 0) // transfer from system shipment
405  {
406  if ($systemShipmentItem->getReservedQuantity() > 0)
407  {
408  $affectedQuantity = $quantity;
409  if ($systemShipmentItem->getReservedQuantity() < -$affectedQuantity)
410  $affectedQuantity = -1 * $systemShipmentItem->getReservedQuantity();
411  }
412  }
413 
414  if ($affectedQuantity != 0) // if there are reserved items among transfered
415  {
416  $sourceItem->setField(
417  "RESERVED_QUANTITY", $sourceItem->getField('RESERVED_QUANTITY') - $affectedQuantity
418  );
419 
420  $systemShipmentItem->setFieldNoDemand(
421  'RESERVED_QUANTITY',
422  $systemShipmentItem->getField('RESERVED_QUANTITY') + $affectedQuantity
423  );
424 
425  $systemShipment->setFieldNoDemand(
426  'RESERVED',
427  ($systemShipmentItem->getField("RESERVED_QUANTITY") > 0) ? "Y" : "N"
428  );
429 
430  $shipmentItemForPool = $sourceItem;
431  $sourceShipmentItemForPool = $systemShipmentItem;
432 
433  if ($quantity > 0)
434  {
435  $shipmentItemForPool = $systemShipmentItem;
436  $sourceShipmentItemForPool = $sourceItem;
437  }
438 
439  $productId = $basketItem->getProductId();
440 
441  $foundItem = false;
442  $poolItems = Internals\ItemsPool::get($order->getInternalId(), $productId);
443  if (!empty($poolItems))
444  {
445  $oldItem = null;
446  foreach ($poolItems as $poolIndex => $poolItem)
447  {
448  if ($poolItem->getInternalIndex() === $shipmentItemForPool->getInternalIndex())
449  {
450  $foundItem = true;
451  }
452 
453  if ($sourceShipmentItemForPool && $poolItem->getInternalIndex() === $sourceShipmentItemForPool->getInternalIndex())
454  {
455  $reserveQuantity = $sourceShipmentItemForPool->getReservedQuantity();
456  if (abs($reserveQuantity) <= 1e-6)
457  {
458  Internals\ItemsPool::delete($order->getInternalId(), $productId, $poolIndex);
459  }
460  }
461  }
462  }
463 
464  if (!$foundItem)
465  {
466  Internals\ItemsPool::add($order->getInternalId(), $productId, $shipmentItemForPool);
467  }
468  }
469 
470  $tryReserveResult = null;
471 
472  $context = [
473  'USER_ID' => $order->getUserId(),
474  'SITE_ID' => $order->getSiteId(),
475  'CURRENCY' => $order->getCurrency(),
476  ];
477 
478  if ($quantity > 0)
479  {
480  if ($systemShipment->needReservation())
481  {
482  /** @var Result $tryReserveResult */
483  $tryReserveResult = Internals\Catalog\Provider::tryReserveShipmentItem($systemShipmentItem, $context);
484  }
485  else
486  {
487  /** @var Result $tryReserveResult */
488  $tryReserveResult = Internals\Catalog\Provider::tryUnreserveShipmentItem($systemShipmentItem);
489  }
490  }
491  elseif ($quantity < 0) // transfer from system shipment
492  {
493  if ($sourceItemCollection->getShipment()->needReservation())
494  {
495  /** @var Result $tryReserveResult */
496  $tryReserveResult = Internals\Catalog\Provider::tryReserveShipmentItem($sourceItem, $context);
497  }
498  }
499 
500  $canReserve = false;
501 
502  if ($tryReserveResult === null)
503  $canReserve = true;
504 
505  if ($tryReserveResult !== null && ($tryReserveResult->isSuccess() && ($tryReserveResultData = $tryReserveResult->getData())))
506  {
507  if (array_key_exists('CAN_RESERVE', $tryReserveResultData))
508  {
509  $canReserve = $tryReserveResultData['CAN_RESERVE'];
510  }
511  }
512 
513  if ($systemShipment->needReservation() && $canReserve)
514  {
515  $order = $this->getParentOrder();
516  if ($order &&
517  !Internals\ActionEntity::isTypeExists(
518  $order->getInternalId(),
520  )
521  )
522  {
524  $order->getInternalId(),
526  [
527  'METHOD' => 'Bitrix\Sale\ShipmentCollection::updateReservedFlag',
528  'PARAMS' => [$systemShipment->getCollection()]
529  ]
530  );
531  }
532  }
533 
534 
535  return new Result();
536  }
537 
538  /**
539  * @param Shipment $shipment
540  *
541  * @return Result
542  * @throws Main\ObjectNotFoundException
543  */
544  public static function updateReservedFlag(Shipment $shipment)
545  {
546  $shipmentReserved = true;
547 
548  $shipmentItemList = $shipment->getShipmentItemCollection()->getShippableItems();
549 
550  if ($shipmentItemList->count() === 0)
551  {
552  $shipmentReserved = false;
553  }
554 
555  /** @var ShipmentItem $shipmentItem */
556  foreach ($shipmentItemList as $shipmentItem)
557  {
558  if ($shipmentItem->getQuantity() - $shipmentItem->getReservedQuantity())
559  {
560  $shipmentReserved = false;
561  break;
562  }
563  }
564 
565  $shipmentReservedValue = $shipmentReserved ? "Y" : "N";
566  $currentValue = $shipment->getField('RESERVED');
567  if ($shipment->getField('RESERVED') != $shipmentReservedValue)
568  {
569  $eventManager = Main\EventManager::getInstance();
570  $eventsList = $eventManager->findEventHandlers('sale', EventActions::EVENT_ON_BEFORE_SHIPMENT_RESERVE);
571  if (!empty($eventsList))
572  {
573  /** @var Main\Entity\Event $event */
575  'ENTITY' => $shipment,
576  'VALUE' => $shipmentReservedValue,
577  ]);
578 
579  $event->send();
580 
581  if ($event->getResults())
582  {
583  $result = new Result();
584  /** @var Main\EventResult $eventResult */
585  foreach($event->getResults() as $eventResult)
586  {
587  if($eventResult->getType() === Main\EventResult::ERROR)
588  {
589  $errorMsg = new ResultError(Main\Localization\Loc::getMessage('SALE_EVENT_ON_BEFORE_SHIPMENT_RESERVE_ERROR'), 'SALE_EVENT_ON_BEFORE_SHIPMENT_RESERVE_ERROR');
590 
591  $eventResultData = $eventResult->getParameters();
592  if ($eventResultData)
593  {
594  if (isset($eventResultData) && $eventResultData instanceof ResultError)
595  {
596  /** @var ResultError $errorMsg */
597  $errorMsg = $eventResultData;
598  }
599  }
600 
601  $result->addError($errorMsg);
602 
603  }
604  }
605 
606  if (!$result->isSuccess())
607  {
608  return $result;
609  }
610  }
611  }
612 
613  $shipment->setFieldNoDemand('RESERVED', $shipmentReserved ? "Y" : "N");
614 
616  'ENTITY' => $shipment,
617  'VALUE' => $shipmentReservedValue,
618  'OLD_VALUE' => $currentValue,
619  ]);
620  }
621 
622  return new Result();
623  }
624 
625  /**
626  * @param $action
627  * @param ShipmentItem $shipmentItem
628  * @param null $name
629  * @param null $oldValue
630  * @param null $value
631  * @return Result
632  * @throws Main\NotSupportedException
633  * @throws Main\SystemException
634  */
635  public function onShipmentItemCollectionModify($action, ShipmentItem $shipmentItem, $name = null, $oldValue = null, $value = null)
636  {
637  if ($action != EventActions::UPDATE)
638  {
639  return new Result();
640  }
641 
642  if ($this->isSystem()
643  && $name != 'RESERVED_QUANTITY'
644  )
645  {
646  throw new Main\NotSupportedException(Loc::getMessage('SALE_SHIPMENT_SYSTEM_SHIPMENT_CHANGE'));
647  }
648 
649  if ($name === "QUANTITY")
650  {
651  $result = $this->transferItem2SystemShipment($shipmentItem, $oldValue - $value);
652 
653  if (!$this->isMarkedFieldCustom('WEIGHT'))
654  {
655  $this->setField(
656  'WEIGHT',
658  );
659  }
660 
661  return $result;
662  }
663  elseif ($name === 'RESERVED_QUANTITY')
664  {
665  $order = $this->getParentOrder();
666  if ($order &&
667  !Internals\ActionEntity::isTypeExists(
668  $order->getInternalId(),
670  )
671  )
672  {
674  $order->getInternalId(),
676  [
677  'METHOD' => 'Bitrix\Sale\ShipmentCollection::updateReservedFlag',
678  'PARAMS' => [$this->getCollection()]
679  ]
680  );
681  }
682  }
683 
684  return new Result();
685  }
686 
687  /**
688  * @param $orderId
689  * @return Result
690  * @throws Main\ArgumentException
691  * @internal
692  *
693  * Deletes shipment without demands.
694  *
695  */
696  public static function deleteNoDemand($orderId)
697  {
698  $result = new Result();
699 
700  $shipmentDataList = static::getList(
701  [
702  "filter" => ["=ORDER_ID" => $orderId],
703  "select" => ["ID"]
704  ]
705  );
706 
707  while ($shipment = $shipmentDataList->fetch())
708  {
709  $r = static::deleteInternal($shipment['ID']);
710  if ($r -> isSuccess())
711  {
713  }
714  else
715  {
716  $result->addErrors($r->getErrors());
717  }
718  }
719 
720  return $result;
721  }
722 
723  /**
724  * Deletes shipment
725  *
726  * @return Result
727  * @throws Main\ArgumentException
728  * @throws Main\ArgumentNullException
729  * @throws Main\ArgumentOutOfRangeException
730  * @throws Main\NotSupportedException
731  * @throws Main\ObjectNotFoundException
732  * @throws \Exception
733  */
734  public function delete()
735  {
736  if ($this->isShipped())
737  {
738  $result = new Result();
739  return $result->addError(
740  new ResultError(
741  Loc::getMessage('SALE_SHIPMENT_EXIST_SHIPPED'),
742  'SALE_SHIPMENT_EXIST_SHIPPED'
743  )
744  );
745  }
746 
747  if (!$this->isSystem())
748  {
749  $this->setField('BASE_PRICE_DELIVERY', 0);
750  $this->disallowDelivery();
751  }
752 
753  $this->deleteDeliveryRequest();
754 
755  $this->getShipmentItemCollection()->clearCollection();
756 
757  return parent::delete();
758  }
759 
760  /**
761  * @return void
762  */
763  protected function deleteDeliveryRequest()
764  {
765  Requests\Manager::onBeforeShipmentDelete($this);
766  }
767 
768  /**
769  * Sets new value to specified field of shipment item
770  *
771  * @param string $name
772  * @param mixed $value
773  * @return Result
774  * @throws Main\ArgumentOutOfRangeException
775  * @throws Main\NotSupportedException
776  * @throws \Exception
777  */
778  public function setField($name, $value)
779  {
780  if ($this->isSystem())
781  {
782  throw new Main\NotSupportedException();
783  }
784 
785  if ($name === "REASON_MARKED" && mb_strlen($value) > 255)
786  {
787  $value = mb_substr($value, 0, 255);
788  }
789 
790  $priceFields = [
791  'BASE_PRICE_DELIVERY' => 'BASE_PRICE_DELIVERY',
792  'PRICE_DELIVERY' => 'PRICE_DELIVERY',
793  'DISCOUNT_PRICE' => 'DISCOUNT_PRICE',
794  ];
795  if (isset($priceFields[$name]))
796  {
797  $value = PriceMaths::roundPrecision($value);
798  }
799 
800  if ($name === 'CUSTOM_PRICE_DELIVERY')
801  {
802  if ($value === 'Y')
803  {
804  $this->markFieldCustom('PRICE_DELIVERY');
805  }
806  else
807  {
808  $this->unmarkFieldCustom('PRICE_DELIVERY');
809  }
810  }
811 
812  return parent::setField($name, $value);
813  }
814 
815  /**
816  * @param $name
817  * @param $value
818  * @return Result
819  * @throws Main\ArgumentException
820  * @throws Main\SystemException
821  */
822  protected function checkValueBeforeSet($name, $value)
823  {
824  $result = parent::checkValueBeforeSet($name, $value);
825 
826  if ($name === "DELIVERY_ID")
827  {
828  if (intval($value) > 0 && !Delivery\Services\Manager::isServiceExist($value))
829  {
830  $result->addError(
831  new ResultError(
832  Loc::getMessage('SALE_SHIPMENT_WRONG_DELIVERY_SERVICE'),
833  'SALE_SHIPMENT_WRONG_DELIVERY_SERVICE'
834  )
835  );
836  }
837  }
838  elseif ($name === 'ACCOUNT_NUMBER')
839  {
840  $dbRes = static::getList([
841  'select' => ['ID'],
842  'filter' => ['=ACCOUNT_NUMBER' => $value]
843  ]);
844 
845  if ($dbRes->fetch())
846  {
847  $result->addError(
848  new ResultError(
849  Loc::getMessage('SALE_SHIPMENT_ACCOUNT_NUMBER_EXISTS')
850  )
851  );
852  }
853  }
854 
855  return $result;
856  }
857 
858  /**
859  * @internal
860  *
861  * @param $name
862  * @param $value
863  * @throws Main\ArgumentNullException
864  * @throws Main\ArgumentOutOfRangeException
865  * @throws Main\NotSupportedException
866  */
867  public function setFieldNoDemand($name, $value)
868  {
869  $priceFields = [
870  'BASE_PRICE_DELIVERY' => 'BASE_PRICE_DELIVERY',
871  'PRICE_DELIVERY' => 'PRICE_DELIVERY',
872  'DISCOUNT_PRICE' => 'DISCOUNT_PRICE',
873  ];
874  if (isset($priceFields[$name]))
875  {
876  $value = PriceMaths::roundPrecision($value);
877  }
878 
879  if ($name === 'CUSTOM_PRICE_DELIVERY')
880  {
881  if ($value === 'Y')
882  {
883  $this->markFieldCustom('PRICE_DELIVERY');
884  }
885  else
886  {
887  $this->unmarkFieldCustom('PRICE_DELIVERY');
888  }
889  }
890 
891  parent::setFieldNoDemand($name, $value);
892  }
893 
894  /**
895  * @param $id
896  * @return array
897  * @throws Main\ArgumentException
898  * @throws Main\ArgumentNullException
899  */
900  public static function loadForOrder($id)
901  {
902  if (intval($id) <= 0)
903  {
904  throw new Main\ArgumentNullException("id");
905  }
906 
907  $shipments = [];
908 
909  $shipmentDataList = static::getList([
910  'filter' => [
911  'ORDER_ID' => $id
912  ],
913  'order' => [
914  'SYSTEM' => 'ASC',
915  'DATE_INSERT' => 'ASC',
916  'ID' => 'ASC'
917  ]
918  ]);
919  while ($shipmentData = $shipmentDataList->fetch())
920  {
921  $shipments[] = static::createShipmentObject($shipmentData);
922  }
923 
924 
925  return $shipments;
926  }
927 
928  /**
929  * @internal
930  *
931  * @return Result
932  * @throws Main\ArgumentException
933  * @throws Main\ArgumentNullException
934  * @throws Main\ArgumentOutOfRangeException
935  * @throws Main\ObjectNotFoundException
936  * @throws \Exception
937  */
938  public function save()
939  {
940  $this->checkCallingContext();
941 
942  $result = new Result();
943 
944  $id = $this->getId();
945  $isNew = ($this->getId() === 0);
946 
947  $this->callEventOnBeforeEntitySaved();
948 
949  if ($id > 0)
950  {
951  $r = $this->update();
952  }
953  else
954  {
955  $r = $this->add();
956 
957  if ($r->getId() > 0)
958  {
959  $id = $r->getId();
960  }
961  }
962 
963  if (!$r->isSuccess())
964  {
965  $result->addErrors($r->getErrors());
966  return $result;
967  }
968 
969  if ($id > 0)
970  {
971  $result->setId($id);
972 
974  $controller->save($this);
975  }
976 
977  if (!$this->isSystem())
978  {
979  $this->saveExtraServices();
980  $this->saveStoreId();
981  }
982 
983  $this->callEventOnEntitySaved();
984 
985  $this->callDelayedEvents();
986 
988  $r = $shipmentItemCollection->save();
989  if (!$r->isSuccess())
990  {
991  $result->addErrors($r->getErrors());
992  return $result;
993  }
994 
995  if (!$this->isSystem())
996  {
997  $registry = Registry::getInstance(static::getRegistryType());
998 
999  /** @var OrderHistory $orderHistory */
1000  $orderHistory = $registry->getOrderHistoryClassName();
1001  $orderHistory::collectEntityFields('SHIPMENT', $this->getParentOrderId(), $id);
1002  }
1003 
1004  $this->onAfterSave($isNew);
1005 
1006  return $result;
1007  }
1008 
1009  /**
1010  * @return void
1011  */
1012  private function checkCallingContext()
1013  {
1014  $order = $this->getOrder();
1015 
1016  if (!$order->isSaveRunning())
1017  {
1018  trigger_error("Incorrect call to the save process. Use method save() on \Bitrix\Sale\Order entity", E_USER_WARNING);
1019  }
1020  }
1021 
1022  /**
1023  * @return Result
1024  * @throws Main\ArgumentException
1025  * @throws Main\ArgumentNullException
1026  * @throws Main\ArgumentOutOfRangeException
1027  * @throws Main\ObjectNotFoundException
1028  * @throws \Exception
1029  */
1030  private function add()
1031  {
1032  $result = new Result();
1033 
1034  $registry = Registry::getInstance(static::getRegistryType());
1035 
1036  $this->setFieldNoDemand('ORDER_ID', $this->getParentOrderId());
1037 
1038  $r = static::addInternal($this->getFields()->getValues());
1039  if (!$r->isSuccess())
1040  {
1041  /** @var OrderHistory $orderHistory */
1042  $orderHistory = $registry->getOrderHistoryClassName();
1043 
1044  $orderHistory::addAction(
1045  'SHIPMENT',
1046  $this->getParentOrderId(),
1047  'SHIPMENT_ADD_ERROR',
1048  null,
1049  $this,
1050  ["ERROR" => $r->getErrorMessages()]
1051  );
1052 
1053  $result->addErrors($r->getErrors());
1054  return $result;
1055  }
1056 
1057  $id = $r->getId();
1058  $this->setFieldNoDemand('ID', $id);
1059  $result->setId($id);
1060 
1061  $this->setAccountNumber($id);
1062 
1063  if (!$this->isSystem())
1064  {
1065  /** @var OrderHistory $orderHistory */
1066  $orderHistory = $registry->getOrderHistoryClassName();
1067 
1068  $orderHistory::addAction(
1069  'SHIPMENT',
1070  $this->getParentOrderId(),
1071  'SHIPMENT_ADDED',
1072  $id,
1073  $this
1074  );
1075  }
1076 
1077  return $result;
1078  }
1079 
1080  /**
1081  * @return Result
1082  * @throws \Exception
1083  */
1084  private function update()
1085  {
1086  $result = new Result();
1087 
1088  $registry = Registry::getInstance(static::getRegistryType());
1089 
1090  $this->setDeliveryRequestMarker();
1091 
1092  $r = static::updateInternal($this->getId(), $this->getFields()->getChangedValues());
1093  if (!$r->isSuccess())
1094  {
1095  /** @var OrderHistory $orderHistory */
1096  $orderHistory = $registry->getOrderHistoryClassName();
1097 
1098  $orderHistory::addAction(
1099  'SHIPMENT',
1100  $this->getParentOrderId(),
1101  'SHIPMENT_UPDATE_ERROR',
1102  $this->getId(),
1103  $this,
1104  ["ERROR" => $r->getErrorMessages()]
1105  );
1106 
1107  $result->addErrors($r->getErrors());
1108  }
1109 
1110  return $result;
1111  }
1112 
1113  /**
1114  * @return void
1115  */
1116  protected function setDeliveryRequestMarker()
1117  {
1118  $order = $this->getParentOrder();
1119 
1120  Requests\Manager::onBeforeShipmentSave($order, $this);
1121  }
1122 
1123  /**
1124  * @return void
1125  *
1126  * @throws Main\ArgumentException
1127  */
1128  private function callDelayedEvents()
1129  {
1130  $eventList = Internals\EventsPool::getEvents('s'.$this->getInternalIndex());
1131 
1132  if ($eventList)
1133  {
1134  foreach ($eventList as $eventName => $eventData)
1135  {
1136  $event = new Main\Event('sale', $eventName, $eventData);
1137  $event->send();
1138 
1139  $registry = Registry::getInstance(static::getRegistryType());
1140 
1141  /** @var Notify $notifyClassName */
1142  $notifyClassName = $registry->getNotifyClassName();
1143  $notifyClassName::callNotify($this, $eventName);
1144  }
1145 
1147  }
1148  }
1149 
1150  /**
1151  * @return void
1152  */
1153  private function callEventOnBeforeEntitySaved()
1154  {
1155  /** @var Main\Entity\Event $event */
1156  $event = new Main\Event('sale', 'OnBeforeSaleShipmentEntitySaved', [
1157  'ENTITY' => $this,
1158  'VALUES' => $this->fields->getOriginalValues()
1159  ]);
1160 
1161  $event->send();
1162  }
1163 
1164  /**
1165  * @return void
1166  */
1167  private function callEventOnEntitySaved()
1168  {
1169  /** @var Main\Event $event */
1170  $event = new Main\Event('sale', 'OnSaleShipmentEntitySaved', [
1171  'ENTITY' => $this,
1172  'VALUES' => $this->fields->getOriginalValues(),
1173  ]);
1174 
1175  $event->send();
1176  }
1177 
1178  /**
1179  * @param $isNew
1180  * @return void
1181  */
1182  protected function onAfterSave($isNew)
1183  {
1184  return;
1185  }
1186 
1187  /**
1188  * @return bool|int
1189  */
1190  public function getParentOrderId()
1191  {
1192  $order = $this->getParentOrder();
1193  if (!$order)
1194  {
1195  return false;
1196  }
1197 
1198  return $order->getId();
1199  }
1200 
1201  /**
1202  * @return Order|null
1203  */
1204  public function getOrder()
1205  {
1206  return $this->getCollection()->getOrder();
1207  }
1208 
1209  /**
1210  * @return array|ShipmentItemCollection
1211  * @throws Main\ArgumentException
1212  * @throws Main\ArgumentNullException
1213  * @throws Main\ObjectNotFoundException
1214  */
1215  public function getShipmentItemCollection()
1216  {
1217  if (empty($this->shipmentItemCollection))
1218  {
1219  $registry = Registry::getInstance(static::getRegistryType());
1220 
1221  /** @var ShipmentItemCollection $itemCollectionClassName */
1222  $itemCollectionClassName = $registry->getShipmentItemCollectionClassName();
1223  $this->shipmentItemCollection = $itemCollectionClassName::load($this);
1224  }
1225 
1227  }
1228 
1229  /**
1230  * @throws Main\ArgumentNullException
1231  * @throws Main\ArgumentOutOfRangeException
1232  */
1233  protected function markSystem()
1234  {
1235  $this->setFieldNoDemand("SYSTEM", 'Y');
1236  }
1237 
1238  /**
1239  * @internal
1240  *
1241  * @param ShipmentCollection $collection
1242  * @param Delivery\Services\Base|null $deliveryService
1243  * @return Shipment
1244  * @throws Main\ArgumentOutOfRangeException
1245  * @throws Main\SystemException
1246  */
1247  public static function createSystem(ShipmentCollection $collection, Delivery\Services\Base $deliveryService = null)
1248  {
1249  $shipment = static::create($collection, $deliveryService);
1250  $shipment->markSystem();
1251 
1252  if ($deliveryService === null)
1253  {
1254  $shipment->setFieldNoDemand('DELIVERY_ID', Delivery\Services\Manager::getEmptyDeliveryServiceId());
1255  }
1256 
1257  return $shipment;
1258  }
1259 
1260  /**
1261  * @return float
1262  */
1263  public function getPrice()
1264  {
1265  return (float)$this->getField('PRICE_DELIVERY');
1266  }
1267 
1268  /**
1269  * @return bool
1270  * @throws Main\ArgumentOutOfRangeException
1271  */
1272  public function isCustomPrice()
1273  {
1274  return $this->isMarkedFieldCustom('PRICE_DELIVERY');
1275  }
1276 
1277  /**
1278  * @return string
1279  */
1280  public function getCurrency()
1281  {
1282  return (string)$this->getField('CURRENCY');
1283  }
1284 
1285  /**
1286  * @return int
1287  */
1288  public function getDeliveryId()
1289  {
1290  return (int)$this->getField('DELIVERY_ID');
1291  }
1292 
1293  /**
1294  * @return string
1295  */
1296  public function getDeliveryName()
1297  {
1298  return (string)$this->getField('DELIVERY_NAME');
1299  }
1300 
1301  /**
1302  * @param $orderId
1303  */
1304  public function setOrderId($orderId)
1305  {
1306  $this->setField('ORDER_ID', $orderId);
1307  }
1308 
1309  /**
1310  * @return Delivery\Services\Base
1311  * @throws Main\ArgumentNullException
1312  * @throws Main\SystemException
1313  */
1314  public function getDelivery()
1315  {
1316  if ($this->service === null)
1317  {
1318  $this->service = $this->loadDeliveryService();
1319  }
1320 
1321  return $this->service;
1322  }
1323 
1324  /**
1325  * @return Delivery\Services\Base
1326  * @throws Main\ArgumentNullException
1327  * @throws Main\SystemException
1328  */
1329  protected function loadDeliveryService()
1330  {
1331  if ($deliveryId = $this->getDeliveryId())
1332  {
1333  return Delivery\Services\Manager::getObjectById($deliveryId);
1334  }
1335 
1336  return null;
1337  }
1338 
1339 
1340  /**
1341  * @return bool
1342  */
1343  public function isSystem()
1344  {
1345  return $this->getField('SYSTEM') === 'Y';
1346  }
1347 
1348  /** @return bool */
1349  public function isCanceled()
1350  {
1351  return $this->getField('CANCELED') === 'Y';
1352  }
1353 
1354  /**
1355  * @return bool
1356  */
1357  public function isShipped()
1358  {
1359  return $this->getField('DEDUCTED') === 'Y';
1360  }
1361 
1362  /**
1363  * @return Main\Type\DateTime
1364  */
1365  public function getShippedDate()
1366  {
1367  return $this->getField('DATE_DEDUCTED');
1368  }
1369 
1370  /**
1371  * @return int
1372  */
1373  public function getShippedUserId()
1374  {
1375  return $this->getField('EMP_DEDUCTED_ID');
1376  }
1377 
1378  /**
1379  * @return string
1380  */
1381  public function getUnshipReason()
1382  {
1383  return (string)$this->getField('REASON_UNDO_DEDUCTED');
1384  }
1385 
1386  /**
1387  * @return bool
1388  */
1389  public function isMarked()
1390  {
1391  return $this->getField('MARKED') === "Y";
1392  }
1393 
1394  /**
1395  * @return bool
1396  */
1397  public function isReserved()
1398  {
1399  return $this->getField('RESERVED') === "Y";
1400  }
1401 
1402  /**
1403  * @return bool
1404  */
1405  public function isAllowDelivery()
1406  {
1407  return $this->getField('ALLOW_DELIVERY') === "Y";
1408  }
1409 
1410  /**
1411  * @return bool
1412  */
1413  public function isEmpty()
1414  {
1415  return $this->getShipmentItemCollection()->isEmpty();
1416  }
1417 
1418  /**
1419  * @return Main\Type\DateTime
1420  */
1421  public function getAllowDeliveryDate()
1422  {
1423  return $this->getField('DATE_ALLOW_DELIVERY');
1424  }
1425 
1426  /**
1427  * @return int
1428  */
1429  public function getAllowDeliveryUserId()
1430  {
1431  return (int)$this->getField('EMP_ALLOW_DELIVERY_ID');
1432  }
1433 
1434  /**
1435  * @return int
1436  */
1437  public function getCompanyId()
1438  {
1439  return (int)$this->getField('COMPANY_ID');
1440  }
1441 
1442  /**
1443  * @throws Main\ArgumentException
1444  * @throws Main\ArgumentOutOfRangeException
1445  * @throws Main\NotSupportedException
1446  * @throws \Exception
1447  */
1448  public function tryReserve()
1449  {
1451  }
1452 
1453  /**
1454  * @throws Main\ArgumentOutOfRangeException
1455  * @throws Main\NotSupportedException
1456  * @throws \Exception
1457  */
1458  public function tryUnreserve()
1459  {
1461  }
1462 
1463  /**
1464  * @return Result
1465  * @throws Main\NotSupportedException
1466  * @throws Main\ObjectNotFoundException
1467  */
1468  public function tryShip()
1469  {
1470  $result = new Result();
1471 
1472  /** @var Result $r */
1474  if ($r->isSuccess())
1475  {
1476  $resultList = $r->getData();
1477 
1478  if (!empty($resultList) && is_array($resultList))
1479  {
1480  /** @var Result $resultDat */
1481  foreach ($resultList as $resultDat)
1482  {
1483  if (!$resultDat->isSuccess())
1484  {
1485  $result->addErrors( $resultDat->getErrors() );
1486  }
1487  }
1488  }
1489  }
1490  else
1491  {
1492  $result->addErrors( $r->getErrors() );
1493  }
1494 
1495  if ($r->hasWarnings())
1496  {
1497  $result->addWarnings( $r->getWarnings() );
1498  }
1499  return $result;
1500  }
1501 
1502  /**
1503  * @return Result
1504  * @throws Main\NotSupportedException
1505  * @throws Main\ObjectNotFoundException
1506  */
1507  public function tryUnship()
1508  {
1509  return $this->tryShip();
1510  }
1511 
1512  /**
1513  *
1514  */
1515  public function needShip()
1516  {
1517  if ($this->fields->isChanged('DEDUCTED'))
1518  {
1519  if ($this->getField('DEDUCTED') === "Y")
1520  {
1521  return true;
1522  }
1523  elseif ($this->getField('DEDUCTED') === "N" && $this->getId() != 0)
1524  {
1525  return false;
1526  }
1527  }
1528 
1529  return null;
1530  }
1531 
1532  /**
1533  * @return Result
1534  * @throws Main\NotSupportedException
1535  * @throws Main\ObjectNotFoundException
1536  */
1537  public function deliver()
1538  {
1539  $order = $this->getParentOrder();
1540  if (!$order)
1541  {
1542  throw new Main\ObjectNotFoundException('Entity "Order" not found');
1543  }
1544 
1545  $result = new Result();
1546 
1547  $context = array(
1548  'USER_ID' => $order->getUserId(),
1549  'SITE_ID' => $order->getSiteId(),
1550  );
1551 
1552  $creator = Internals\ProviderCreator::create($context);
1553 
1555 
1556  /** @var ShipmentItemCollection $shipmentItemCollection */
1558  {
1559  throw new Main\ObjectNotFoundException('Entity "ShipmentItemCollection" not found');
1560  }
1561 
1562  /** @var ShipmentItem $shipmentItem */
1563  foreach ($shipmentItemCollection as $shipmentItem)
1564  {
1565  $creator->addShipmentItem($shipmentItem);
1566  }
1567 
1568  $r = $creator->deliver();
1569  if ($r->isSuccess())
1570  {
1571  $r = $creator->createItemsResultAfterDeliver($r);
1572  if ($r->isSuccess())
1573  {
1574  $data = $r->getData();
1575  if (array_key_exists('RESULT_AFTER_DELIVER_LIST', $data))
1576  {
1577  $resultList = $data['RESULT_AFTER_DELIVER_LIST'];
1578  }
1579  }
1580  }
1581  else
1582  {
1583  $result->addErrors($r->getErrors());
1584  }
1585 
1586  if (!empty($resultList) && is_array($resultList))
1587  {
1588  Recurring::repeat($order, $resultList);
1589  }
1590 
1591  return $result;
1592  }
1593 
1594  /**
1595  * @return Result
1596  * @throws Main\NotSupportedException
1597  */
1598  public function allowDelivery()
1599  {
1600  return $this->setField('ALLOW_DELIVERY', "Y");
1601  }
1602 
1603  /**
1604  * @return Result
1605  * @throws Main\NotSupportedException
1606  */
1607  public function disallowDelivery()
1608  {
1609  return $this->setField('ALLOW_DELIVERY', "N");
1610  }
1611 
1612  /**
1613  * @param BasketItem $basketItem
1614  * @return Result
1615  * @throws Main\ArgumentException
1616  * @throws Main\ArgumentNullException
1617  * @throws Main\ArgumentOutOfRangeException
1618  * @throws Main\NotSupportedException
1619  * @throws Main\ObjectNotFoundException
1620  * @throws Main\SystemException
1621  */
1622  public function onBeforeBasketItemDelete(BasketItem $basketItem)
1623  {
1624  $result = new Result();
1625 
1627  $r = $shipmentItemCollection->onBeforeBasketItemDelete($basketItem);
1628  if (!$r->isSuccess())
1629  {
1630  return $result->addErrors($r->getErrors());
1631  }
1632 
1633  if ($this->isSystem())
1634  {
1635  return $this->syncQuantityAfterModify($basketItem);
1636  }
1637 
1638  return $result;
1639  }
1640 
1641  /**
1642  * @param $action
1643  * @param BasketItem $basketItem
1644  * @param null $name
1645  * @param null $oldValue
1646  * @param null $value
1647  * @return Result
1648  * @throws Main\ArgumentException
1649  * @throws Main\ArgumentNullException
1650  * @throws Main\ArgumentOutOfRangeException
1651  * @throws Main\NotSupportedException
1652  * @throws Main\ObjectNotFoundException
1653  * @throws Main\SystemException
1654  */
1655  public function onBasketModify($action, BasketItem $basketItem, $name = null, $oldValue = null, $value = null)
1656  {
1657  $result = new Result();
1658 
1659  if ($action === EventActions::ADD)
1660  {
1661  if (!$this->isSystem())
1662  {
1663  return $result;
1664  }
1665 
1666  return $this->getShipmentItemCollection()->onBasketModify($action, $basketItem, $name, $oldValue, $value);
1667  }
1668  elseif ($action === EventActions::UPDATE)
1669  {
1670  if ($name === "QUANTITY")
1671  {
1672  if ($this->isSystem())
1673  {
1674  return $this->syncQuantityAfterModify($basketItem, $value, $oldValue);
1675  }
1676 
1677  /** @var ShipmentItemCollection $shipmentItemCollection */
1679 
1680  $r = $shipmentItemCollection->onBasketModify($action, $basketItem, $name, $oldValue, $value);
1681 
1682  if (
1683  $r->isSuccess()
1684  && !$this->isCustomPrice()
1685  )
1686  {
1687  /** @var Delivery\CalculationResult $deliveryCalculate */
1688  $deliveryCalculate = $this->calculateDelivery();
1689  if ($deliveryCalculate->isSuccess())
1690  {
1691  $this->setField('BASE_PRICE_DELIVERY', $deliveryCalculate->getPrice());
1692  }
1693  else
1694  {
1695  $result->addWarnings($deliveryCalculate->getErrors());
1696  }
1697  }
1698  }
1699  elseif ($name === 'WEIGHT')
1700  {
1701  if (!$this->isMarkedFieldCustom('WEIGHT'))
1702  {
1703  if ($this->getShipmentItemCollection()->isExistBasketItem($basketItem))
1704  {
1705  $this->setField('WEIGHT', $this->getShipmentItemCollection()->getWeight());
1706  }
1707  }
1708  }
1709  elseif ($name === 'PRICE')
1710  {
1711  if (!$this->isMarkedFieldCustom('BASE_PRICE_DELIVERY'))
1712  {
1713  if ($this->getShipmentItemCollection()->isExistBasketItem($basketItem))
1714  {
1715  $r = $this->calculateDelivery();
1716  if ($r->isSuccess())
1717  {
1718  $this->setField('BASE_PRICE_DELIVERY', $r->getPrice());
1719  }
1720  else
1721  {
1722  $result->addErrors($r->getErrors());
1723  }
1724  }
1725  }
1726  }
1727  }
1728 
1729  return $result;
1730  }
1731 
1732  /**
1733  * @param string $name
1734  * @param mixed $oldValue
1735  * @param mixed $value
1736  * @return Result
1737  * @throws Main\ArgumentException
1738  * @throws Main\ArgumentNullException
1739  * @throws Main\ArgumentOutOfRangeException
1740  * @throws Main\NotSupportedException
1741  * @throws Main\ObjectException
1742  */
1743  protected function onFieldModify($name, $oldValue, $value)
1744  {
1745  global $USER;
1746 
1747  $result = new Result();
1748 
1749  if ($name === "MARKED")
1750  {
1751  if ($oldValue != "Y")
1752  {
1753  $this->setField('DATE_MARKED', new Main\Type\DateTime());
1754 
1755  if (is_object($USER))
1756  {
1757  $this->setField('EMP_MARKED_ID', $USER->GetID());
1758  }
1759  }
1760  elseif ($value === "N")
1761  {
1762  $this->setField('REASON_MARKED', '');
1763  }
1764  }
1765  elseif ($name === "ALLOW_DELIVERY")
1766  {
1767  $this->setField('DATE_ALLOW_DELIVERY', new Main\Type\DateTime());
1768 
1769  if (is_object($USER))
1770  {
1771  $this->setField('EMP_ALLOW_DELIVERY_ID', $USER->GetID());
1772  }
1773 
1774  if ($oldValue === 'N')
1775  {
1776  $shipmentStatus = Main\Config\Option::get('sale', 'shipment_status_on_allow_delivery', '');
1777 
1778  $registry = Registry::getInstance(static::getRegistryType());
1779  /** @var DeliveryStatus $deliveryStatus */
1780  $deliveryStatusClassName = $registry->getDeliveryStatusClassName();
1781 
1782  if (strval($shipmentStatus) != '' && $this->getField('STATUS_ID') != $deliveryStatusClassName::getFinalStatus())
1783  {
1784  $r = $this->setStatus($shipmentStatus);
1785  if (!$r->isSuccess())
1786  {
1787  $result->addErrors($r->getErrors());
1788  }
1789  }
1790  }
1791 
1793  's'.$this->getInternalIndex(),
1795  [
1796  'ENTITY' => $this,
1797  'VALUES' => $this->fields->getOriginalValues()
1798  ]
1799  );
1800  }
1801  elseif ($name === "DEDUCTED")
1802  {
1803  $this->setField('DATE_DEDUCTED', new Main\Type\DateTime());
1804 
1805  if (is_object($USER))
1806  {
1807  $this->setField('EMP_DEDUCTED_ID', $USER->GetID());
1808  }
1809 
1810  if ($oldValue === 'N')
1811  {
1812  $shipmentStatus = Main\Config\Option::get('sale', 'shipment_status_on_shipped', '');
1813 
1814  $registry = Registry::getInstance(static::getRegistryType());
1815  /** @var DeliveryStatus $deliveryStatus */
1816  $deliveryStatusClassName = $registry->getDeliveryStatusClassName();
1817 
1818  if (strval($shipmentStatus) != '' && $this->getField('STATUS_ID') != $deliveryStatusClassName::getFinalStatus())
1819  {
1820  $r = $this->setStatus($shipmentStatus);
1821  if (!$r->isSuccess())
1822  {
1823  $result->addErrors($r->getErrors());
1824  }
1825  }
1826  }
1827 
1828  if ($value === 'Y')
1829  {
1830  /** @var ShipmentItem $shipmentItem */
1831  foreach ($this->getShipmentItemCollection() as $shipmentItem)
1832  {
1833  $r = $shipmentItem->checkMarkingCodeOnDeducted();
1834  if (!$r->isSuccess())
1835  {
1836  $result->addErrors($r->getErrors());
1837  }
1838  }
1839  }
1840 
1842  's'.$this->getInternalIndex(),
1844  [
1845  'ENTITY' => $this,
1846  'VALUES' => $this->fields->getOriginalValues()
1847  ]
1848  );
1849 
1850  Cashbox\Internals\Pool::addDoc($this->getOrder()->getInternalId(), $this);
1851  }
1852  elseif ($name === "STATUS_ID")
1853  {
1854  $event = new Main\Event(
1855  'sale',
1857  [
1858  'ENTITY' => $this,
1859  'VALUE' => $value,
1860  'OLD_VALUE' => $oldValue,
1861  ]
1862  );
1863  $event->send();
1864 
1866  's'.$this->getInternalIndex(),
1868  [
1869  'ENTITY' => $this,
1870  'VALUE' => $value,
1871  'OLD_VALUE' => $oldValue,
1872  ]
1873  );
1874 
1876  's'.$this->getInternalIndex(),
1878  [
1879  'ENTITY' => $this,
1880  'VALUE' => $value,
1881  'OLD_VALUE' => $oldValue,
1882  ]
1883  );
1884  }
1885  elseif ($name === 'RESPONSIBLE_ID')
1886  {
1887  $this->setField('DATE_RESPONSIBLE_ID', new Main\Type\DateTime());
1888  }
1889  elseif ($name === 'TRACKING_NUMBER')
1890  {
1891  if ($value)
1892  {
1894  's'.$this->getInternalIndex(),
1896  [
1897  'ENTITY' => $this,
1898  'VALUES' => $this->getFields()->getOriginalValues(),
1899  ]
1900  );
1901  }
1902  }
1903 
1904 
1905  $r = parent::onFieldModify($name, $oldValue, $value);
1906  if (!$r->isSuccess())
1907  {
1908  $result->addErrors($r->getErrors());
1909  }
1910 
1911  if ($r->hasWarnings())
1912  {
1913  $result->addWarnings($r->getWarnings());
1914  }
1915 
1916  if (($resultData = $r->getData()) && !empty($resultData))
1917  {
1918  $result->addData($resultData);
1919  }
1920 
1921  return $result;
1922  }
1923 
1924  /**
1925  * @internal
1926  *
1927  * @param BasketItem $basketItem
1928  * @param null $value
1929  * @param null $oldValue
1930  * @return Result
1931  * @throws Main\ArgumentException
1932  * @throws Main\ArgumentNullException
1933  * @throws Main\ArgumentOutOfRangeException
1934  * @throws Main\NotSupportedException
1935  * @throws Main\ObjectNotFoundException
1936  */
1937  protected function syncQuantityAfterModify(BasketItem $basketItem, $value = null, $oldValue = null)
1938  {
1939  $result = new Result();
1940 
1941  /** @var ShipmentItemCollection $shipmentItemCollection */
1943  {
1944  throw new Main\ObjectNotFoundException('Entity "ShipmentItemCollection" not found');
1945  }
1946 
1947  $shipmentItem = $shipmentItemCollection->getItemByBasketCode($basketItem->getBasketCode());
1948 
1949  if ($value === 0)
1950  {
1951  if ($shipmentItem !== null)
1952  {
1953  $shipmentItem->setFieldNoDemand('QUANTITY', 0);
1954  }
1955 
1956  return $result;
1957  }
1958 
1959  if ($shipmentItem === null)
1960  {
1961  $shipmentItem = $shipmentItemCollection->createItem($basketItem);
1962  }
1963 
1964  $deltaQuantity = $value - $oldValue;
1965 
1966 
1967  /** @var Basket $basket */
1968  $basket = $basketItem->getCollection();
1969  if (!$basket)
1970  {
1971  throw new Main\ObjectNotFoundException('Entity "Basket" not found');
1972  }
1973 
1974  /** @var Order $order */
1975  $order = $basket->getOrder();
1976  if (!$order)
1977  {
1978  throw new Main\ObjectNotFoundException('Entity "Order" not found');
1979  }
1980 
1981  $context = [
1982  'USER_ID' => $order->getUserId(),
1983  'SITE_ID' => $order->getSiteId(),
1984  'CURRENCY' => $order->getCurrency(),
1985  ];
1986 
1987  if ($deltaQuantity > 0) // plus
1988  {
1989  $shipmentItem->setFieldNoDemand(
1990  "QUANTITY",
1991  $shipmentItem->getField("QUANTITY") + $deltaQuantity
1992  );
1993 
1994  if ($this->needReservation())
1995  {
1996  /** @var Result $tryReserveResult */
1997  Internals\Catalog\Provider::tryReserveShipmentItem($shipmentItem, $context);
1998  }
1999  }
2000  else // minus
2001  {
2002  if (floatval($shipmentItem->getField("QUANTITY")) <= 0)
2003  {
2004  return new Result();
2005  }
2006 
2007  if ($value != 0 && roundEx($shipmentItem->getField("QUANTITY"), SALE_VALUE_PRECISION) < roundEx(-$deltaQuantity, SALE_VALUE_PRECISION))
2008  {
2009  $result->addError(
2010  new ResultError(
2011  str_replace(
2012  array("#NAME#", "#QUANTITY#", "#DELTA_QUANTITY#"),
2013  array($basketItem->getField("NAME"), $shipmentItem->getField("QUANTITY"), abs($deltaQuantity)),
2014  Loc::getMessage('SALE_SHIPMENT_SYSTEM_QUANTITY_ERROR')
2015  ),
2016  'SALE_SHIPMENT_SYSTEM_QUANTITY_ERROR'
2017  )
2018  );
2019  return $result;
2020  }
2021 
2022  if ($value > 0)
2023  {
2024  $shipmentItem->setFieldNoDemand(
2025  "QUANTITY",
2026  $shipmentItem->getField("QUANTITY") + $deltaQuantity
2027  );
2028  if ($this->needReservation())
2029  {
2030  Internals\Catalog\Provider::tryReserveShipmentItem($shipmentItem, $context);
2031  }
2032  }
2033 
2034  }
2035 
2036  return $result;
2037  }
2038 
2039  /**
2040  * @return array
2041  */
2042  public function getServiceParams()
2043  {
2044  $params = $this->getField('PARAMS');
2045  return isset($params["SERVICE_PARAMS"]) ? $params["SERVICE_PARAMS"] : array();
2046  }
2047 
2048  /**
2049  * @param array $serviceParams
2050  * @throws Main\NotSupportedException
2051  */
2052  public function setServiceParams(array $serviceParams)
2053  {
2054  $params = $this->getField('PARAMS');
2055  $params["SERVICE_PARAMS"] = $serviceParams;
2056  $this->setField("PARAMS", $params);
2057  }
2058 
2059  /**
2060  * @return null
2061  */
2062  public function getExtraServices()
2063  {
2064  if($this->extraServices === null)
2065  {
2066  $this->setExtraServices(
2067  Delivery\ExtraServices\Manager::getValuesForShipment(
2068  $this->getId(),
2069  $this->getDeliveryId()
2070  )
2071  );
2072  }
2073 
2074  return $this->extraServices;
2075  }
2076 
2077  /**
2078  * @param array $extraServices
2079  */
2080  public function setExtraServices(array $extraServices)
2081  {
2082  $this->extraServices = $extraServices;
2083  }
2084 
2085  /**
2086  * @return Result
2087  */
2088  protected function saveExtraServices()
2089  {
2090  return Delivery\ExtraServices\Manager::saveValuesForShipment($this->getId(), $this->getExtraServices());
2091  }
2092 
2093  /**
2094  * @return int
2095  */
2096  public function getStoreId()
2097  {
2098  if($this->storeId === null)
2099  {
2100  $this->setStoreId(
2101  Delivery\ExtraServices\Manager::getStoreIdForShipment(
2102  $this->getId(),
2103  $this->getDeliveryId()
2104  ));
2105  }
2106 
2107  return $this->storeId;
2108  }
2109 
2110  /**
2111  * @param $storeId
2112  */
2113  public function setStoreId($storeId)
2114  {
2115  $this->storeId = (int)$storeId;
2116  }
2117 
2118  /**
2119  * @return Result
2120  */
2121  protected function saveStoreId()
2122  {
2123  return Delivery\ExtraServices\Manager::saveStoreIdForShipment($this->getId(), $this->getDeliveryId(), $this->getStoreId());
2124  }
2125 
2126  /**
2127  * @return float
2128  */
2129  public function getWeight() : float
2130  {
2131  return (float)$this->getField('WEIGHT');
2132  }
2133 
2134  /**
2135  * @param float $weight
2136  * @return string|null
2137  */
2138  public function setWeight(float $weight)
2139  {
2140  return $this->setField('WEIGHT', $weight);
2141  }
2142 
2143  /**
2144  * @return Delivery\CalculationResult
2145  * @throws Main\NotSupportedException
2146  */
2147  public function calculateDelivery()
2148  {
2149  if ($this->isSystem())
2150  {
2151  throw new Main\NotSupportedException();
2152  }
2153 
2154  if ($this->getDeliveryId() === 0)
2155  {
2156  return new Delivery\CalculationResult();
2157  }
2158 
2159  return Delivery\Services\Manager::calculateDeliveryPrice($this);
2160  }
2161 
2162  /**
2163  * @throws Main\ArgumentOutOfRangeException
2164  * @throws Main\NotSupportedException
2165  */
2166  public function resetData()
2167  {
2168  if (!$this->isCustomPrice())
2169  {
2170  $this->setField('BASE_PRICE_DELIVERY', 0);
2171  }
2172  }
2173 
2174  /**
2175  * @param BasketItem $basketItem
2176  * @return float|int
2177  * @throws Main\ArgumentException
2178  * @throws Main\ArgumentNullException
2179  * @throws Main\ObjectNotFoundException
2180  */
2181  public function getBasketItemQuantity(BasketItem $basketItem)
2182  {
2183  /** @var ShipmentItemCollection $shipmentItemCollection */
2185 
2186  return $shipmentItemCollection->getBasketItemQuantity($basketItem);
2187  }
2188 
2189  /**
2190  * @param string $name
2191  * @param null $oldValue
2192  * @param null $value
2193  * @throws Main\ObjectNotFoundException
2194  */
2195  protected function addChangesToHistory($name, $oldValue = null, $value = null)
2196  {
2197  if ($this->getId() > 0 && !$this->isSystem())
2198  {
2199  $order = $this->getOrder();
2200 
2201  if ($order && $order->getId() > 0)
2202  {
2203  $registry = Registry::getInstance(static::getRegistryType());
2204 
2205  /** @var OrderHistory $orderHistory */
2206  $orderHistory = $registry->getOrderHistoryClassName();
2207  $orderHistory::addField(
2208  'SHIPMENT',
2209  $order->getId(),
2210  $name,
2211  $oldValue,
2212  $value,
2213  $this->getId(),
2214  $this
2215  );
2216  }
2217  }
2218  }
2219 
2220  /**
2221  * @param BasketItem $basketItem
2222  *
2223  * @return bool
2224  * @throws Main\ObjectNotFoundException
2225  */
2226  public function isExistBasketItem(BasketItem $basketItem)
2227  {
2228  /** @var ShipmentItemCollection $shipmentItemCollection */
2230  {
2231  throw new Main\ObjectNotFoundException('Entity "ShipmentItemCollection" not found');
2232  }
2233 
2234  return $shipmentItemCollection->isExistBasketItem($basketItem);
2235  }
2236 
2237  /**
2238  * @return Result
2239  * @throws Main\ArgumentException
2240  * @throws Main\ArgumentNullException
2241  * @throws Main\ObjectNotFoundException
2242  */
2243  public function verify()
2244  {
2245  $result = new Result();
2246 
2247  if ($this->getDeliveryId() <= 0)
2248  {
2249  $result->addError(
2250  new ResultError(
2251  Loc::getMessage("SALE_SHIPMENT_DELIVERY_SERVICE_EMPTY"),
2252  'SALE_SHIPMENT_DELIVERY_SERVICE_EMPTY'
2253  )
2254  );
2255  }
2256 
2257  /** @var ShipmentItemCollection $shipmentItemCollection */
2259  {
2260  /** @var ShipmentItem $shipmentItem */
2261  foreach ($shipmentItemCollection as $shipmentItem)
2262  {
2263  $r = $shipmentItem->verify();
2264  if (!$r->isSuccess())
2265  {
2266  $result->addErrors($r->getErrors());
2267  }
2268  }
2269  }
2270 
2271  return $result;
2272  }
2273 
2274  /**
2275  * @param $id
2276  *
2277  * @return Result
2278  * @throws Main\ObjectNotFoundException
2279  * @throws \Exception
2280  */
2281  public function setAccountNumber($id)
2282  {
2283  $result = new Result();
2284  $accountNumber = null;
2285  $id = intval($id);
2286  if ($id <= 0)
2287  {
2288  $result->addError(new ResultError(Loc::getMessage('SALE_PAYMENT_GENERATE_ACCOUNT_NUMBER_ORDER_NUMBER_WRONG_ID'), 'SALE_PAYMENT_GENERATE_ACCOUNT_NUMBER_ORDER_NUMBER_WRONG_ID'));
2289  return $result;
2290  }
2291 
2293 
2294  try
2295  {
2296  /** @var \Bitrix\Sale\Result $r */
2297  $r = static::updateInternal($id, array("ACCOUNT_NUMBER" => $value));
2298  $res = $r->isSuccess(true);
2299  }
2300  catch (Main\DB\SqlQueryException $exception)
2301  {
2302  $res = false;
2303  }
2304 
2305  if ($res)
2306  {
2307  $this->setFieldNoDemand('ACCOUNT_NUMBER', $value);
2308  }
2309 
2310  return $result;
2311  }
2312 
2313  /**
2314  * @param $mapping
2315  * @return Shipment|null|string
2316  */
2317  public function getBusinessValueProviderInstance($mapping)
2318  {
2319  $providerInstance = null;
2320 
2321  if (is_array($mapping))
2322  {
2323  switch ($mapping['PROVIDER_KEY'])
2324  {
2325  case 'SHIPMENT': $providerInstance = $this; break;
2326  case 'COMPANY' : $providerInstance = $this->getField('COMPANY_ID'); break;
2327  default:
2328  $order = $this->getOrder();
2329  if ($order)
2330  {
2331  $providerInstance = $order->getBusinessValueProviderInstance($mapping);
2332  }
2333  }
2334  }
2335 
2336  return $providerInstance;
2337  }
2338 
2339  /**
2340  * @return int|null
2341  */
2342  public function getPersonTypeId()
2343  {
2344  $order = $this->getOrder();
2345  if ($order)
2346  {
2347  return $order->getPersonTypeId();
2348  }
2349 
2350  return null;
2351  }
2352 
2353  /**
2354  * @param array $parameters
2355  * @return Main\ORM\Query\Result
2356  * @throws Main\ArgumentException
2357  * @throws Main\ObjectPropertyException
2358  * @throws Main\SystemException
2359  */
2360  public static function getList(array $parameters)
2361  {
2362  return Internals\ShipmentTable::getList($parameters);
2363  }
2364 
2365  /**
2366  * @internal
2367  *
2368  * @param \SplObjectStorage $cloneEntity
2369  * @return Internals\CollectableEntity|Shipment|object
2370  * @throws Main\ArgumentException
2371  * @throws Main\ArgumentNullException
2372  * @throws Main\ObjectNotFoundException
2373  * @throws Main\SystemException
2374  */
2375  public function createClone(\SplObjectStorage $cloneEntity)
2376  {
2377  if ($this->isClone() && $cloneEntity->contains($this))
2378  {
2379  return $cloneEntity[$this];
2380  }
2381 
2382  /** @var Shipment $shipmentClone */
2383  $shipmentClone = parent::createClone($cloneEntity);
2384 
2385  /** @var ShipmentItemCollection $shipmentItemCollection */
2387  {
2388  if (!$cloneEntity->contains($shipmentItemCollection))
2389  {
2390  $cloneEntity[$shipmentItemCollection] = $shipmentItemCollection->createClone($cloneEntity);
2391  }
2392 
2393  if ($cloneEntity->contains($shipmentItemCollection))
2394  {
2395  $shipmentClone->shipmentItemCollection = $cloneEntity[$shipmentItemCollection];
2396  }
2397  }
2398 
2399  /** @var Delivery\Services\Base $service */
2400  if ($service = $this->getDelivery())
2401  {
2402  if (!$cloneEntity->contains($service))
2403  {
2404  $cloneEntity[$service] = $service->createClone($cloneEntity);
2405  }
2406 
2407  if ($cloneEntity->contains($service))
2408  {
2409  $shipmentClone->service = $cloneEntity[$service];
2410  }
2411  }
2412 
2413  return $shipmentClone;
2414  }
2415 
2416  /**
2417  * @param $status
2418  * @return Result
2419  * @throws Main\ArgumentException
2420  * @throws Main\ArgumentOutOfRangeException
2421  * @throws Main\NotSupportedException
2422  * @throws \Exception
2423  */
2424  protected function setStatus($status)
2425  {
2426  global $USER;
2427 
2428  $result = new Result();
2429 
2430  $registry = Registry::getInstance(static::getRegistryType());
2431  /** @var DeliveryStatus $deliveryStatus */
2432  $deliveryStatusClassName = $registry->getDeliveryStatusClassName();
2433 
2434  if (is_object($USER) && $USER->isAuthorized())
2435  {
2436  $statusesList = $deliveryStatusClassName::getAllowedUserStatuses($USER->getID(), $this->getField('STATUS_ID'));
2437  }
2438  else
2439  {
2440  $statusesList = $deliveryStatusClassName::getAllStatuses();
2441  }
2442 
2443  if($this->getField('STATUS_ID') != $status && array_key_exists($status, $statusesList))
2444  {
2445  /** @var Result $r */
2446  $r = $this->setField('STATUS_ID', $status);
2447  if (!$r->isSuccess())
2448  {
2449  $result->addErrors($r->getErrors());
2450  return $result;
2451  }
2452  }
2453 
2454  return $result;
2455  }
2456 
2457  /**
2458  * @param $value
2459  *
2460  * @return string
2461  */
2462  public function getErrorEntity($value)
2463  {
2464  $className = null;
2465  $errorsList = static::getAutoFixErrorsList();
2466  if (is_array($errorsList) && in_array($value, $errorsList))
2467  {
2468  $className = static::getClassName();
2469  }
2470  else
2471  {
2472  /** @var ShipmentItemCollection $shipmentItemCollection */
2474  {
2475  $className = $shipmentItemCollection->getErrorEntity($value);
2476  }
2477  }
2478 
2479  return $className;
2480  }
2481 
2482  /**
2483  * @param $value
2484  *
2485  * @return bool
2486  */
2487  public function canAutoFixError($value)
2488  {
2489  $autoFix = false;
2490  $errorsList = static::getAutoFixErrorsList();
2491  if (is_array($errorsList) && in_array($value, $errorsList))
2492  {
2493  $autoFix = true;
2494  }
2495  else
2496  {
2497  /** @var ShipmentItemCollection $shipmentItemCollection */
2499  {
2500  $autoFix = $shipmentItemCollection->canAutoFixError($value);
2501  }
2502  }
2503 
2504  return $autoFix;
2505  }
2506 
2507  /**
2508  * @return array
2509  */
2510  public function getAutoFixErrorsList()
2511  {
2512  return array_keys(static::getAutoFixRules());
2513  }
2514 
2515  /**
2516  * @param $code
2517  *
2518  * @return Result
2519  */
2520  public function tryFixError($code)
2521  {
2522  $result = new Result();
2523 
2524  $method = static::getFixMethod($code);
2525  $r = call_user_func_array($method, array($this));
2526  if (!$r->isSuccess())
2527  {
2528  $result->addErrors($r->getErrors());
2529  }
2530  elseif ($r->hasWarnings())
2531  {
2532  $result->addWarnings($r->getWarnings());
2533  }
2534 
2535  return $result;
2536  }
2537 
2538  /**
2539  * @param $code
2540  * @return mixed|null
2541  */
2542  protected static function getFixMethod($code)
2543  {
2544  $codeList = static::getAutoFixRules();
2545 
2546  if (!empty($codeList[$code]))
2547  {
2548  return $codeList[$code];
2549  }
2550  return null;
2551  }
2552 
2553  /**
2554  * @param Shipment $entity
2555  *
2556  * @return Result
2557  * @throws Main\ObjectNotFoundException
2558  */
2559  public static function fixReserveErrors(Shipment $entity)
2560  {
2561  $result = new Result();
2562 
2563  $r = $entity->tryReserve();
2564  if (!$r->isSuccess())
2565  {
2566  $result->addErrors($r->getErrors());
2567  }
2568  elseif ($r->hasWarnings())
2569  {
2570  $result->addWarnings($r->getWarnings());
2571  }
2572 
2573  return $result;
2574  }
2575 
2576  /**
2577  * @param Shipment $entity
2578  *
2579  * @return Result
2580  * @throws Main\ObjectNotFoundException
2581  */
2582  public static function fixShipErrors(Shipment $entity)
2583  {
2584  $result = new Result();
2585 
2586  $r = $entity->setField('DEDUCTED', 'Y');
2587  if (!$r->isSuccess())
2588  {
2589  if (!$r->isSuccess())
2590  {
2591  $result->addErrors($r->getErrors());
2592  }
2593  }
2594 
2595  $r = $entity->tryShip();
2596  if (!$r->isSuccess())
2597  {
2598  if (!$r->isSuccess())
2599  {
2600  $result->addErrors($r->getErrors());
2601  }
2602  }
2603 
2604  return $result;
2605  }
2606 
2607  /**
2608  * @return array
2609  */
2610  protected static function getAutoFixRules()
2611  {
2612  return [
2613  'PROVIDER_BASKET_ITEM_WRONG_AVAILABLE_QUANTITY' => ['\Bitrix\Sale\Shipment', "fixReserveErrors"],
2614  'SALE_PROVIDER_RESERVE_SHIPMENT_ITEM_WRONG_AVAILABLE_QUANTITY' => ['\Bitrix\Sale\Shipment', "fixReserveErrors"],
2615  'PROVIDER_UNRESERVE_SHIPMENT_ITEM_WRONG_AVAILABLE_QUANTITY' => ['\Bitrix\Sale\Shipment', "fixReserveErrors"],
2616  'SALE_PROVIDER_RESERVE_SHIPMENT_ITEM_QUANTITY_NOT_ENOUGH' => ['\Bitrix\Sale\Shipment', "fixReserveErrors"],
2617 
2618  'SALE_PROVIDER_SHIPMENT_SHIPPED_LESS_QUANTITY' => ['\Bitrix\Sale\Shipment', "fixShipErrors"],
2619  'SALE_PROVIDER_SHIPMENT_SHIPPED_MORE_QUANTITY' => ['\Bitrix\Sale\Shipment', "fixShipErrors"],
2620  'DDCT_DEDUCTION_QUANTITY_STORE_ERROR' => ['\Bitrix\Sale\Shipment', "fixShipErrors"],
2621  'SALE_PROVIDER_SHIPMENT_QUANTITY_NOT_ENOUGH' => ['\Bitrix\Sale\Shipment', "fixShipErrors"],
2622  'DDCT_DEDUCTION_QUANTITY_ERROR' => ['\Bitrix\Sale\Shipment', "fixShipErrors"],
2623  ];
2624  }
2625 
2626  /**
2627  * @return bool
2628  */
2629  public function canMarked()
2630  {
2631  return true;
2632  }
2633 
2634  /**
2635  * @return string
2636  */
2637  public function getMarkField()
2638  {
2639  return 'MARKED';
2640  }
2641 
2642  /**
2643  * @return bool
2644  * @throws Main\ArgumentException
2645  * @throws Main\ArgumentNullException
2646  * @throws Main\ObjectNotFoundException
2647  */
2648  public function isChanged()
2649  {
2650  if (parent::isChanged())
2651  {
2652  return true;
2653  }
2654 
2655  return $this->getShipmentItemCollection()->isChanged();
2656  }
2657 
2658  /**
2659  * @internal
2660  */
2661  public function clearChanged()
2662  {
2663  parent::clearChanged();
2664 
2666  {
2667  /** @var ShipmentItem $shipmentItem */
2668  foreach ($shipmentItemCollection as $shipmentItem)
2669  {
2670  $shipmentItem->clearChanged();
2671  }
2672  }
2673  }
2674 
2675  /**
2676  * @return float|int
2677  * @throws Main\ArgumentException
2678  * @throws Main\ArgumentNullException
2679  * @throws Main\LoaderException
2680  * @throws Main\ObjectPropertyException
2681  * @throws Main\SystemException
2682  */
2683  public function getVatRate()
2684  {
2685  $vatRate = 0;
2686 
2687  $service = $this->getDelivery();
2688  if ($service)
2689  {
2690  if (!Main\Loader::includeModule('catalog'))
2691  {
2692  return $vatRate;
2693  }
2694 
2695  $vatId = $service->getVatId();
2696  if ($vatId <= 0)
2697  {
2698  return $vatRate;
2699  }
2700 
2701  $dbRes = VatTable::getById($vatId);
2702  $vatInfo = $dbRes->fetch();
2703  if ($vatInfo)
2704  {
2705  $vatRate = $vatInfo['RATE'] / 100;
2706  }
2707  }
2708 
2709  return $vatRate;
2710  }
2711 
2712  /**
2713  * @return float
2714  * @throws Main\ArgumentNullException
2715  * @throws Main\LoaderException
2716  */
2717  public function getVatSum()
2718  {
2719  $vatRate = $this->getVatRate();
2720  $price = $this->getPrice() * $vatRate / (1 + $vatRate);
2721 
2722  return PriceMaths::roundPrecision($price);
2723  }
2724 
2725  /**
2726  * @param array $data
2727  * @return Entity\AddResult
2728  * @throws \Exception
2729  */
2730  protected function addInternal(array $data)
2731  {
2732  return Internals\ShipmentTable::add($data);
2733  }
2734 
2735  /**
2736  * @param $primary
2737  * @param array $data
2738  * @return Entity\UpdateResult
2739  * @throws \Exception
2740  */
2741  protected function updateInternal($primary, array $data)
2742  {
2743  return Internals\ShipmentTable::update($primary, $data);
2744  }
2745 
2746  /**
2747  * @param $primary
2748  * @return Entity\DeleteResult
2749  * @throws Main\ArgumentException
2750  * @throws Main\ArgumentNullException
2751  */
2752  protected static function deleteInternal($primary)
2753  {
2754  return Internals\ShipmentTable::deleteWithItems($primary);
2755  }
2756 
2757  /**
2758  * @return array
2759  */
2760  protected static function getFieldsMap()
2761  {
2763  }
2764 
2765  /**
2766  * @return null
2767  */
2768  public static function getUfId()
2769  {
2770  return Internals\ShipmentTable::getUfId();
2771  }
2772 
2773  /**
2774  * @param $value
2775  * @param bool $custom
2776  *
2777  * @return Result
2778  * @throws Main\ArgumentOutOfRangeException
2779  * @throws Main\NotSupportedException
2780  * @throws \Exception
2781  */
2782  public function setBasePriceDelivery($value, $custom = false)
2783  {
2784  $result = new Result();
2785 
2786  if ($custom === true)
2787  {
2788  $this->markFieldCustom('PRICE_DELIVERY');
2789  }
2790 
2791  $r = $this->setField('BASE_PRICE_DELIVERY', $value);
2792  if (!$r->isSuccess())
2793  {
2794  $result->addErrors($r->getErrors());
2795  }
2796 
2797  return $result;
2798  }
2799 
2800  /**
2801  * @return null|string
2802  * @internal
2803  *
2804  */
2805  public static function getEntityEventName()
2806  {
2807  return 'SaleShipment';
2808  }
2809 
2810  /**
2811  * @return array
2812  * @throws Main\ArgumentException
2813  * @throws Main\SystemException
2814  */
2815  public function toArray() : array
2816  {
2817  $result = parent::toArray();
2818 
2819  $result['ITEMS'] = $this->getShipmentItemCollection()->toArray();
2820 
2821  return $result;
2822  }
2823 
2824  /**
2825  * @deprecated Use getOrder instead
2826  *
2827  * @return Order|null
2828  */
2829  public function getParentOrder()
2830  {
2831  return $this->getOrder();
2832  }
2833 }
2834 
Exception is thrown when function argument is not valid.
Exception is thrown when "empty" value is passed to a function that does not accept it as a valid arg...
static get($moduleId, $name, $default="", $siteId=false)
Returns a value of an option.
static includeModule($moduleName)
Includes a module by its name.
static loadMessages($file)
Loads language messages for specified file in a lazy way.
Definition: loc.php:67
static getMessage($code, $replace=null, $language=null)
Returns translation by message code.
Definition: loc.php:29
Exception is thrown when operation is not supported.
Exception is thrown when an object is not present.
static getProductReservationCondition()
Returns current reservation condition.
static getMap()
Returns entity map definition.
const EVENT_ON_BEFORE_SHIPMENT_STATUS_CHANGE
const EVENT_ON_SHIPMENT_TRACKING_NUMBER_CHANGE
const EVENT_ON_SHIPMENT_STATUS_CHANGE_SEND_MAIL
static resetEvents(Order $order=null)
Definition: eventspool.php:32
static addEvent(Order $order, $type, $event)
Definition: eventspool.php:24
static getEvents(Order $order)
Definition: eventspool.php:9
static generateForShipment(CollectableEntity $item)
static tryUnreserveShipmentItem(Sale\ShipmentItem $shipmentItem)
static tryReserveShipment(Sale\Shipment $shipment, array $context=array())
static tryShipShipment(Sale\Shipment $shipment, array $context=array())
static tryReserveShipmentItem(Sale\ShipmentItem $shipmentItem, array $context=array())
static delete($code, $type, $index)
Definition: poolbase.php:62
static add($code, $type, $value)
Definition: poolbase.php:51
static get($code, $type)
Definition: poolbase.php:19
static isTypeExists($code, $type)
Definition: poolbase.php:76
static roundPrecision($value)
Definition: pricemaths.php:17
static repeat(Order $order, array $resultList)
Definition: recurring.php:26
onFieldModify($name, $oldValue, $value)
Definition: shipment.php:1743
setDeliveryService(Delivery\Services\Base $service)
Definition: shipment.php:180
setBasePriceDelivery($value, $custom=false)
Definition: shipment.php:2782
setWeight(float $weight)
Definition: shipment.php:2138
static deleteNoDemand($orderId)
Definition: shipment.php:696
static create(ShipmentCollection $collection, Delivery\Services\Base $service=null)
Definition: shipment.php:198
static getRegistryEntity()
Definition: shipment.php:39
updateInternal($primary, array $data)
Definition: shipment.php:2741
createClone(\SplObjectStorage $cloneEntity)
Definition: shipment.php:2375
setServiceParams(array $serviceParams)
Definition: shipment.php:2052
syncQuantityAfterModify(BasketItem $basketItem, $value=null, $oldValue=null)
Definition: shipment.php:1937
getBasketItemQuantity(BasketItem $basketItem)
Definition: shipment.php:2181
checkValueBeforeSet($name, $value)
Definition: shipment.php:822
static getFixMethod($code)
Definition: shipment.php:2542
getBusinessValueProviderInstance($mapping)
Definition: shipment.php:2317
static getMeaningfulFields()
Definition: shipment.php:169
static getCustomizableFields()
Definition: shipment.php:132
__construct(array $fields=[])
Definition: shipment.php:44
static getAutoFixRules()
Definition: shipment.php:2610
isExistBasketItem(BasketItem $basketItem)
Definition: shipment.php:2226
onBeforeBasketItemDelete(BasketItem $basketItem)
Definition: shipment.php:1622
static loadForOrder($id)
Definition: shipment.php:900
static getEntityEventName()
Definition: shipment.php:2805
static createSystem(ShipmentCollection $collection, Delivery\Services\Base $deliveryService=null)
Definition: shipment.php:1247
static getAvailableFields()
Definition: shipment.php:82
static fixReserveErrors(Shipment $entity)
Definition: shipment.php:2559
static getList(array $parameters)
Definition: shipment.php:2360
setStoreId($storeId)
Definition: shipment.php:2113
onBeforeSetFields(array $values)
Definition: shipment.php:141
static fixShipErrors(Shipment $entity)
Definition: shipment.php:2582
addInternal(array $data)
Definition: shipment.php:2730
addChangesToHistory($name, $oldValue=null, $value=null)
Definition: shipment.php:2195
setFieldNoDemand($name, $value)
Definition: shipment.php:867
static generateXmlId()
Definition: shipment.php:236
static deleteInternal($primary)
Definition: shipment.php:2752
onBasketModify($action, BasketItem $basketItem, $name=null, $oldValue=null, $value=null)
Definition: shipment.php:1655
setField($name, $value)
Sets new value to specified field of shipment item.
Definition: shipment.php:778
setOrderId($orderId)
Definition: shipment.php:1304
static getRegistryType()
Definition: shipment.php:257
setExtraServices(array $extraServices)
Definition: shipment.php:2080
onShipmentItemCollectionModify($action, ShipmentItem $shipmentItem, $name=null, $oldValue=null, $value=null)
Definition: shipment.php:635
static updateReservedFlag(Shipment $shipment)
Definition: shipment.php:544
__construct(Base $connector)
Constructor.
Definition: resultview.php:40