diff --git a/vendor/magento/module-rma/Model/ItemCountValidator.php b/vendor/magento/module-rma/Model/ItemCountValidator.php new file mode 100644 index 000000000000..150367206c7f --- /dev/null +++ b/vendor/magento/module-rma/Model/ItemCountValidator.php @@ -0,0 +1,265 @@ +dataHelper = $dataHelper; + $this->itemFactory = $itemFactory; + $this->escaper = $escaper; + } + + /** + * Validate Return items quantity + * + * @param Rma $value + * @return bool + * @throws LocalizedException + */ + public function isValid($value) + { + if ($value->getStatus() == Status::STATE_CLOSED) { + return true; + } + + if ($value->getIsUpdate() || $value->getEntityId()) { + $items = $value->getItems(); + foreach ($items as $item) { + $this->checkQuantities($item); + $this->checkQuantityStatuses($item); + } + } else { + if (false === in_array($value->getStatus(), Status::STATE_ALL)) { + $this->_addMessages([__('Invalid status provided: %1', $value->getStatus())]); + return false; + } + } + + $this->checkAvailability($value); + + if (!empty($this->getMessages())) { + return false; + } + + return true; + } + + /** + * Checks Rma items against initial order items + * + * @param Rma $value + * @return void + * @throws LocalizedException + */ + private function checkAvailability(Rma $value): void + { + $errors = $errorKeys = []; + $availableItemsArray = $this->getAvailableItems($value); + $itemsArray = $this->getItemsTotals($value); + foreach ($itemsArray as $key => $info) { + if (!array_key_exists($key, $availableItemsArray)) { + $errors['return_item_not_allowed'] = __('You cannot return %1.', $key); + continue; + } + if ($availableItemsArray[$key]['qty'] < $info['quantity']) { + $escapedProductName = $this->escaper->escapeHtml($availableItemsArray[$key]['name']); + $errors['return_item_quantity_not_allowed'] = + __('A quantity of %1 is greater than you can return.', $escapedProductName); + $errorKeys[$key] = 'qty_requested'; + $errorKeys['tabs'] = 'items_section'; + } + } + + if ($errors || $errorKeys) { + $this->_addMessages(array_merge($errors, ['error_keys' => $errorKeys])); + } + } + + /** + * Extract total quantities + * + * @param Rma $value + * @return array + */ + private function getItemsTotals(Rma $value): array + { + $itemsArray = []; + $items = $value->getItems(); + foreach ($items as $item) { + if (!isset($itemsArray[$item->getOrderItemId()])) { + $itemsArray[$item->getOrderItemId()]['quantity'] = $item->getQtyRequested(); + } else { + $itemsArray[$item->getOrderItemId()]['quantity'] += $item->getQtyRequested(); + } + $itemsArray[$item->getOrderItemId()]['status'] = $item->getStatus(); + } + ksort($itemsArray); + + return $itemsArray; + } + + /** + * Validate item quantity status + * + * @param ItemInterface $item + * @return void + */ + private function checkQuantityStatuses(ItemInterface $item): void + { + $errors = $errorKeys = []; + $escapedProductName = $this->escaper->escapeHtml($item->getProductName()); + + //if we change item status i.e. to authorized, then qty_authorized must be non-empty and so on. + foreach ($this->getQtyToStatus() as $qtyKey => $qtyValue) { + if ($item->getStatus() === $qtyValue['status'] + && $item->getOrigData('status') !== $qtyValue['status'] + && !$item->getData($qtyKey) + ) { + $errors[] = __('%1 for item %2 cannot be empty.', $qtyValue['name'], $escapedProductName); + $errorKeys[$item->getId()] = $qtyKey; + $errorKeys['tabs'] = 'items_section'; + } + } + + if ($errors || $errorKeys) { + $this->_addMessages(array_merge($errors, ['error_keys' => $errorKeys])); + } + } + + /** + * Check if there are enough items in the order to perform a return + * + * @param ItemInterface $item + * @return void + */ + private function checkQuantities(ItemInterface $item): void + { + $validation = $errors = $errorKeys = []; + foreach ([Rma::QTY_REQUESTED, Rma::QTY_AUTHORIZED, Rma::QTY_RETURNED, Rma::QTY_APPROVED] as $tempQty) { + $quantity = $item->getData($tempQty); + if ($quantity === null) { + if ($item->getOrigData($tempQty) !== null) { + $validation[$tempQty] = (double)$item->getOrigData($tempQty); + } + } else { + $validation[$tempQty] = (double)$quantity; + } + } + $validation['dummy'] = -1; + $previousValue = null; + $escapedProductName = $this->escaper->escapeHtml($item->getProductName()); + foreach ($validation as $key => $val) { + if (isset($previousValue) && $val > $previousValue) { + $errors[] = __('There is an error in quantities for item %1.', $escapedProductName); + $errorKeys[$item->getId()] = $key; + $errorKeys['tabs'] = 'items_section'; + break; + } + $previousValue = $val; + } + + if ($errors || $errorKeys) { + $this->_addMessages(array_merge($errors, ['error_keys' => $errorKeys])); + } + } + + /** + * Extract all order items + * + * @param Rma $value + * @return array + * @throws LocalizedException + */ + private function getAvailableItems(Rma $value): array + { + $order = $value->getOrder(); + if (!$value->getEntityId()) { + $availableItems = $this->dataHelper->getOrderItems($order->getId())->getItems(); + } else { + $itemResource = $this->itemFactory->create(); + $availableItems = $itemResource->getOrderItemsCollection($order->getId()); + } + + $availableItemsArray = []; + foreach ($availableItems as $item) { + $availableItemsArray[$item->getId()] = [ + 'name' => $item->getName(), + 'qty' => $item->getAvailableQty() + ]; + } + + return $availableItemsArray; + } + + /** + * Get relevant Rma statuses + * + * @return array[] + */ + private function getQtyToStatus(): array + { + return [ + 'qty_authorized' => [ + 'name' => __('Authorized Qty'), + 'status' => Status::STATE_AUTHORIZED, + ], + 'qty_returned' => [ + 'name' => __('Returned Qty'), + 'status' => Status::STATE_RECEIVED, + ], + 'qty_approved' => [ + 'name' => __('Approved Qty'), + 'status' => Status::STATE_APPROVED, + ], + ]; + } +} diff --git a/vendor/magento/module-rma/Model/Rma.php b/vendor/magento/module-rma/Model/Rma.php index ed1b9bdcfc9b..7fbc6891f1b5 100644 --- a/vendor/magento/module-rma/Model/Rma.php +++ b/vendor/magento/module-rma/Model/Rma.php @@ -7,6 +7,7 @@ namespace Magento\Rma\Model; +use Magento\Framework\Validator\ValidatorInterface; use Magento\Framework\Api\AttributeValueFactory; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\LocalizedException; @@ -20,7 +21,6 @@ use Magento\Store\Model\Store; /** - * RMA model * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -56,6 +56,14 @@ class Rma extends \Magento\Sales\Model\AbstractModel implements \Magento\Rma\Api public const TRACKS = 'tracks'; + public const QTY_AUTHORIZED = 'qty_authorized'; + + public const QTY_RETURNED = 'qty_returned'; + + public const QTY_APPROVED = 'qty_approved'; + + public const QTY_REQUESTED = 'qty_requested'; + /**#@-*/ /** @@ -114,13 +122,6 @@ class Rma extends \Magento\Sales\Model\AbstractModel implements \Magento\Rma\Api */ protected $_storeManager; - /** - * Eav configuration model - * - * @var \Magento\Eav\Model\Config - */ - protected $_eavConfig; - /** * Rma item factory model * @@ -135,13 +136,6 @@ class Rma extends \Magento\Sales\Model\AbstractModel implements \Magento\Rma\Api */ protected $_attrSourceFactory; - /** - * Rma grid factory model - * - * @var \Magento\Rma\Model\GridFactory - */ - protected $_rmaGridFactory; - /** * Rma source status factory * @@ -219,11 +213,6 @@ class Rma extends \Magento\Sales\Model\AbstractModel implements \Magento\Rma\Api */ protected $_shippingFactory; - /** - * @var \Magento\Framework\Escaper - */ - protected $_escaper; - /** * @var \Magento\Framework\Message\ManagerInterface */ @@ -262,6 +251,16 @@ class Rma extends \Magento\Sales\Model\AbstractModel implements \Magento\Rma\Api */ protected $_eventObject = 'rma'; + /** + * @var ValidatorInterface + */ + private ValidatorInterface $validator; + + /** + * @var array + */ + private array $errorKeys = []; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -270,10 +269,8 @@ class Rma extends \Magento\Sales\Model\AbstractModel implements \Magento\Rma\Api * @param \Magento\Rma\Helper\Data $rmaData * @param \Magento\Framework\Session\Generic $session * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Eav\Model\Config $eavConfig * @param ItemFactory $rmaItemFactory * @param Item\Attribute\Source\StatusFactory $attrSourceFactory - * @param GridFactory $rmaGridFactory * @param Rma\Source\StatusFactory $statusFactory * @param \Magento\Rma\Model\ResourceModel\ItemFactory $itemFactory * @param \Magento\Rma\Model\ResourceModel\Item\CollectionFactory $itemsFactory @@ -285,7 +282,6 @@ class Rma extends \Magento\Sales\Model\AbstractModel implements \Magento\Rma\Api * @param \Magento\Sales\Model\ResourceModel\Order\Item\CollectionFactory $ordersFactory * @param \Magento\Quote\Model\Quote\Address\RateRequestFactory $rateRequestFactory * @param \Magento\Shipping\Model\ShippingFactory $shippingFactory - * @param \Magento\Framework\Escaper $escaper * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Framework\Message\ManagerInterface $messageManager * @param RmaAttributesManagementInterface $metadataService @@ -294,6 +290,7 @@ class Rma extends \Magento\Sales\Model\AbstractModel implements \Magento\Rma\Api * @param array $data * @param Json|null $serializer * @param EntityAttributesLoader|null $attributesLoader + * @param ValidatorInterface|null $validator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -304,10 +301,8 @@ public function __construct( \Magento\Rma\Helper\Data $rmaData, \Magento\Framework\Session\Generic $session, \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Eav\Model\Config $eavConfig, \Magento\Rma\Model\ItemFactory $rmaItemFactory, \Magento\Rma\Model\Item\Attribute\Source\StatusFactory $attrSourceFactory, - \Magento\Rma\Model\GridFactory $rmaGridFactory, \Magento\Rma\Model\Rma\Source\StatusFactory $statusFactory, \Magento\Rma\Model\ResourceModel\ItemFactory $itemFactory, \Magento\Rma\Model\ResourceModel\Item\CollectionFactory $itemsFactory, @@ -319,7 +314,6 @@ public function __construct( \Magento\Sales\Model\ResourceModel\Order\Item\CollectionFactory $ordersFactory, \Magento\Quote\Model\Quote\Address\RateRequestFactory $rateRequestFactory, \Magento\Shipping\Model\ShippingFactory $shippingFactory, - \Magento\Framework\Escaper $escaper, \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, \Magento\Framework\Message\ManagerInterface $messageManager, RmaAttributesManagementInterface $metadataService, @@ -327,16 +321,15 @@ public function __construct( \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], Json $serializer = null, - EntityAttributesLoader $attributesLoader = null + EntityAttributesLoader $attributesLoader = null, + ?ValidatorInterface $validator = null ) { $objectManager = ObjectManager::getInstance(); $this->_rmaData = $rmaData; $this->_session = $session; $this->_storeManager = $storeManager; - $this->_eavConfig = $eavConfig; $this->_rmaItemFactory = $rmaItemFactory; $this->_attrSourceFactory = $attrSourceFactory; - $this->_rmaGridFactory = $rmaGridFactory; $this->_statusFactory = $statusFactory; $this->_itemFactory = $itemFactory; $this->_itemsFactory = $itemsFactory; @@ -348,12 +341,12 @@ public function __construct( $this->_ordersFactory = $ordersFactory; $this->_rateRequestFactory = $rateRequestFactory; $this->_shippingFactory = $shippingFactory; - $this->_escaper = $escaper; $this->_localeDate = $localeDate; $this->messageManager = $messageManager; $this->metadataService = $metadataService; $this->serializer = $serializer ?: $objectManager->get(Json::class); $this->attributesLoader = $attributesLoader ?: $objectManager->get(EntityAttributesLoader::class); + $this->validator = $validator ?: $objectManager->get(ValidatorInterface::class); parent::__construct( $context, $registry, @@ -722,11 +715,11 @@ public function close() * * @param array $data * @return bool|$this + * @throws \Exception */ public function saveRma($data) { // TODO: move errors adding to controller - $errors = 0; $this->messageManager->getMessages(true); if ($this->getCustomerCustomEmail()) { $validateEmail = $this->_validateEmail($this->getCustomerCustomEmail()); @@ -735,17 +728,16 @@ public function saveRma($data) $this->messageManager->addError($error); } $this->_session->setRmaFormData($data); - $errors = 1; } } - $itemModels = $this->_createItemsCollection($data); - if (!$itemModels || $errors) { + try { + $this->_createItemsCollection($data); + } catch (\Throwable $e) { return false; } - $this->save(); - return $this; + return $this->save(); } /** @@ -753,332 +745,80 @@ public function saveRma($data) * * @param array $item * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _preparePost($item) { - $errors = false; $preparePost = []; - $qtyKeys = ['qty_authorized', 'qty_returned', 'qty_approved']; - ksort($item); - foreach ($item as $key => $value) { - if ($key == 'order_item_id') { - $preparePost['order_item_id'] = (int)$value; - } elseif ($key == 'qty_requested') { - $preparePost['qty_requested'] = is_numeric($value) ? $value : 0; - } elseif (in_array($key, $qtyKeys)) { - if (is_numeric($value)) { - $preparePost[$key] = (double)$value; - } else { - $preparePost[$key] = ''; - } - } elseif ($key == 'resolution') { - $preparePost['resolution'] = (int)$value; - } elseif ($key == 'condition') { - $preparePost['condition'] = (int)$value; - } elseif ($key == 'reason') { - $preparePost['reason'] = (int)$value; - } elseif ($key == 'reason_other' && !empty($value)) { - $preparePost['reason_other'] = $value; - } else { - $preparePost[$key] = $value; - } - } - - $order = $this->getOrder(); - $realItem = $order->getItemById($preparePost['order_item_id']); - - $stat = Status::STATE_PENDING; - if (!empty($preparePost['status'])) { - /** @var $status Status */ - $status = $this->_attrSourceFactory->create(); - if ($status->checkStatus($preparePost['status'])) { - $stat = $preparePost['status']; - } - } - - $preparePost['status'] = $stat; - - $preparePost['product_name'] = $realItem->getName(); - $preparePost['product_sku'] = $realItem->getSku(); - $preparePost['product_admin_name'] = $this->_rmaData->getAdminProductName($realItem); - $preparePost['product_admin_sku'] = $this->_rmaData->getAdminProductSku($realItem); - $preparePost['product_options'] = $this->serializer->serialize($realItem->getProductOptions()); - $preparePost['is_qty_decimal'] = $realItem->getIsQtyDecimal(); - - if ($preparePost['is_qty_decimal']) { - $preparePost['qty_requested'] = (double)$preparePost['qty_requested']; - } else { - $preparePost['qty_requested'] = (int)$preparePost['qty_requested']; - - foreach ($qtyKeys as $key) { - if (!empty($preparePost[$key])) { - $preparePost[$key] = (int)$preparePost[$key]; - } - } - } - - if (isset($preparePost['qty_requested']) && $preparePost['qty_requested'] <= 0) { - $errors = true; - } - - foreach ($qtyKeys as $key) { - if (isset($preparePost[$key]) && !is_string($preparePost[$key]) && $preparePost[$key] <= 0) { - $errors = true; - } - } - - if ($errors) { - $this->messageManager->addError( - __('There is an error in quantities for item %1.', $preparePost['product_name']) - ); - } - return $preparePost; - } - - /** - * Checks Items Quantity in Return - * - * @param Item $itemModels - * @param int $orderId - * @return array|bool - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - protected function _checkPost($itemModels, $orderId) - { - $errors = []; - $errorKeys = []; - if (!$this->getIsUpdate()) { - $availableItems = $this->_rmaData->getOrderItems($orderId); - } else { - /** @var $itemResource \Magento\Rma\Model\ResourceModel\Item */ - $itemResource = $this->_itemFactory->create(); - $availableItems = $itemResource->getOrderItemsCollection($orderId); - } - - $itemsArray = []; - foreach ($itemModels as $item) { - if (!isset($itemsArray[$item->getOrderItemId()])) { - $itemsArray[$item->getOrderItemId()] = $item->getQtyRequested(); - } else { - $itemsArray[$item->getOrderItemId()] += $item->getQtyRequested(); - } - - if ($this->getIsUpdate()) { - $validation = []; - foreach (['qty_requested', 'qty_authorized', 'qty_returned', 'qty_approved'] as $tempQty) { - if ($item->getData($tempQty) === null) { - if ($item->getOrigData($tempQty) !== null) { - $validation[$tempQty] = (double)$item->getOrigData($tempQty); - } - } else { - $validation[$tempQty] = (double)$item->getData($tempQty); - } - } - $validation['dummy'] = -1; - $previousValue = null; - $escapedProductName = $this->_escaper->escapeHtml($item->getProductName()); - foreach ($validation as $key => $value) { - if (isset($previousValue) && $value > $previousValue) { - $errors[] = __('There is an error in quantities for item %1.', $escapedProductName); - $errorKeys[$item->getId()] = $key; - $errorKeys['tabs'] = 'items_section'; - break; - } - $previousValue = $value; - } - - //if we change item status i.e. to authorized, then qty_authorized must be non-empty and so on. - $qtyToStatus = [ - 'qty_authorized' => [ - 'name' => __('Authorized Qty'), - 'status' => \Magento\Rma\Model\Rma\Source\Status::STATE_AUTHORIZED, - ], - 'qty_returned' => [ - 'name' => __('Returned Qty'), - 'status' => \Magento\Rma\Model\Rma\Source\Status::STATE_RECEIVED, - ], - 'qty_approved' => [ - 'name' => __('Approved Qty'), - 'status' => \Magento\Rma\Model\Rma\Source\Status::STATE_APPROVED, - ], - ]; - foreach ($qtyToStatus as $qtyKey => $qtyValue) { - if ($item->getStatus() === $qtyValue['status'] - && $item->getOrigData( - 'status' - ) !== $qtyValue['status'] - && !$item->getData( - $qtyKey - ) - ) { - $errors[] = __('%1 for item %2 cannot be empty.', $qtyValue['name'], $escapedProductName); - $errorKeys[$item->getId()] = $qtyKey; - $errorKeys['tabs'] = 'items_section'; - } - } + foreach ($item as $key => $value) { + switch ($key) { + case 'order_item_id': + $preparePost['order_item_id'] = (int)$value; + break; + case 'qty_requested': + $preparePost['qty_requested'] = is_numeric($value) ? $value : 0; + break; + case 'resolution': + $preparePost['resolution'] = (int)$value; + break; + case 'condition': + $preparePost['condition'] = (int)$value; + break; + case 'reason': + $preparePost['reason'] = (int)$value; + break; + case 'reason_other': + $preparePost['reason_other'] = $value; + break; + default: + $preparePost[$key] = $value; } } - ksort($itemsArray); - - $availableItemsArray = []; - foreach ($availableItems as $item) { - $availableItemsArray[$item->getId()] = [ - 'name' => $item->getName(), - 'qty' => $item->getAvailableQty(), - ]; - } - foreach ($itemsArray as $key => $qty) { - $escapedProductName = $this->_escaper->escapeHtml($availableItemsArray[$key]['name']); - if (!array_key_exists($key, $availableItemsArray)) { - $errors[] = __('You cannot return %1.', $escapedProductName); - } - if (isset($availableItemsArray[$key]) && $availableItemsArray[$key]['qty'] < $qty) { - $errors[] = __('A quantity of %1 is greater than you can return.', $escapedProductName); - $errorKeys[$key] = 'qty_requested'; - $errorKeys['tabs'] = 'items_section'; - } - } + $preparePost = $this->preparePostItemStatus($preparePost); + $preparePost = $this->preparePostItemProductDetails($preparePost); - if (count($errors)) { - return [$errors, $errorKeys]; - } - return true; + return $this->preparePostItemQuantities($preparePost); } /** * Creates rma items collection by passed data * - * @param array $data + * @param mixed $data * @return Item[] - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @throws \Exception */ - protected function _createItemsCollection($data) + protected function _createItemsCollection($data): array { if (!is_array($data)) { $data = (array)$data; } - $order = $this->getOrder(); - $itemModels = []; - $errors = []; - $errorKeys = []; + $itemModels = []; foreach ($data['items'] as $key => $item) { - if (isset($item['items'])) { - $itemModel = $firstModel = false; - $files = $f = []; - foreach ($item['items'] as $id => $qty) { - if ($itemModel) { - $firstModel = $itemModel; - } - /** @var $itemModel Item */ - $itemModel = $this->_rmaItemFactory->create(); - $subItem = $item; - unset($subItem['items']); - $subItem['order_item_id'] = $id; - $subItem['qty_requested'] = $qty; - - $itemPost = $this->_preparePost($subItem); - - $f = $itemModel->setData($itemPost)->prepareAttributes($itemPost, $key); - - /* Copy image(s) to another bundle items */ - if (!empty($f)) { - $files = $f; - } - if (!empty($files) && $firstModel) { - foreach ($files as $code) { - $itemModel->setData($code, $firstModel->getData($code)); - } - } - // @codingStandardsIgnoreStart - $errors = array_merge($itemModel->getErrors(), $errors); - // @codingStandardsIgnoreEnd - - $itemModels[] = $itemModel; - } - } else { - /** @var $itemModel Item */ - $itemModel = $this->_rmaItemFactory->create(); - if (isset($item['entity_id']) && $item['entity_id']) { - $itemModel->load($item['entity_id']); - if ($itemModel->getEntityId()) { - if (empty($item['reason'])) { - $item['reason'] = $itemModel->getReason(); - } - - if (empty($item['reason_other'])) { - $item['reason_other'] = - $itemModel->getReasonOther() === null ? '' : $itemModel->getReasonOther(); - } - - if (empty($item['condition'])) { - $item['condition'] = $itemModel->getCondition(); - } - - if (empty($item['qty_requested'])) { - $item['qty_requested'] = $itemModel->getQtyRequested(); - } - } - } - - $itemPost = $this->_preparePost($item); - - $itemModel->setData($itemPost)->prepareAttributes($itemPost, $key); - //check if entity_id present and its a split return. Reset the entity - if ($itemModel->getEntityId() && - !is_numeric($itemModel->getEntityId())) { - $itemModel->setEntityId(null); - } - // @codingStandardsIgnoreStart - $errors = array_merge($itemModel->getErrors(), $errors); - // @codingStandardsIgnoreEnd - if ($errors) { - $errorKeys['tabs'] = 'items_section'; - } - - $itemModels[] = $itemModel; - - if ($this->isStatusNeedsAuthEmail($itemModel->getStatus()) - && $itemModel->getOrigData( - 'status' - ) !== $itemModel->getStatus() - ) { - $this->setIsSendAuthEmail(1); - } - } + $itemModels = $this->mergeModels($itemModels, $this->createItemModel($item, (string)$key)); } - - $result = $this->_checkPost($itemModels, $order->getId()); + $this->setItems($itemModels); + $result = $this->validator->isValid($this); if ($result !== true) { - list($result, $errorKey) = $result; - $errors = array_merge($result, $errors); - $errorKeys = array_merge($errorKey, $errorKeys); + $errors = $this->validator->getMessages(); + $this->errorKeys = array_merge($this->errorKeys, $errors['error_keys'] ?: []); + unset($errors['error_keys']); + $this->addErrors($errors); } - $eMessages = $this->messageManager->getMessages()->getErrors(); - if (!empty($errors) || !empty($eMessages)) { + if (!empty($this->messageManager->getMessages()->getErrors())) { $this->_session->setRmaFormData($data); - if (!empty($errorKeys)) { - $this->_session->setRmaErrorKeys($errorKeys); - } - if (!empty($errors)) { - foreach ($errors as $message) { - $this->messageManager->addError($message); - } + if (!empty($this->errorKeys)) { + $this->_session->setRmaErrorKeys($this->errorKeys); } - return false; } - $this->setItems($itemModels); + + if (empty($itemModels) || !empty($this->messageManager->getMessages()->getErrors())) { + throw new \LogicException('Faulty RMA items'); + } return $this->getItems(); } @@ -1527,4 +1267,221 @@ public function validateOrderItems(): void } } } + + /** + * @inheritdoc + */ + protected function _getValidationRulesBeforeSave() + { + return $this->validator; + } + + /** + * Adds items to existing list + * + * @param array $itemModels + * @param array $newModels + * @return array + */ + private function mergeModels(array $itemModels, array $newModels): array + { + return array_merge($itemModels, $newModels); + } + + /** + * Set item quantities to proper type + * + * @param array $itemDetails + * @return array + */ + private function preparePostItemQuantities(array $itemDetails): array + { + $quantityKeys = [self::QTY_AUTHORIZED, self::QTY_APPROVED, self::QTY_RETURNED]; + + foreach ($itemDetails as $key => $val) { + if (in_array($key, $quantityKeys)) { + if (is_numeric($val)) { + $preparePost[$key] = (double)$val; + } else { + $preparePost[$key] = ''; + } + } + } + + if ($itemDetails['is_qty_decimal']) { + $itemDetails['qty_requested'] = (double)$itemDetails['qty_requested']; + } else { + $itemDetails['qty_requested'] = (int)$itemDetails['qty_requested']; + + foreach ($quantityKeys as $key) { + if (!empty($preparePost[$key])) { + $preparePost[$key] = (int)$preparePost[$key]; + } + } + } + + return $itemDetails; + } + + /** + * Adds real product information to item details + * + * @param array $itemDetails + * @return array + */ + private function preparePostItemProductDetails(array $itemDetails): array + { + $realItem = $this->getOrder()->getItemById($itemDetails['order_item_id']); + + $itemDetails['product_name'] = $realItem->getName(); + $itemDetails['product_sku'] = $realItem->getSku(); + $itemDetails['product_admin_name'] = $this->_rmaData->getAdminProductName($realItem); + $itemDetails['product_admin_sku'] = $this->_rmaData->getAdminProductSku($realItem); + $itemDetails['product_options'] = $this->serializer->serialize($realItem->getProductOptions()); + $itemDetails['is_qty_decimal'] = $realItem->getIsQtyDecimal(); + + return $itemDetails; + } + + /** + * Add messages to existing message hub + * + * @param array $errors + */ + private function addErrors(array $errors): void + { + foreach ($errors as $message) { + $this->messageManager->addError($message); + } + } + + /** + * Normalize item post status + * + * @param array $itemDetails + * @return array + */ + private function preparePostItemStatus(array $itemDetails): array + { + $stat = Status::STATE_PENDING; + if (!empty($itemDetails['status'])) { + $status = $this->_attrSourceFactory->create(); + if ($status->checkStatus($itemDetails['status'])) { + $stat = $itemDetails['status']; + } + } + + $itemDetails['status'] = $stat; + + return $itemDetails; + } + + /** + * Generate model with provided item details + * + * @param array $item + * @param string $key + * @return array + */ + private function createItemModel(array $item, string $key): array + { + if (isset($item['items'])) { + $itemModel = $this->createSubItems($item['items'], $key); + } else { + $itemModel = $this->hydrateItemEntity($item, $key); + //check if entity_id present, and it's a split return. Reset the entity + if ($itemModel->getEntityId() && + !is_numeric($itemModel->getEntityId())) { + $itemModel->setEntityId(null); + } + + if ($this->isStatusNeedsAuthEmail($itemModel->getStatus()) + && $itemModel->getOrigData('status') !== $itemModel->getStatus() + ) { + $this->setIsSendAuthEmail(1); + } + + $this->addErrors($itemModel->getErrors()); + if ($itemModel->getErrors()) { + $this->errorKeys['tabs'] = 'items_section'; + } + $itemModel = [$itemModel]; + } + + return $itemModel; + } + + /** + * Adds data to existing entity, if available + * + * @param array $itemDetails + * @param string $key + * @return Item + */ + private function hydrateItemEntity(array $itemDetails, string $key): Item + { + $itemModel = $this->_rmaItemFactory->create(); + if (isset($itemDetails['entity_id']) && $itemDetails['entity_id']) { + $itemModel->load($itemDetails['entity_id']); + if ($itemModel->getEntityId()) { + if (empty($itemDetails['reason'])) { + $itemDetails['reason'] = $itemModel->getReason(); + } + + if (empty($itemDetails['reason_other'])) { + $itemDetails['reason_other'] = + $itemModel->getReasonOther() === null ? '' : $itemModel->getReasonOther(); + } + + if (empty($itemDetails['condition'])) { + $itemDetails['condition'] = $itemModel->getCondition(); + } + + if (empty($itemDetails['qty_requested'])) { + $itemDetails['qty_requested'] = $itemModel->getQtyRequested(); + } + } + } + + $itemPost = $this->_preparePost($itemDetails); + $itemModel->setData($itemPost)->prepareAttributes($itemPost, $key); + + return $itemModel; + } + + /** + * Creates sub-items for returned product + * + * @param array $item + * @param string $key + * @return array + */ + private function createSubItems(array $item, string $key): ?array + { + if (empty($item['items'])) { + return []; + } + + $items = []; + foreach ($item['items'] as $id => $qty) { + $itemModel = $this->_rmaItemFactory->create(); + $subItem = $item; + unset($subItem['items']); + $subItem['order_item_id'] = $id; + $subItem['qty_requested'] = $qty; + + $itemPost = $this->_preparePost($subItem); + + $files = $itemModel->setData($itemPost)->prepareAttributes($itemPost, $key); + if (!empty($files)) { + foreach ($files as $code) { + $itemModel->setData($code, $itemModel->getData($code)); + } + } + $this->addErrors($itemModel->getErrors()); + $items[] = $itemModel; + } + + return $items; + } } diff --git a/vendor/magento/module-rma/Model/Rma/Source/Status.php b/vendor/magento/module-rma/Model/Rma/Source/Status.php index bce8181801b1..ea1f9356f0bf 100644 --- a/vendor/magento/module-rma/Model/Rma/Source/Status.php +++ b/vendor/magento/module-rma/Model/Rma/Source/Status.php @@ -13,29 +13,36 @@ class Status extends \Magento\Rma\Model\Rma\Source\AbstractSource /** * Status constants */ - const STATE_PENDING = 'pending'; + public const STATE_PENDING = 'pending'; - const STATE_AUTHORIZED = 'authorized'; + public const STATE_AUTHORIZED = 'authorized'; - const STATE_PARTIAL_AUTHORIZED = 'partially_authorized'; + public const STATE_PARTIAL_AUTHORIZED = 'partially_authorized'; - const STATE_RECEIVED = 'received'; + public const STATE_RECEIVED = 'received'; - const STATE_RECEIVED_ON_ITEM = 'received_on_item'; + public const STATE_RECEIVED_ON_ITEM = 'received_on_item'; - const STATE_APPROVED = 'approved'; + public const STATE_APPROVED = 'approved'; - const STATE_APPROVED_ON_ITEM = 'approved_on_item'; + public const STATE_APPROVED_ON_ITEM = 'approved_on_item'; - const STATE_REJECTED = 'rejected'; + public const STATE_REJECTED = 'rejected'; - const STATE_REJECTED_ON_ITEM = 'rejected_on_item'; + public const STATE_REJECTED_ON_ITEM = 'rejected_on_item'; - const STATE_DENIED = 'denied'; + public const STATE_DENIED = 'denied'; - const STATE_CLOSED = 'closed'; + public const STATE_CLOSED = 'closed'; - const STATE_PROCESSED_CLOSED = 'processed_closed'; + public const STATE_PROCESSED_CLOSED = 'processed_closed'; + + public const STATE_ALL = [ + self::STATE_PENDING, self::STATE_AUTHORIZED, self::STATE_PARTIAL_AUTHORIZED, self::STATE_RECEIVED, + self::STATE_RECEIVED_ON_ITEM, self::STATE_APPROVED, self::STATE_APPROVED_ON_ITEM, + self::STATE_REJECTED, self::STATE_REJECTED_ON_ITEM, self::STATE_DENIED, self::STATE_CLOSED, + self::STATE_PROCESSED_CLOSED + ]; /** * Rma item attribute status factory diff --git a/vendor/magento/module-rma/etc/di.xml b/vendor/magento/module-rma/etc/di.xml index 2493a21b4bac..d22cd85aba70 100644 --- a/vendor/magento/module-rma/etc/di.xml +++ b/vendor/magento/module-rma/etc/di.xml @@ -36,7 +36,7 @@ - Magento\Framework\Translate + Magento\Rma\Model\ItemCountValidator diff --git a/vendor/magento/module-rma/i18n/en_US.csv b/vendor/magento/module-rma/i18n/en_US.csv index d8856f42cc64..c22a1e7b7f50 100644 --- a/vendor/magento/module-rma/i18n/en_US.csv +++ b/vendor/magento/module-rma/i18n/en_US.csv @@ -353,3 +353,4 @@ Comma-separated,Comma-separated "RMA Comment Email Recipient","RMA Comment Email Recipient" "Enabled for RMA","Enabled for RMA" "The label cannot be created for '%1' because the product does not exist in the system.", "The label cannot be created for '%1' because the product does not exist in the system." +"Invalid status provided: %s","Invalid status provided: %s"