diff --git a/app/code/Magento/Sales/Api/Data/CommentInterface.php b/app/code/Magento/Sales/Api/Data/CommentInterface.php
index d7021dc9f9546..fcab786319340 100644
--- a/app/code/Magento/Sales/Api/Data/CommentInterface.php
+++ b/app/code/Magento/Sales/Api/Data/CommentInterface.php
@@ -12,6 +12,16 @@
*/
interface CommentInterface
{
+ /*
+ * Is-visible-on-storefront flag.
+ */
+ const IS_VISIBLE_ON_FRONT = 'is_visible_on_front';
+
+ /*
+ * Comment.
+ */
+ const COMMENT = 'comment';
+
/**
* Gets the comment for the invoice.
*
diff --git a/app/code/Magento/Sales/Api/Data/EntityInterface.php b/app/code/Magento/Sales/Api/Data/EntityInterface.php
new file mode 100644
index 0000000000000..d09b25920f899
--- /dev/null
+++ b/app/code/Magento/Sales/Api/Data/EntityInterface.php
@@ -0,0 +1,53 @@
+orderRepository = $orderRepository;
$this->invoiceDocumentFactory = $invoiceDocumentFactory;
$this->invoiceValidator = $invoiceValidator;
+ $this->orderValidator = $orderValidator;
$this->paymentAdapter = $paymentAdapter;
$this->orderStateResolver = $orderStateResolver;
$this->config = $config;
@@ -147,7 +158,16 @@ public function execute(
($appendComment && $notify),
$arguments
);
- $errorMessages = $this->invoiceValidator->validate($invoice, $order);
+ $errorMessages = array_merge(
+ $this->invoiceValidator->validate(
+ $invoice,
+ [InvoiceQuantityValidator::class]
+ ),
+ $this->orderValidator->validate(
+ $order,
+ [CanInvoice::class]
+ )
+ );
if (!empty($errorMessages)) {
throw new \Magento\Sales\Exception\DocumentValidationException(
__("Invoice Document Validation Error(s):\n" . implode("\n", $errorMessages))
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/CommentCreation.php b/app/code/Magento/Sales/Model/Order/Invoice/CommentCreation.php
new file mode 100644
index 0000000000000..fa53f72ebcafc
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Invoice/CommentCreation.php
@@ -0,0 +1,98 @@
+comment;
+ }
+
+ /**
+ * Sets the comment for the invoice.
+ *
+ * @param string $comment
+ * @return $this
+ */
+ public function setComment($comment)
+ {
+ $this->comment = $comment;
+ return $this;
+ }
+
+ /**
+ * Gets the is-visible-on-storefront flag value for the invoice.
+ *
+ * @return int Is-visible-on-storefront flag value.
+ */
+ public function getIsVisibleOnFront()
+ {
+ return $this->isVisibleOnFront;
+ }
+
+ /**
+ * Sets the is-visible-on-storefront flag value for the invoice.
+ *
+ * @param int $isVisibleOnFront
+ * @return $this
+ */
+ public function setIsVisibleOnFront($isVisibleOnFront)
+ {
+ $this->isVisibleOnFront = $isVisibleOnFront;
+ return $this;
+ }
+
+ /**
+ * Retrieve existing extension attributes object or create a new one.
+ *
+ * @return \Magento\Sales\Api\Data\InvoiceCommentCreationExtensionInterface|null
+ */
+ public function getExtensionAttributes()
+ {
+ return $this->extensionAttributes;
+ }
+
+ /**
+ * Set an extension attributes object.
+ *
+ * @param \Magento\Sales\Api\Data\InvoiceCommentCreationExtensionInterface $extensionAttributes
+ * @return $this
+ */
+ public function setExtensionAttributes(
+ \Magento\Sales\Api\Data\InvoiceCommentCreationExtensionInterface $extensionAttributes
+ ) {
+ $this->extensionAttributes = $extensionAttributes;
+ return $this;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidator.php b/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidator.php
new file mode 100644
index 0000000000000..cbb68edaa8a55
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidator.php
@@ -0,0 +1,36 @@
+validator = $validator;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validate(InvoiceInterface $entity, array $validators)
+ {
+ return $this->validator->validate($entity, $validators);
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidatorInterface.php
new file mode 100644
index 0000000000000..568019a40fce5
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Invoice/InvoiceValidatorInterface.php
@@ -0,0 +1,24 @@
+qty = $qty;
}
+
+ /**
+ * Retrieve existing extension attributes object or create a new one.
+ *
+ * @return \Magento\Sales\Api\Data\InvoiceItemCreationExtensionInterface|null
+ */
+ public function getExtensionAttributes()
+ {
+ return $this->extensionAttributes;
+ }
+
+ /**
+ * Set an extension attributes object.
+ *
+ * @param \Magento\Sales\Api\Data\InvoiceItemCreationExtensionInterface $extensionAttributes
+ * @return $this
+ */
+ public function setExtensionAttributes(
+ \Magento\Sales\Api\Data\InvoiceItemCreationExtensionInterface $extensionAttributes
+ ) {
+ $this->extensionAttributes = $extensionAttributes;
+ return $this;
+ }
}
diff --git a/app/code/Magento/Sales/Model/Order/InvoiceValidator.php b/app/code/Magento/Sales/Model/Order/InvoiceQuantityValidator.php
similarity index 73%
rename from app/code/Magento/Sales/Model/Order/InvoiceValidator.php
rename to app/code/Magento/Sales/Model/Order/InvoiceQuantityValidator.php
index 35222599fc69e..9ae81dacb0a17 100644
--- a/app/code/Magento/Sales/Model/Order/InvoiceValidator.php
+++ b/app/code/Magento/Sales/Model/Order/InvoiceQuantityValidator.php
@@ -9,42 +9,38 @@
use Magento\Sales\Api\Data\InvoiceInterface;
use Magento\Sales\Api\Data\InvoiceItemInterface;
use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Api\OrderRepositoryInterface;
+use Magento\Sales\Model\ValidatorInterface;
/**
* Interface InvoiceValidatorInterface
*/
-class InvoiceValidator implements InvoiceValidatorInterface
+class InvoiceQuantityValidator implements ValidatorInterface
{
/**
- * @var OrderValidatorInterface
+ * @var OrderRepositoryInterface
*/
- private $orderValidator;
+ private $orderRepository;
/**
* InvoiceValidator constructor.
- * @param OrderValidatorInterface $orderValidator
+ * @param OrderRepositoryInterface $orderRepository
*/
- public function __construct(OrderValidatorInterface $orderValidator)
+ public function __construct(OrderRepositoryInterface $orderRepository)
{
- $this->orderValidator = $orderValidator;
+ $this->orderRepository = $orderRepository;
}
/**
- * @param InvoiceInterface $invoice
- * @param OrderInterface $order
- * @return array
+ * @inheritdoc
*/
- public function validate(InvoiceInterface $invoice, OrderInterface $order)
+ public function validate($invoice)
{
- $messages = $this->checkQtyAvailability($invoice, $order);
-
- if (!$this->orderValidator->canInvoice($order)) {
- $messages[] = __(
- 'An invoice cannot be created when an order has a status of %1.',
- $order->getStatus()
- );
+ if ($invoice->getOrderId() === null) {
+ return [__('Order Id is required for invoice document')];
}
- return $messages;
+ $order = $this->orderRepository->get($invoice->getOrderId());
+ return $this->checkQtyAvailability($invoice, $order);
}
/**
diff --git a/app/code/Magento/Sales/Model/Order/InvoiceValidatorInterface.php b/app/code/Magento/Sales/Model/Order/InvoiceValidatorInterface.php
deleted file mode 100644
index 64b2f98dfe37e..0000000000000
--- a/app/code/Magento/Sales/Model/Order/InvoiceValidatorInterface.php
+++ /dev/null
@@ -1,25 +0,0 @@
-validator = $validator;
+ }
+
+ /**
+ * @inheritdoc
*/
- public function canInvoice(OrderInterface $order)
+ public function validate(OrderInterface $entity, array $validators)
{
- if ($order->getState() === Order::STATE_PAYMENT_REVIEW ||
- $order->getState() === Order::STATE_HOLDED ||
- $order->getState() === Order::STATE_CANCELED ||
- $order->getState() === Order::STATE_COMPLETE ||
- $order->getState() === Order::STATE_CLOSED
- ) {
- return false;
- };
- /** @var \Magento\Sales\Model\Order\Item $item */
- foreach ($order->getItems() as $item) {
- if ($item->getQtyToInvoice() > 0 && !$item->getLockedDoInvoice()) {
- return true;
- }
- }
- return false;
+ return $this->validator->validate($entity, $validators);
}
}
diff --git a/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php b/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php
index d0dcc38af642a..c5a9a6c1d3296 100644
--- a/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php
+++ b/app/code/Magento/Sales/Model/Order/OrderValidatorInterface.php
@@ -3,21 +3,22 @@
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\Sales\Model\Order;
use Magento\Sales\Api\Data\OrderInterface;
+use Magento\Sales\Exception\DocumentValidationException;
+use Magento\Sales\Model\ValidatorInterface;
/**
* Interface OrderValidatorInterface
- *
- * @api
*/
interface OrderValidatorInterface
{
/**
- * @param OrderInterface $order
- * @return bool
+ * @param OrderInterface $entity
+ * @param ValidatorInterface[] $validators
+ * @return string[]
+ * @throws DocumentValidationException
*/
- public function canInvoice(OrderInterface $order);
+ public function validate(OrderInterface $entity, array $validators);
}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment.php b/app/code/Magento/Sales/Model/Order/Shipment.php
index 6647bae750fff..2277f92d6e0e8 100644
--- a/app/code/Magento/Sales/Model/Order/Shipment.php
+++ b/app/code/Magento/Sales/Model/Order/Shipment.php
@@ -405,7 +405,7 @@ public function addTrack(\Magento\Sales\Model\Order\Shipment\Track $track)
* Adds comment to shipment with additional possibility to send it to customer via email
* and show it in customer account
*
- * @param \Magento\Sales\Model\Order\Shipment\Comment $comment
+ * @param \Magento\Sales\Model\Order\Shipment\Comment|string $comment
* @param bool $notify
* @param bool $visibleOnFront
* @return $this
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/CommentCreation.php b/app/code/Magento/Sales/Model/Order/Shipment/CommentCreation.php
new file mode 100644
index 0000000000000..19d06fb0eff32
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/CommentCreation.php
@@ -0,0 +1,96 @@
+extensionAttributes;
+ }
+
+ /**
+ * Set an extension attributes object.
+ *
+ * @param \Magento\Sales\Api\Data\ShipmentCommentCreationExtensionInterface $extensionAttributes
+ * @return $this
+ */
+ public function setExtensionAttributes(
+ \Magento\Sales\Api\Data\ShipmentCommentCreationExtensionInterface $extensionAttributes
+ ) {
+ $this->extensionAttributes = $extensionAttributes;
+ return $this;
+ }
+
+ /**
+ * Gets the comment for the invoice.
+ *
+ * @return string Comment.
+ */
+ public function getComment()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * Sets the comment for the invoice.
+ *
+ * @param string $comment
+ * @return $this
+ */
+ public function setComment($comment)
+ {
+ $this->comment = $comment;
+ return $this;
+ }
+
+ /**
+ * Gets the is-visible-on-storefront flag value for the invoice.
+ *
+ * @return int Is-visible-on-storefront flag value.
+ */
+ public function getIsVisibleOnFront()
+ {
+ return $this->isVisibleOnFront;
+ }
+
+ /**
+ * Sets the is-visible-on-storefront flag value for the invoice.
+ *
+ * @param int $isVisibleOnFront
+ * @return $this
+ */
+ public function setIsVisibleOnFront($isVisibleOnFront)
+ {
+ $this->isVisibleOnFront = $isVisibleOnFront;
+ return $this;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/CreationArguments.php b/app/code/Magento/Sales/Model/Order/Shipment/CreationArguments.php
new file mode 100644
index 0000000000000..8a43a73553e79
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/CreationArguments.php
@@ -0,0 +1,37 @@
+extensionAttributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setExtensionAttributes(
+ \Magento\Sales\Api\Data\ShipmentCreationArgumentsExtensionInterface $extensionAttributes
+ ) {
+ $this->extensionAttributes = $extensionAttributes;
+ return $this;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/Item.php b/app/code/Magento/Sales/Model/Order/Shipment/Item.php
index c7fdca853b17e..8627f76031b06 100644
--- a/app/code/Magento/Sales/Model/Order/Shipment/Item.php
+++ b/app/code/Magento/Sales/Model/Order/Shipment/Item.php
@@ -151,22 +151,7 @@ public function getOrderItem()
*/
public function setQty($qty)
{
- if ($this->getOrderItem()->getIsQtyDecimal()) {
- $qty = (double)$qty;
- } else {
- $qty = (int)$qty;
- }
- $qty = $qty > 0 ? $qty : 0;
- /**
- * Check qty availability
- */
- if ($qty <= $this->getOrderItem()->getQtyToShip() || $this->getOrderItem()->isDummy(true)) {
- $this->setData('qty', $qty);
- } else {
- throw new \Magento\Framework\Exception\LocalizedException(
- __('We found an invalid quantity to ship for item "%1".', $this->getName())
- );
- }
+ $this->setData('qty', $qty);
return $this;
}
@@ -174,6 +159,7 @@ public function setQty($qty)
* Applying qty to order item
*
* @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function register()
{
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/ItemCreation.php b/app/code/Magento/Sales/Model/Order/Shipment/ItemCreation.php
new file mode 100644
index 0000000000000..e3cb2f23d7cf3
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/ItemCreation.php
@@ -0,0 +1,87 @@
+orderItemId;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setOrderItemId($orderItemId)
+ {
+ $this->orderItemId = $orderItemId;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQty()
+ {
+ return $this->qty;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setQty($qty)
+ {
+ $this->qty = $qty;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return \Magento\Sales\Api\Data\ShipmentItemCreationExtensionInterface|null
+ */
+ public function getExtensionAttributes()
+ {
+ return $this->extensionAttributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param \Magento\Sales\Api\Data\ShipmentItemCreationExtensionInterface $extensionAttributes
+ * @return $this
+ */
+ public function setExtensionAttributes(
+ \Magento\Sales\Api\Data\ShipmentItemCreationExtensionInterface $extensionAttributes
+ ) {
+ $this->extensionAttributes = $extensionAttributes;
+ return $this;
+ }
+ //@codeCoverageIgnoreEnd
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/Notifier.php b/app/code/Magento/Sales/Model/Order/Shipment/Notifier.php
new file mode 100644
index 0000000000000..21dd5ad4a58f6
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/Notifier.php
@@ -0,0 +1,41 @@
+senders = $senders;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function notify(
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Magento\Sales\Api\Data\ShipmentInterface $shipment,
+ \Magento\Sales\Api\Data\ShipmentCommentCreationInterface $comment = null,
+ $forceSyncMode = false
+ ) {
+ foreach ($this->senders as $sender) {
+ $sender->send($order, $shipment, $comment, $forceSyncMode);
+ }
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/NotifierInterface.php b/app/code/Magento/Sales/Model/Order/Shipment/NotifierInterface.php
new file mode 100644
index 0000000000000..f34eb6178d094
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/NotifierInterface.php
@@ -0,0 +1,31 @@
+getItems() as $item) {
+ if ($item->getQty() > 0) {
+ $item->register();
+ }
+ }
+ return $order;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/OrderRegistrarInterface.php b/app/code/Magento/Sales/Model/Order/Shipment/OrderRegistrarInterface.php
new file mode 100644
index 0000000000000..7d54acece3599
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/OrderRegistrarInterface.php
@@ -0,0 +1,26 @@
+extensionAttributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setExtensionAttributes(
+ \Magento\Sales\Api\Data\ShipmentPackageExtensionInterface $extensionAttributes
+ ) {
+ $this->extensionAttributes = $extensionAttributes;
+ return $this;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/PackageCreation.php b/app/code/Magento/Sales/Model/Order/Shipment/PackageCreation.php
new file mode 100644
index 0000000000000..50ad944b8251c
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/PackageCreation.php
@@ -0,0 +1,36 @@
+extensionAttributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setExtensionAttributes(
+ \Magento\Sales\Api\Data\ShipmentPackageCreationExtensionInterface $extensionAttributes
+ ) {
+ $this->extensionAttributes = $extensionAttributes;
+ return $this;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php b/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php
new file mode 100644
index 0000000000000..228a45ff16aae
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php
@@ -0,0 +1,149 @@
+paymentHelper = $paymentHelper;
+ $this->shipmentResource = $shipmentResource;
+ $this->globalConfig = $globalConfig;
+ $this->eventManager = $eventManager;
+ }
+
+ /**
+ * Sends order shipment email to the customer.
+ *
+ * Email will be sent immediately in two cases:
+ *
+ * - if asynchronous email sending is disabled in global settings
+ * - if $forceSyncMode parameter is set to TRUE
+ *
+ * Otherwise, email will be sent later during running of
+ * corresponding cron job.
+ *
+ * @param \Magento\Sales\Api\Data\OrderInterface $order
+ * @param \Magento\Sales\Api\Data\ShipmentInterface $shipment
+ * @param \Magento\Sales\Api\Data\ShipmentCommentCreationInterface|null $comment
+ * @param bool $forceSyncMode
+ *
+ * @return bool
+ */
+ public function send(
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Magento\Sales\Api\Data\ShipmentInterface $shipment,
+ \Magento\Sales\Api\Data\ShipmentCommentCreationInterface $comment = null,
+ $forceSyncMode = false
+ ) {
+ $shipment->setSendEmail(true);
+
+ if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) {
+ $transport = [
+ 'order' => $order,
+ 'shipment' => $shipment,
+ 'comment' => $comment ? $comment->getComment() : '',
+ 'billing' => $order->getBillingAddress(),
+ 'payment_html' => $this->getPaymentHtml($order),
+ 'store' => $order->getStore(),
+ 'formattedShippingAddress' => $this->getFormattedShippingAddress($order),
+ 'formattedBillingAddress' => $this->getFormattedBillingAddress($order)
+ ];
+
+ $this->eventManager->dispatch(
+ 'email_shipment_set_template_vars_before',
+ ['sender' => $this, 'transport' => $transport]
+ );
+
+ $this->templateContainer->setTemplateVars($transport);
+
+ if ($this->checkAndSend($order)) {
+ $shipment->setEmailSent(true);
+
+ $this->shipmentResource->saveAttribute($shipment, ['send_email', 'email_sent']);
+
+ return true;
+ }
+ } else {
+ $shipment->setEmailSent(null);
+
+ $this->shipmentResource->saveAttribute($shipment, 'email_sent');
+ }
+
+ $this->shipmentResource->saveAttribute($shipment, 'send_email');
+
+ return false;
+ }
+
+ /**
+ * Returns payment info block as HTML.
+ *
+ * @param \Magento\Sales\Api\Data\OrderInterface $order
+ *
+ * @return string
+ */
+ private function getPaymentHtml(\Magento\Sales\Api\Data\OrderInterface $order)
+ {
+ return $this->paymentHelper->getInfoBlockHtml(
+ $order->getPayment(),
+ $this->identityContainer->getStore()->getStoreId()
+ );
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/SenderInterface.php b/app/code/Magento/Sales/Model/Order/Shipment/SenderInterface.php
new file mode 100644
index 0000000000000..a030038b7b139
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/SenderInterface.php
@@ -0,0 +1,29 @@
+validator = $validator;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validate(ShipmentInterface $entity, array $validators)
+ {
+ return $this->validator->validate($entity, $validators);
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/ShipmentValidatorInterface.php b/app/code/Magento/Sales/Model/Order/Shipment/ShipmentValidatorInterface.php
new file mode 100644
index 0000000000000..198a4019bf6b8
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/ShipmentValidatorInterface.php
@@ -0,0 +1,24 @@
+trackNumber;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setTrackNumber($trackNumber)
+ {
+ $this->trackNumber = $trackNumber;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCarrierCode()
+ {
+ return $this->carrierCode;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setCarrierCode($carrierCode)
+ {
+ $this->carrierCode = $carrierCode;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getExtensionAttributes()
+ {
+ return $this->extensionAttributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setExtensionAttributes(
+ \Magento\Sales\Api\Data\ShipmentTrackCreationExtensionInterface $extensionAttributes
+ ) {
+ $this->extensionAttributes = $extensionAttributes;
+ return $this;
+ }
+
+ //@codeCoverageIgnoreEnd
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/Validation/QuantityValidator.php b/app/code/Magento/Sales/Model/Order/Shipment/Validation/QuantityValidator.php
new file mode 100644
index 0000000000000..20e3712d889ed
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/Validation/QuantityValidator.php
@@ -0,0 +1,108 @@
+orderRepository = $orderRepository;
+ }
+
+ /**
+ * @param ShipmentInterface $entity
+ * @return array
+ * @throws DocumentValidationException
+ * @throws NoSuchEntityException
+ */
+ public function validate($entity)
+ {
+ if ($entity->getOrderId() === null) {
+ return [__('Order Id is required for shipment document')];
+ }
+
+ if (empty($entity->getItems())) {
+ return [__('You can\'t create a shipment without products.')];
+ }
+ $messages = [];
+
+ $order = $this->orderRepository->get($entity->getOrderId());
+ $orderItemsById = $this->getOrderItems($order);
+
+ $totalQuantity = 0;
+ foreach ($entity->getItems() as $item) {
+ if (!isset($orderItemsById[$item->getOrderItemId()])) {
+ $messages[] = __(
+ 'The shipment contains product SKU "%1" that is not part of the original order.',
+ $item->getSku()
+ );
+ continue;
+ }
+ $orderItem = $orderItemsById[$item->getOrderItemId()];
+
+ if (!$this->isQtyAvailable($orderItem, $item->getQty())) {
+ $messages[] =__(
+ 'The quantity to ship must not be greater than the unshipped quantity'
+ . ' for product SKU "%1".',
+ $orderItem->getSku()
+ );
+ } else {
+ $totalQuantity += $item->getQty();
+ }
+ }
+ if ($totalQuantity <= 0) {
+ $messages[] = __('You can\'t create a shipment without products.');
+ }
+
+ return $messages;
+ }
+
+ /**
+ * @param OrderInterface $order
+ * @return OrderItemInterface[]
+ */
+ private function getOrderItems(OrderInterface $order)
+ {
+ $orderItemsById = [];
+ foreach ($order->getItems() as $item) {
+ $orderItemsById[$item->getItemId()] = $item;
+ }
+
+ return $orderItemsById;
+ }
+
+ /**
+ * @param Item $orderItem
+ * @param int $qty
+ * @return bool
+ */
+ private function isQtyAvailable(Item $orderItem, $qty)
+ {
+ return $qty <= $orderItem->getQtyToShip() || $orderItem->isDummy(true);
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Shipment/Validation/TrackValidator.php b/app/code/Magento/Sales/Model/Order/Shipment/Validation/TrackValidator.php
new file mode 100644
index 0000000000000..55970d37c597d
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Shipment/Validation/TrackValidator.php
@@ -0,0 +1,33 @@
+getTracks()) {
+ return $messages;
+ }
+ foreach ($entity->getTracks() as $track) {
+ if (!$track->getTrackNumber()) {
+ $messages[] = __('Please enter a tracking number.');
+ }
+ }
+ return $messages;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php b/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php
new file mode 100644
index 0000000000000..d10f84d815543
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php
@@ -0,0 +1,128 @@
+shipmentFactory = $shipmentFactory;
+ $this->trackFactory = $trackFactory;
+ $this->hydratorPool = $hydratorPool;
+ }
+
+ /**
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ *
+ * @param OrderInterface $order
+ * @param ShipmentItemCreationInterface[] $items
+ * @param ShipmentTrackCreationInterface[] $tracks
+ * @param ShipmentCommentCreationInterface|null $comment
+ * @param bool $appendComment
+ * @param ShipmentPackageCreationInterface[] $packages
+ * @param ShipmentCreationArgumentsInterface|null $arguments
+ * @return ShipmentInterface
+ */
+ public function create(
+ OrderInterface $order,
+ array $items = [],
+ array $tracks = [],
+ ShipmentCommentCreationInterface $comment = null,
+ $appendComment = false,
+ array $packages = [],
+ ShipmentCreationArgumentsInterface $arguments = null
+ ) {
+ $shipmentItems = $this->itemsToArray($items);
+ /** @var Shipment $shipment */
+ $shipment = $this->shipmentFactory->create(
+ $order,
+ $shipmentItems
+ );
+ $this->prepareTracks($shipment, $tracks);
+ if ($comment) {
+ $shipment->addComment(
+ $comment->getComment(),
+ $appendComment,
+ $comment->getIsVisibleOnFront()
+ );
+ }
+
+ return $shipment;
+ }
+
+ /**
+ * Adds tracks to the shipment.
+ *
+ * @param ShipmentInterface $shipment
+ * @param ShipmentTrackCreationInterface[] $tracks
+ * @return ShipmentInterface
+ */
+ private function prepareTracks(\Magento\Sales\Api\Data\ShipmentInterface $shipment, array $tracks)
+ {
+ foreach ($tracks as $track) {
+ $hydrator = $this->hydratorPool->getHydrator(
+ \Magento\Sales\Api\Data\ShipmentTrackCreationInterface::class
+ );
+ $shipment->addTrack($this->trackFactory->create(['data' => $hydrator->extract($track)]));
+ }
+ return $shipment;
+ }
+
+ /**
+ * Convert items to array
+ *
+ * @param ShipmentItemCreationInterface[] $items
+ * @return array
+ */
+ private function itemsToArray(array $items = [])
+ {
+ $shipmentItems = [];
+ foreach ($items as $item) {
+ $shipmentItems[$item->getOrderItemId()] = $item->getQty();
+ }
+ return $shipmentItems;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/ShipmentFactory.php b/app/code/Magento/Sales/Model/Order/ShipmentFactory.php
index 2ac012760ee47..a8839c7537587 100644
--- a/app/code/Magento/Sales/Model/Order/ShipmentFactory.php
+++ b/app/code/Magento/Sales/Model/Order/ShipmentFactory.php
@@ -5,6 +5,10 @@
*/
namespace Magento\Sales\Model\Order;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface;
+
/**
* Factory class for @see \Magento\Sales\Api\Data\ShipmentInterface
*/
@@ -72,6 +76,8 @@ public function create(\Magento\Sales\Model\Order $order, array $items = [], $tr
* @param \Magento\Sales\Model\Order $order
* @param array $items
* @return \Magento\Sales\Api\Data\ShipmentInterface
+ * @throws LocalizedException
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function prepareItems(
\Magento\Sales\Api\Data\ShipmentInterface $shipment,
@@ -79,7 +85,6 @@ protected function prepareItems(
array $items = []
) {
$totalQty = 0;
-
foreach ($order->getAllItems() as $orderItem) {
if (!$this->canShipItem($orderItem, $items)) {
continue;
@@ -103,7 +108,7 @@ protected function prepareItems(
$qty = $bundleSelectionAttributes['qty'] * $items[$orderItem->getParentItemId()];
$qty = min($qty, $orderItem->getSimpleQtyToShip());
- $item->setQty($qty);
+ $item->setQty($this->castQty($orderItem, $qty));
$shipment->addItem($item);
continue;
@@ -126,10 +131,9 @@ protected function prepareItems(
$totalQty += $qty;
- $item->setQty($qty);
+ $item->setQty($this->castQty($orderItem, $qty));
$shipment->addItem($item);
}
-
return $shipment->setTotalQty($totalQty);
}
@@ -211,4 +215,20 @@ protected function canShipItem($item, array $items = [])
return $item->getQtyToShip() > 0;
}
}
+
+ /**
+ * @param Item $item
+ * @param string|int|float $qty
+ * @return float|int
+ */
+ private function castQty(\Magento\Sales\Model\Order\Item $item, $qty)
+ {
+ if ($item->getIsQtyDecimal()) {
+ $qty = (double)$qty;
+ } else {
+ $qty = (int)$qty;
+ }
+
+ return $qty > 0 ? $qty : 0;
+ }
}
diff --git a/app/code/Magento/Sales/Model/Order/Validation/CanInvoice.php b/app/code/Magento/Sales/Model/Order/Validation/CanInvoice.php
new file mode 100644
index 0000000000000..bb14dc1bb5180
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Validation/CanInvoice.php
@@ -0,0 +1,66 @@
+isStateReadyForInvoice($entity)) {
+ $messages[] = __('An invoice cannot be created when an order has a status of %1', $entity->getStatus());
+ } elseif (!$this->canInvoice($entity)) {
+ $messages[] = __('The order does not allow an invoice to be created.');
+ }
+
+ return $messages;
+ }
+
+ /**
+ * @param OrderInterface $order
+ * @return bool
+ */
+ private function isStateReadyForInvoice(OrderInterface $order)
+ {
+ if ($order->getState() === Order::STATE_PAYMENT_REVIEW ||
+ $order->getState() === Order::STATE_HOLDED ||
+ $order->getState() === Order::STATE_CANCELED ||
+ $order->getState() === Order::STATE_COMPLETE ||
+ $order->getState() === Order::STATE_CLOSED
+ ) {
+ return false;
+ };
+
+ return true;
+ }
+
+ /**
+ * @param OrderInterface $order
+ * @return bool
+ */
+ private function canInvoice(OrderInterface $order)
+ {
+ /** @var \Magento\Sales\Model\Order\Item $item */
+ foreach ($order->getItems() as $item) {
+ if ($item->getQtyToInvoice() > 0 && !$item->getLockedDoInvoice()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Order/Validation/CanShip.php b/app/code/Magento/Sales/Model/Order/Validation/CanShip.php
new file mode 100644
index 0000000000000..46638a62483e6
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Order/Validation/CanShip.php
@@ -0,0 +1,65 @@
+isStateReadyForShipment($entity)) {
+ $messages[] = __('A shipment cannot be created when an order has a status of %1', $entity->getStatus());
+ } elseif (!$this->canShip($entity)) {
+ $messages[] = __('The order does not allow a shipment to be created.');
+ }
+
+ return $messages;
+ }
+
+ /**
+ * @param OrderInterface $order
+ * @return bool
+ */
+ private function isStateReadyForShipment(OrderInterface $order)
+ {
+ if ($order->getState() === Order::STATE_PAYMENT_REVIEW ||
+ $order->getState() === Order::STATE_HOLDED ||
+ $order->getIsVirtual() ||
+ $order->getState() === Order::STATE_CANCELED
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param OrderInterface $order
+ * @return bool
+ */
+ private function canShip(OrderInterface $order)
+ {
+ /** @var \Magento\Sales\Model\Order\Item $item */
+ foreach ($order->getItems() as $item) {
+ if ($item->getQtyToShip() > 0 && !$item->getIsVirtual() && !$item->getLockedDoShip()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/ShipOrder.php b/app/code/Magento/Sales/Model/ShipOrder.php
new file mode 100644
index 0000000000000..d051144cf73ca
--- /dev/null
+++ b/app/code/Magento/Sales/Model/ShipOrder.php
@@ -0,0 +1,206 @@
+resourceConnection = $resourceConnection;
+ $this->orderRepository = $orderRepository;
+ $this->shipmentDocumentFactory = $shipmentDocumentFactory;
+ $this->shipmentValidator = $shipmentValidator;
+ $this->orderValidator = $orderValidator;
+ $this->orderStateResolver = $orderStateResolver;
+ $this->config = $config;
+ $this->shipmentRepository = $shipmentRepository;
+ $this->notifierInterface = $notifierInterface;
+ $this->logger = $logger;
+ $this->orderRegistrar = $orderRegistrar;
+ }
+
+ /**
+ * @param int $orderId
+ * @param \Magento\Sales\Api\Data\ShipmentItemCreationInterface[] $items
+ * @param bool $notify
+ * @param bool $appendComment
+ * @param \Magento\Sales\Api\Data\ShipmentCommentCreationInterface|null $comment
+ * @param \Magento\Sales\Api\Data\ShipmentTrackCreationInterface[] $tracks
+ * @param \Magento\Sales\Api\Data\ShipmentPackageCreationInterface[] $packages
+ * @param \Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface|null $arguments
+ * @return int
+ * @throws \Magento\Sales\Api\Exception\DocumentValidationExceptionInterface
+ * @throws \Magento\Sales\Api\Exception\CouldNotShipExceptionInterface
+ * @throws \Magento\Framework\Exception\InputException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @throws \DomainException
+ */
+ public function execute(
+ $orderId,
+ array $items = [],
+ $notify = false,
+ $appendComment = false,
+ \Magento\Sales\Api\Data\ShipmentCommentCreationInterface $comment = null,
+ array $tracks = [],
+ array $packages = [],
+ \Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface $arguments = null
+ ) {
+ $connection = $this->resourceConnection->getConnection('sales');
+ $order = $this->orderRepository->get($orderId);
+ $shipment = $this->shipmentDocumentFactory->create(
+ $order,
+ $items,
+ $tracks,
+ $comment,
+ ($appendComment && $notify),
+ $packages,
+ $arguments
+ );
+ $orderValidationResult = $this->orderValidator->validate(
+ $order,
+ [
+ CanShip::class
+ ]
+ );
+ $shipmentValidationResult = $this->shipmentValidator->validate(
+ $shipment,
+ [
+ QuantityValidator::class,
+ TrackValidator::class
+ ]
+ );
+ $validationMessages = array_merge($orderValidationResult, $shipmentValidationResult);
+ if (!empty($validationMessages)) {
+ throw new \Magento\Sales\Exception\DocumentValidationException(
+ __("Shipment Document Validation Error(s):\n" . implode("\n", $validationMessages))
+ );
+ }
+ $connection->beginTransaction();
+ try {
+ $this->orderRegistrar->register($order, $shipment);
+ $order->setState(
+ $this->orderStateResolver->getStateForOrder($order, [OrderStateResolverInterface::IN_PROGRESS])
+ );
+ $order->setStatus($this->config->getStateDefaultStatus($order->getState()));
+ $this->shipmentRepository->save($shipment);
+ $this->orderRepository->save($order);
+ $connection->commit();
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ $connection->rollBack();
+ throw new \Magento\Sales\Exception\CouldNotShipException(
+ __('Could not save a shipment, see error log for details')
+ );
+ }
+ if ($notify) {
+ if (!$appendComment) {
+ $comment = null;
+ }
+ $this->notifierInterface->notify($order, $shipment, $comment);
+ }
+ return $shipment->getEntityId();
+ }
+}
diff --git a/app/code/Magento/Sales/Model/Validator.php b/app/code/Magento/Sales/Model/Validator.php
new file mode 100644
index 0000000000000..b8d57ded29702
--- /dev/null
+++ b/app/code/Magento/Sales/Model/Validator.php
@@ -0,0 +1,56 @@
+objectManager = $objectManager;
+ }
+
+ /**
+ * @param object $entity
+ * @param ValidatorInterface[] $validators
+ * @return string[]
+ * @throws ConfigurationMismatchException
+ */
+ public function validate($entity, array $validators)
+ {
+ $messages = [];
+ foreach ($validators as $validatorName) {
+ $validator = $this->objectManager->get($validatorName);
+ if (!$validator instanceof ValidatorInterface) {
+ throw new ConfigurationMismatchException(
+ __(
+ sprintf('Validator %s is not instance of general validator interface', $validatorName)
+ )
+ );
+ }
+ $messages = array_merge($messages, $validator->validate($entity));
+ }
+
+ return $messages;
+ }
+}
diff --git a/app/code/Magento/Sales/Model/ValidatorInterface.php b/app/code/Magento/Sales/Model/ValidatorInterface.php
new file mode 100644
index 0000000000000..1882782e314f7
--- /dev/null
+++ b/app/code/Magento/Sales/Model/ValidatorInterface.php
@@ -0,0 +1,23 @@
+disableOriginalConstructor()
->getMock();
+ $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
$this->paymentAdapterMock = $this->getMockBuilder(PaymentAdapterInterface::class)
->disableOriginalConstructor()
->getMock();
@@ -172,11 +183,12 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
- $this->orderInvoice = new OrderInvoice(
+ $this->invoiceOrder = new InvoiceOrder(
$this->resourceConnectionMock,
$this->orderRepositoryMock,
$this->invoiceDocumentFactoryMock,
$this->invoiceValidatorMock,
+ $this->orderValidatorMock,
$this->paymentAdapterMock,
$this->orderStateResolverMock,
$this->configMock,
@@ -212,7 +224,11 @@ public function testOrderInvoice($orderId, $capture, $items, $notify, $appendCom
$this->invoiceValidatorMock->expects($this->once())
->method('validate')
- ->with($this->invoiceMock, $this->orderMock)
+ ->with($this->invoiceMock)
+ ->willReturn([]);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->orderMock)
->willReturn([]);
$this->paymentAdapterMock->expects($this->once())
@@ -271,7 +287,7 @@ public function testOrderInvoice($orderId, $capture, $items, $notify, $appendCom
$this->assertEquals(
2,
- $this->orderInvoice->execute(
+ $this->invoiceOrder->execute(
$orderId,
$capture,
$items,
@@ -311,10 +327,14 @@ public function testDocumentValidationException()
$this->invoiceValidatorMock->expects($this->once())
->method('validate')
- ->with($this->invoiceMock, $this->orderMock)
+ ->with($this->invoiceMock)
->willReturn($errorMessages);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->orderMock)
+ ->willReturn([]);
- $this->orderInvoice->execute(
+ $this->invoiceOrder->execute(
$orderId,
$capture,
$items,
@@ -356,7 +376,11 @@ public function testCouldNotInvoiceException()
$this->invoiceValidatorMock->expects($this->once())
->method('validate')
- ->with($this->invoiceMock, $this->orderMock)
+ ->with($this->invoiceMock)
+ ->willReturn([]);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->orderMock)
->willReturn([]);
$e = new \Exception();
@@ -372,7 +396,7 @@ public function testCouldNotInvoiceException()
$this->adapterInterface->expects($this->once())
->method('rollBack');
- $this->orderInvoice->execute(
+ $this->invoiceOrder->execute(
$orderId,
$capture,
$items,
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceQuantityValidatorTest.php
similarity index 66%
rename from app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceValidatorTest.php
rename to app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceQuantityValidatorTest.php
index 6fdfdb61b3635..8d800e12a6ff0 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceValidatorTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/InvoiceQuantityValidatorTest.php
@@ -6,15 +6,16 @@
namespace Magento\Sales\Test\Unit\Model\Order;
+use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order;
/**
* Test for \Magento\Sales\Model\Order\InvoiceValidator class
*/
-class InvoiceValidatorTest extends \PHPUnit_Framework_TestCase
+class InvoiceQuantityValidatorTest extends \PHPUnit_Framework_TestCase
{
/**
- * @var \Magento\Sales\Model\Order\InvoiceValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Sales\Model\Order\InvoiceQuantityValidator|\PHPUnit_Framework_MockObject_MockObject
*/
private $model;
@@ -24,14 +25,14 @@ class InvoiceValidatorTest extends \PHPUnit_Framework_TestCase
private $objectManager;
/**
- * @var \Magento\Sales\Model\Order\OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Sales\Api\Data\OrderInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- private $orderValidatorMock;
+ private $orderMock;
/**
- * @var \Magento\Sales\Api\Data\OrderInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Sales\Api\OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- private $orderMock;
+ private $orderRepositoryMock;
/**
* @var \Magento\Sales\Api\Data\InvoiceInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -42,24 +43,21 @@ protected function setUp()
{
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
- $this->orderValidatorMock = $this->getMockBuilder(\Magento\Sales\Model\Order\OrderValidatorInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['canInvoice'])
- ->getMockForAbstractClass();
-
$this->orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class)
->disableOriginalConstructor()
- ->setMethods(['getStatus'])
->getMockForAbstractClass();
$this->invoiceMock = $this->getMockBuilder(\Magento\Sales\Api\Data\InvoiceInterface::class)
->disableOriginalConstructor()
->setMethods(['getTotalQty', 'getItems'])
->getMockForAbstractClass();
-
+ $this->orderRepositoryMock = $this->getMockBuilder(
+ OrderRepositoryInterface::class
+ )->disableOriginalConstructor()->getMockForAbstractClass();
+ $this->orderRepositoryMock->expects($this->any())->method('get')->willReturn($this->orderMock);
$this->model = $this->objectManager->getObject(
- \Magento\Sales\Model\Order\InvoiceValidator::class,
- ['orderValidator' => $this->orderValidatorMock]
+ \Magento\Sales\Model\Order\InvoiceQuantityValidator::class,
+ ['orderRepository' => $this->orderRepositoryMock]
);
}
@@ -75,39 +73,12 @@ public function testValidate()
$this->orderMock->expects($this->once())
->method('getItems')
->willReturn([$orderItemMock]);
- $this->orderValidatorMock->expects($this->once())
- ->method('canInvoice')
- ->with($this->orderMock)
- ->willReturn(true);
- $this->assertEquals(
- $expectedResult,
- $this->model->validate($this->invoiceMock, $this->orderMock)
- );
- }
-
- public function testValidateCanNotInvoiceOrder()
- {
- $orderStatus = 'Test Status';
- $expectedResult = [__('An invoice cannot be created when an order has a status of %1.', $orderStatus)];
- $invoiceItemMock = $this->getInvoiceItemMock(1, 1);
- $this->invoiceMock->expects($this->once())
- ->method('getItems')
- ->willReturn([$invoiceItemMock]);
-
- $orderItemMock = $this->getOrderItemMock(1, 1, true);
- $this->orderMock->expects($this->once())
- ->method('getItems')
- ->willReturn([$orderItemMock]);
- $this->orderMock->expects($this->once())
- ->method('getStatus')
- ->willReturn($orderStatus);
- $this->orderValidatorMock->expects($this->once())
- ->method('canInvoice')
- ->with($this->orderMock)
- ->willReturn(false);
+ $this->invoiceMock->expects($this->exactly(2))
+ ->method('getOrderId')
+ ->willReturn(1);
$this->assertEquals(
$expectedResult,
- $this->model->validate($this->invoiceMock, $this->orderMock)
+ $this->model->validate($this->invoiceMock)
);
}
@@ -125,13 +96,12 @@ public function testValidateInvoiceQtyBiggerThanOrder()
$this->orderMock->expects($this->once())
->method('getItems')
->willReturn([$orderItemMock]);
- $this->orderValidatorMock->expects($this->once())
- ->method('canInvoice')
- ->with($this->orderMock)
- ->willReturn(true);
+ $this->invoiceMock->expects($this->exactly(2))
+ ->method('getOrderId')
+ ->willReturn(1);
$this->assertEquals(
$expectedResult,
- $this->model->validate($this->invoiceMock, $this->orderMock)
+ $this->model->validate($this->invoiceMock)
);
}
@@ -146,13 +116,21 @@ public function testValidateNoOrderItems()
$this->orderMock->expects($this->once())
->method('getItems')
->willReturn([]);
- $this->orderValidatorMock->expects($this->once())
- ->method('canInvoice')
- ->with($this->orderMock)
- ->willReturn(true);
+ $this->invoiceMock->expects($this->exactly(2))
+ ->method('getOrderId')
+ ->willReturn(1);
+ $this->assertEquals(
+ $expectedResult,
+ $this->model->validate($this->invoiceMock)
+ );
+ }
+
+ public function testValidateNoOrder()
+ {
+ $expectedResult = [__('Order Id is required for invoice document')];
$this->assertEquals(
$expectedResult,
- $this->model->validate($this->invoiceMock, $this->orderMock)
+ $this->model->validate($this->invoiceMock)
);
}
@@ -169,13 +147,12 @@ public function testValidateNoInvoiceItems()
$this->orderMock->expects($this->once())
->method('getItems')
->willReturn([$orderItemMock]);
- $this->orderValidatorMock->expects($this->once())
- ->method('canInvoice')
- ->with($this->orderMock)
- ->willReturn(true);
+ $this->invoiceMock->expects($this->exactly(2))
+ ->method('getOrderId')
+ ->willReturn(1);
$this->assertEquals(
$expectedResult,
- $this->model->validate($this->invoiceMock, $this->orderMock)
+ $this->model->validate($this->invoiceMock)
);
}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/OrderRegistrarTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/OrderRegistrarTest.php
new file mode 100644
index 0000000000000..e5bff791edcca
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/OrderRegistrarTest.php
@@ -0,0 +1,73 @@
+orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $this->shipmentMock = $this->getMockBuilder(\Magento\Sales\Api\Data\ShipmentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->model = new \Magento\Sales\Model\Order\Shipment\OrderRegistrar();
+ }
+
+ public function testRegister()
+ {
+ $item1 = $this->getShipmentItemMock();
+ $item1->expects($this->once())
+ ->method('getQty')
+ ->willReturn(0);
+ $item1->expects($this->never())
+ ->method('register');
+
+ $item2 = $this->getShipmentItemMock();
+ $item2->expects($this->once())
+ ->method('getQty')
+ ->willReturn(0.5);
+ $item2->expects($this->once())
+ ->method('register');
+
+ $items = [$item1, $item2];
+ $this->shipmentMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn($items);
+ $this->assertEquals(
+ $this->orderMock,
+ $this->model->register($this->orderMock, $this->shipmentMock)
+ );
+ }
+
+ /**
+ * @return \PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getShipmentItemMock()
+ {
+ return $this->getMockBuilder(\Magento\Sales\Api\Data\ShipmentItemInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['register'])
+ ->getMockForAbstractClass();
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php
new file mode 100644
index 0000000000000..8373c7e57d0fe
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php
@@ -0,0 +1,361 @@
+orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class)
+ ->setMethods(['getStoreId'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->storeMock->expects($this->any())
+ ->method('getStoreId')
+ ->willReturn(1);
+ $this->orderMock->expects($this->any())
+ ->method('getStore')
+ ->willReturn($this->storeMock);
+
+ $this->senderMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Email\Sender::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['send', 'sendCopyTo'])
+ ->getMock();
+
+ $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->shipmentMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Shipment::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['setSendEmail', 'setEmailSent'])
+ ->getMock();
+
+ $this->commentMock = $this->getMockBuilder(\Magento\Sales\Api\Data\ShipmentCommentCreationInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->commentMock->expects($this->any())
+ ->method('getComment')
+ ->willReturn('Comment text');
+
+ $this->addressMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Address::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderMock->expects($this->any())
+ ->method('getBillingAddress')
+ ->willReturn($this->addressMock);
+ $this->orderMock->expects($this->any())
+ ->method('getShippingAddress')
+ ->willReturn($this->addressMock);
+
+ $this->globalConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->paymentInfoMock = $this->getMockBuilder(\Magento\Payment\Model\Info::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderMock->expects($this->any())
+ ->method('getPayment')
+ ->willReturn($this->paymentInfoMock);
+
+ $this->paymentHelperMock = $this->getMockBuilder(\Magento\Payment\Helper\Data::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->paymentHelperMock->expects($this->any())
+ ->method('getInfoBlockHtml')
+ ->with($this->paymentInfoMock, 1)
+ ->willReturn('Payment Info Block');
+
+ $this->shipmentResourceMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Shipment::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->addressRendererMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Address\Renderer::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->addressRendererMock->expects($this->any())
+ ->method('format')
+ ->with($this->addressMock, 'html')
+ ->willReturn('Formatted address');
+
+ $this->templateContainerMock = $this->getMockBuilder(\Magento\Sales\Model\Order\Email\Container\Template::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->identityContainerMock = $this->getMockBuilder(
+ \Magento\Sales\Model\Order\Email\Container\ShipmentIdentity::class
+ )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->identityContainerMock->expects($this->any())
+ ->method('getStore')
+ ->willReturn($this->storeMock);
+
+ $this->senderBuilderFactoryMock = $this->getMockBuilder(
+ \Magento\Sales\Model\Order\Email\SenderBuilderFactory::class
+ )
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+
+ $this->subject = new \Magento\Sales\Model\Order\Shipment\Sender\EmailSender(
+ $this->templateContainerMock,
+ $this->identityContainerMock,
+ $this->senderBuilderFactoryMock,
+ $this->loggerMock,
+ $this->addressRendererMock,
+ $this->paymentHelperMock,
+ $this->shipmentResourceMock,
+ $this->globalConfigMock,
+ $this->eventManagerMock
+ );
+ }
+
+ /**
+ * @param int $configValue
+ * @param bool $forceSyncMode
+ * @param bool $isComment
+ * @param bool $emailSendingResult
+ *
+ * @dataProvider sendDataProvider
+ *
+ * @return void
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testSend($configValue, $forceSyncMode, $isComment, $emailSendingResult)
+ {
+ $this->globalConfigMock->expects($this->once())
+ ->method('getValue')
+ ->with('sales_email/general/async_sending')
+ ->willReturn($configValue);
+
+ if (!$isComment) {
+ $this->commentMock = null;
+ }
+
+ $this->shipmentMock->expects($this->once())
+ ->method('setSendEmail')
+ ->with(true);
+
+ if (!$configValue || $forceSyncMode) {
+ $transport = [
+ 'order' => $this->orderMock,
+ 'shipment' => $this->shipmentMock,
+ 'comment' => $isComment ? 'Comment text' : '',
+ 'billing' => $this->addressMock,
+ 'payment_html' => 'Payment Info Block',
+ 'store' => $this->storeMock,
+ 'formattedShippingAddress' => 'Formatted address',
+ 'formattedBillingAddress' => 'Formatted address',
+ ];
+
+ $this->eventManagerMock->expects($this->once())
+ ->method('dispatch')
+ ->with(
+ 'email_shipment_set_template_vars_before',
+ [
+ 'sender' => $this->subject,
+ 'transport' => $transport,
+ ]
+ );
+
+ $this->templateContainerMock->expects($this->once())
+ ->method('setTemplateVars')
+ ->with($transport);
+
+ $this->identityContainerMock->expects($this->once())
+ ->method('isEnabled')
+ ->willReturn($emailSendingResult);
+
+ if ($emailSendingResult) {
+ $this->senderBuilderFactoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->senderMock);
+
+ $this->senderMock->expects($this->once())
+ ->method('send');
+
+ $this->senderMock->expects($this->once())
+ ->method('sendCopyTo');
+
+ $this->shipmentMock->expects($this->once())
+ ->method('setEmailSent')
+ ->with(true);
+
+ $this->shipmentResourceMock->expects($this->once())
+ ->method('saveAttribute')
+ ->with($this->shipmentMock, ['send_email', 'email_sent']);
+
+ $this->assertTrue(
+ $this->subject->send(
+ $this->orderMock,
+ $this->shipmentMock,
+ $this->commentMock,
+ $forceSyncMode
+ )
+ );
+ } else {
+ $this->shipmentResourceMock->expects($this->once())
+ ->method('saveAttribute')
+ ->with($this->shipmentMock, 'send_email');
+
+ $this->assertFalse(
+ $this->subject->send(
+ $this->orderMock,
+ $this->shipmentMock,
+ $this->commentMock,
+ $forceSyncMode
+ )
+ );
+ }
+ } else {
+ $this->shipmentMock->expects($this->once())
+ ->method('setEmailSent')
+ ->with(null);
+
+ $this->shipmentResourceMock->expects($this->at(0))
+ ->method('saveAttribute')
+ ->with($this->shipmentMock, 'email_sent');
+ $this->shipmentResourceMock->expects($this->at(1))
+ ->method('saveAttribute')
+ ->with($this->shipmentMock, 'send_email');
+
+ $this->assertFalse(
+ $this->subject->send(
+ $this->orderMock,
+ $this->shipmentMock,
+ $this->commentMock,
+ $forceSyncMode
+ )
+ );
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function sendDataProvider()
+ {
+ return [
+ 'Successful sync sending with comment' => [0, false, true, true],
+ 'Successful sync sending without comment' => [0, false, false, true],
+ 'Failed sync sending with comment' => [0, false, true, false],
+ 'Successful forced sync sending with comment' => [1, true, true, true],
+ 'Async sending' => [1, false, false, false],
+ ];
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Validation/QuantityValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Validation/QuantityValidatorTest.php
new file mode 100644
index 0000000000000..01cccd2458695
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Validation/QuantityValidatorTest.php
@@ -0,0 +1,67 @@
+shipmentMock = $this->getMockBuilder(ShipmentInterface::class)
+ ->getMock();
+ $this->shipmentItemMock = $this->getMockBuilder(ShipmentItemInterface::class)
+ ->getMock();
+ $this->validator = $objectManagerHelper->getObject(QuantityValidator::class);
+ }
+
+ public function testValidateTrackWithoutOrderId()
+ {
+ $this->shipmentMock->expects($this->once())
+ ->method('getOrderId')
+ ->willReturn(null);
+ $this->assertEquals(
+ [__('Order Id is required for shipment document')],
+ $this->validator->validate($this->shipmentMock)
+ );
+ }
+
+ public function testValidateTrackWithoutItems()
+ {
+ $this->shipmentMock->expects($this->once())
+ ->method('getOrderId')
+ ->willReturn(1);
+ $this->shipmentMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn(null);
+ $this->assertEquals(
+ [__('You can\'t create a shipment without products.')],
+ $this->validator->validate($this->shipmentMock)
+ );
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Validation/TrackValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Validation/TrackValidatorTest.php
new file mode 100644
index 0000000000000..0d8d951ccf18a
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Validation/TrackValidatorTest.php
@@ -0,0 +1,74 @@
+shipmentMock = $this->getMockBuilder(ShipmentInterface::class)
+ ->getMockForAbstractClass();
+ $this->shipmentTrackMock = $this->getMockBuilder(ShipmentTrackInterface::class)
+ ->getMockForAbstractClass();
+ $this->validator = $objectManagerHelper->getObject(TrackValidator::class);
+ }
+
+ public function testValidateTrackWithNumber()
+ {
+ $this->shipmentTrackMock->expects($this->once())
+ ->method('getTrackNumber')
+ ->willReturn('12345');
+ $this->shipmentMock->expects($this->exactly(2))
+ ->method('getTracks')
+ ->willReturn([$this->shipmentTrackMock]);
+ $this->assertEquals([], $this->validator->validate($this->shipmentMock));
+ }
+
+ public function testValidateTrackWithoutNumber()
+ {
+ $this->shipmentTrackMock->expects($this->once())
+ ->method('getTrackNumber')
+ ->willReturn(null);
+ $this->shipmentMock->expects($this->exactly(2))
+ ->method('getTracks')
+ ->willReturn([$this->shipmentTrackMock]);
+ $this->assertEquals([__('Please enter a tracking number.')], $this->validator->validate($this->shipmentMock));
+ }
+
+ public function testValidateTrackWithEmptyTracks()
+ {
+ $this->shipmentTrackMock->expects($this->never())
+ ->method('getTrackNumber');
+ $this->shipmentMock->expects($this->once())
+ ->method('getTracks')
+ ->willReturn([]);
+ $this->assertEquals([], $this->validator->validate($this->shipmentMock));
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php
new file mode 100644
index 0000000000000..b0677b050f6fb
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php
@@ -0,0 +1,195 @@
+shipmentFactoryMock = $this->getMockBuilder(ShipmentFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderMock = $this->getMockBuilder(Order::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->itemMock = $this->getMockBuilder(ShipmentItemCreationInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->commentMock = $this->getMockBuilder(ShipmentCommentCreationInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->shipmentMock = $this->getMockBuilder(ShipmentInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['addComment', 'addTrack'])
+ ->getMockForAbstractClass();
+
+ $this->hydratorPoolMock = $this->getMockBuilder(HydratorPool::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->trackFactoryMock = $this->getMockBuilder(TrackFactory::class)
+ ->setMethods(['create'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->trackMock = $this->getMockBuilder(Track::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->hydratorMock = $this->getMockBuilder(HydratorInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->shipmentDocumentFactory = new ShipmentDocumentFactory(
+ $this->shipmentFactoryMock,
+ $this->hydratorPoolMock,
+ $this->trackFactoryMock
+ );
+ }
+
+ public function testCreate()
+ {
+ $trackNum = "123456789";
+ $trackData = [$trackNum];
+ $tracks = [$this->trackMock];
+ $appendComment = true;
+ $packages = [];
+ $items = [1 => 10];
+
+ $this->itemMock->expects($this->once())
+ ->method('getOrderItemId')
+ ->willReturn(1);
+
+ $this->itemMock->expects($this->once())
+ ->method('getQty')
+ ->willReturn(10);
+
+ $this->shipmentFactoryMock->expects($this->once())
+ ->method('create')
+ ->with(
+ $this->orderMock,
+ $items
+ )
+ ->willReturn($this->shipmentMock);
+
+ $this->shipmentMock->expects($this->once())
+ ->method('addTrack')
+ ->willReturnSelf();
+
+ $this->hydratorPoolMock->expects($this->once())
+ ->method('getHydrator')
+ ->with(ShipmentTrackCreationInterface::class)
+ ->willReturn($this->hydratorMock);
+
+ $this->hydratorMock->expects($this->once())
+ ->method('extract')
+ ->with($this->trackMock)
+ ->willReturn($trackData);
+
+ $this->trackFactoryMock->expects($this->once())
+ ->method('create')
+ ->with(['data' => $trackData])
+ ->willReturn($this->trackMock);
+
+ if ($appendComment) {
+ $comment = "New comment!";
+ $visibleOnFront = true;
+ $this->commentMock->expects($this->once())
+ ->method('getComment')
+ ->willReturn($comment);
+
+ $this->commentMock->expects($this->once())
+ ->method('getIsVisibleOnFront')
+ ->willReturn($visibleOnFront);
+
+ $this->shipmentMock->expects($this->once())
+ ->method('addComment')
+ ->with($comment, $appendComment, $visibleOnFront)
+ ->willReturnSelf();
+ }
+
+ $this->assertEquals(
+ $this->shipmentDocumentFactory->create(
+ $this->orderMock,
+ [$this->itemMock],
+ $tracks,
+ $this->commentMock,
+ $appendComment,
+ $packages
+ ),
+ $this->shipmentMock
+ );
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentFactoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentFactoryTest.php
index 3760934457a85..46d6ac62fc256 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentFactoryTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentFactoryTest.php
@@ -5,10 +5,9 @@
*/
namespace Magento\Sales\Test\Unit\Model\Order;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-
/**
* Unit test for shipment factory class.
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ShipmentFactoryTest extends \PHPUnit_Framework_TestCase
{
@@ -39,7 +38,7 @@ class ShipmentFactoryTest extends \PHPUnit_Framework_TestCase
*/
protected function setUp()
{
- $objectManager = new ObjectManager($this);
+ $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->converter = $this->getMock(
\Magento\Sales\Model\Convert\Order::class,
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/OrderValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanInvoiceTest.php
similarity index 77%
rename from app/code/Magento/Sales/Test/Unit/Model/Order/OrderValidatorTest.php
rename to app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanInvoiceTest.php
index 905d7c7a5b3f8..dd76bc1e52586 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/OrderValidatorTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanInvoiceTest.php
@@ -4,17 +4,17 @@
* See COPYING.txt for license details.
*/
-namespace Magento\Sales\Test\Unit\Model\Order;
+namespace Magento\Sales\Test\Unit\Model\Order\Validation;
use Magento\Sales\Model\Order;
/**
* Test for \Magento\Sales\Model\Order\OrderValidator class
*/
-class OrderValidatorTest extends \PHPUnit_Framework_TestCase
+class CanInvoiceTest extends \PHPUnit_Framework_TestCase
{
/**
- * @var \Magento\Sales\Model\Order\OrderValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Sales\Model\Order\Validation\CanInvoice|\PHPUnit_Framework_MockObject_MockObject
*/
private $model;
@@ -47,7 +47,7 @@ protected function setUp()
->setMethods(['getQtyToInvoice', 'getLockedDoInvoice'])
->getMockForAbstractClass();
- $this->model = new \Magento\Sales\Model\Order\OrderValidator();
+ $this->model = new \Magento\Sales\Model\Order\Validation\CanInvoice();
}
/**
@@ -62,9 +62,12 @@ public function testCanInvoiceWrongState($state)
->willReturn($state);
$this->orderMock->expects($this->never())
->method('getItems');
+ $this->orderMock->expects($this->once())
+ ->method('getStatus')
+ ->willReturn('status');
$this->assertEquals(
- false,
- $this->model->canInvoice($this->orderMock)
+ [__('An invoice cannot be created when an order has a status of %1', 'status')],
+ $this->model->validate($this->orderMock)
);
}
@@ -93,9 +96,8 @@ public function testCanInvoiceNoItems()
->method('getItems')
->willReturn([]);
- $this->assertEquals(
- false,
- $this->model->canInvoice($this->orderMock)
+ $this->assertNotEmpty(
+ $this->model->validate($this->orderMock)
);
}
@@ -125,7 +127,7 @@ public function testCanInvoice($qtyToInvoice, $itemLockedDoInvoice, $expectedRes
$this->assertEquals(
$expectedResult,
- $this->model->canInvoice($this->orderMock)
+ $this->model->validate($this->orderMock)
);
}
@@ -137,10 +139,10 @@ public function testCanInvoice($qtyToInvoice, $itemLockedDoInvoice, $expectedRes
public function canInvoiceDataProvider()
{
return [
- [0, null, false],
- [-1, null, false],
- [1, true, false],
- [0.5, false, true],
+ [0, null, [__('The order does not allow an invoice to be created.')]],
+ [-1, null, [__('The order does not allow an invoice to be created.')]],
+ [1, true, [__('The order does not allow an invoice to be created.')]],
+ [0.5, false, []],
];
}
}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanShipTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanShipTest.php
new file mode 100644
index 0000000000000..11d99fbb9cced
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanShipTest.php
@@ -0,0 +1,146 @@
+objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+ $this->orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getStatus', 'getItems'])
+ ->getMockForAbstractClass();
+
+ $this->orderItemMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderItemInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getQtyToShip', 'getLockedDoShip'])
+ ->getMockForAbstractClass();
+
+ $this->model = new \Magento\Sales\Model\Order\Validation\CanShip();
+ }
+
+ /**
+ * @param string $state
+ *
+ * @dataProvider canShipWrongStateDataProvider
+ */
+ public function testCanShipWrongState($state)
+ {
+ $this->orderMock->expects($this->any())
+ ->method('getState')
+ ->willReturn($state);
+ $this->orderMock->expects($this->once())
+ ->method('getStatus')
+ ->willReturn('status');
+ $this->orderMock->expects($this->never())
+ ->method('getItems');
+ $this->assertEquals(
+ [__('A shipment cannot be created when an order has a status of %1', 'status')],
+ $this->model->validate($this->orderMock)
+ );
+ }
+
+ /**
+ * Data provider for testCanShipWrongState
+ * @return array
+ */
+ public function canShipWrongStateDataProvider()
+ {
+ return [
+ [Order::STATE_PAYMENT_REVIEW],
+ [Order::STATE_HOLDED],
+ [Order::STATE_CANCELED],
+ ];
+ }
+
+ public function testCanShipNoItems()
+ {
+ $this->orderMock->expects($this->any())
+ ->method('getState')
+ ->willReturn(Order::STATE_PROCESSING);
+
+ $this->orderMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn([]);
+
+ $this->assertNotEmpty(
+ $this->model->validate($this->orderMock)
+ );
+ }
+
+ /**
+ * @param float $qtyToShipment
+ * @param bool|null $itemLockedDoShipment
+ * @param bool $expectedResult
+ *
+ * @dataProvider canShipDataProvider
+ */
+ public function testCanShip($qtyToShipment, $itemLockedDoShipment, $expectedResult)
+ {
+ $this->orderMock->expects($this->any())
+ ->method('getState')
+ ->willReturn(Order::STATE_PROCESSING);
+
+ $items = [$this->orderItemMock];
+ $this->orderMock->expects($this->once())
+ ->method('getItems')
+ ->willReturn($items);
+ $this->orderItemMock->expects($this->any())
+ ->method('getQtyToShip')
+ ->willReturn($qtyToShipment);
+ $this->orderItemMock->expects($this->any())
+ ->method('getLockedDoShip')
+ ->willReturn($itemLockedDoShipment);
+
+ $this->assertEquals(
+ $expectedResult,
+ $this->model->validate($this->orderMock)
+ );
+ }
+
+ /**
+ * Data provider for testCanShip
+ *
+ * @return array
+ */
+ public function canShipDataProvider()
+ {
+ return [
+ [0, null, [__('The order does not allow a shipment to be created.')]],
+ [-1, null, [__('The order does not allow a shipment to be created.')]],
+ [1, true, [__('The order does not allow a shipment to be created.')]],
+ [0.5, false, []],
+ ];
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/ShipOrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/ShipOrderTest.php
new file mode 100644
index 0000000000000..b719babf209f0
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Unit/Model/ShipOrderTest.php
@@ -0,0 +1,430 @@
+resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->shipmentDocumentFactoryMock = $this->getMockBuilder(ShipmentDocumentFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->shipmentValidatorMock = $this->getMockBuilder(ShipmentValidatorInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->orderValidatorMock = $this->getMockBuilder(OrderValidatorInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->orderRegistrarMock = $this->getMockBuilder(OrderRegistrarInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->orderStateResolverMock = $this->getMockBuilder(OrderStateResolverInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->configMock = $this->getMockBuilder(OrderConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->shipmentRepositoryMock = $this->getMockBuilder(ShipmentRepositoryInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->notifierInterfaceMock = $this->getMockBuilder(NotifierInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->loggerMock = $this->getMockBuilder(LoggerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->shipmentCommentCreationMock = $this->getMockBuilder(ShipmentCommentCreationInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->shipmentCreationArgumentsMock = $this->getMockBuilder(ShipmentCreationArgumentsInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->orderMock = $this->getMockBuilder(OrderInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->shipmentMock = $this->getMockBuilder(ShipmentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->packageMock = $this->getMockBuilder(ShipmentPackageInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->trackMock = $this->getMockBuilder(ShipmentTrackCreationInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->adapterMock = $this->getMockBuilder(AdapterInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+ $this->model = $helper->getObject(
+ ShipOrder::class,
+ [
+ 'resourceConnection' => $this->resourceConnectionMock,
+ 'orderRepository' => $this->orderRepositoryMock,
+ 'shipmentRepository' => $this->shipmentRepositoryMock,
+ 'shipmentDocumentFactory' => $this->shipmentDocumentFactoryMock,
+ 'shipmentValidator' => $this->shipmentValidatorMock,
+ 'orderValidator' => $this->orderValidatorMock,
+ 'orderStateResolver' => $this->orderStateResolverMock,
+ 'orderRegistrar' => $this->orderRegistrarMock,
+ 'notifierInterface' => $this->notifierInterfaceMock,
+ 'config' => $this->configMock,
+ 'logger' => $this->loggerMock
+ ]
+ );
+ }
+
+ /**
+ * @dataProvider dataProvider
+ */
+ public function testExecute($orderId, $items, $notify, $appendComment)
+ {
+ $this->resourceConnectionMock->expects($this->once())
+ ->method('getConnection')
+ ->with('sales')
+ ->willReturn($this->adapterMock);
+
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('get')
+ ->willReturn($this->orderMock);
+
+ $this->shipmentDocumentFactoryMock->expects($this->once())
+ ->method('create')
+ ->with(
+ $this->orderMock,
+ $items,
+ [$this->trackMock],
+ $this->shipmentCommentCreationMock,
+ ($appendComment && $notify),
+ [$this->packageMock],
+ $this->shipmentCreationArgumentsMock
+ )->willReturn($this->shipmentMock);
+
+ $this->shipmentValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->shipmentMock)
+ ->willReturn([]);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->orderMock)
+ ->willReturn([]);
+
+ $this->orderRegistrarMock->expects($this->once())
+ ->method('register')
+ ->with($this->orderMock, $this->shipmentMock)
+ ->willReturn($this->orderMock);
+
+ $this->orderStateResolverMock->expects($this->once())
+ ->method('getStateForOrder')
+ ->with($this->orderMock, [OrderStateResolverInterface::IN_PROGRESS])
+ ->willReturn(Order::STATE_PROCESSING);
+
+ $this->orderMock->expects($this->once())
+ ->method('setState')
+ ->with(Order::STATE_PROCESSING)
+ ->willReturnSelf();
+
+ $this->orderMock->expects($this->once())
+ ->method('getState')
+ ->willReturn(Order::STATE_PROCESSING);
+
+ $this->configMock->expects($this->once())
+ ->method('getStateDefaultStatus')
+ ->with(Order::STATE_PROCESSING)
+ ->willReturn('Processing');
+
+ $this->orderMock->expects($this->once())
+ ->method('setStatus')
+ ->with('Processing')
+ ->willReturnSelf();
+
+ $this->shipmentRepositoryMock->expects($this->once())
+ ->method('save')
+ ->with($this->shipmentMock)
+ ->willReturn($this->shipmentMock);
+
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('save')
+ ->with($this->orderMock)
+ ->willReturn($this->orderMock);
+
+ if ($notify) {
+ $this->notifierInterfaceMock->expects($this->once())
+ ->method('notify')
+ ->with($this->orderMock, $this->shipmentMock, $this->shipmentCommentCreationMock);
+ }
+
+ $this->shipmentMock->expects($this->once())
+ ->method('getEntityId')
+ ->willReturn(2);
+
+ $this->assertEquals(
+ 2,
+ $this->model->execute(
+ $orderId,
+ $items,
+ $notify,
+ $appendComment,
+ $this->shipmentCommentCreationMock,
+ [$this->trackMock],
+ [$this->packageMock],
+ $this->shipmentCreationArgumentsMock
+ )
+ );
+ }
+
+ /**
+ * @expectedException \Magento\Sales\Api\Exception\DocumentValidationExceptionInterface
+ */
+ public function testDocumentValidationException()
+ {
+ $orderId = 1;
+ $items = [1 => 2];
+ $notify = true;
+ $appendComment = true;
+ $errorMessages = ['error1', 'error2'];
+
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('get')
+ ->willReturn($this->orderMock);
+
+ $this->shipmentDocumentFactoryMock->expects($this->once())
+ ->method('create')
+ ->with(
+ $this->orderMock,
+ $items,
+ [$this->trackMock],
+ $this->shipmentCommentCreationMock,
+ ($appendComment && $notify),
+ [$this->packageMock],
+ $this->shipmentCreationArgumentsMock
+ )->willReturn($this->shipmentMock);
+
+ $this->shipmentValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->shipmentMock)
+ ->willReturn($errorMessages);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->orderMock)
+ ->willReturn([]);
+
+ $this->model->execute(
+ $orderId,
+ $items,
+ $notify,
+ $appendComment,
+ $this->shipmentCommentCreationMock,
+ [$this->trackMock],
+ [$this->packageMock],
+ $this->shipmentCreationArgumentsMock
+ );
+ }
+
+ /**
+ * @expectedException \Magento\Sales\Api\Exception\CouldNotShipExceptionInterface
+ */
+ public function testCouldNotInvoiceException()
+ {
+ $orderId = 1;
+ $this->resourceConnectionMock->expects($this->once())
+ ->method('getConnection')
+ ->with('sales')
+ ->willReturn($this->adapterMock);
+
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('get')
+ ->willReturn($this->orderMock);
+
+ $this->shipmentDocumentFactoryMock->expects($this->once())
+ ->method('create')
+ ->with(
+ $this->orderMock
+ )->willReturn($this->shipmentMock);
+
+ $this->shipmentValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->shipmentMock)
+ ->willReturn([]);
+ $this->orderValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($this->orderMock)
+ ->willReturn([]);
+ $e = new \Exception();
+
+ $this->orderRegistrarMock->expects($this->once())
+ ->method('register')
+ ->with($this->orderMock, $this->shipmentMock)
+ ->willThrowException($e);
+
+ $this->loggerMock->expects($this->once())
+ ->method('critical')
+ ->with($e);
+
+ $this->adapterMock->expects($this->once())
+ ->method('rollBack');
+
+ $this->model->execute(
+ $orderId
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProvider()
+ {
+ return [
+ 'TestWithNotifyTrue' => [1, [1 => 2], true, true],
+ 'TestWithNotifyFalse' => [1, [1 => 2], false, true],
+ ];
+ }
+}
diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml
index 383650c8688fc..dfdb0f6a261c5 100644
--- a/app/code/Magento/Sales/etc/di.xml
+++ b/app/code/Magento/Sales/etc/di.xml
@@ -30,7 +30,9 @@
+
+
@@ -49,7 +51,10 @@
-
+
+
+
+
@@ -62,13 +67,14 @@
+
-
+
-
+
@@ -86,10 +92,14 @@
-
-
-
+
+
+
+
+
+
+
@@ -909,4 +919,18 @@
+
+
+
+ - Magento\Sales\Model\Order\Shipment\Sender\EmailSender
+
+
+
+
+
+
+ - Magento\Framework\EntityManager\HydratorInterface
+
+
+
diff --git a/app/code/Magento/Sales/etc/webapi.xml b/app/code/Magento/Sales/etc/webapi.xml
index 8d1b1fda5bc31..4c7fe03a201f8 100644
--- a/app/code/Magento/Sales/etc/webapi.xml
+++ b/app/code/Magento/Sales/etc/webapi.xml
@@ -235,6 +235,12 @@
+
+
+
+
+
+
@@ -254,7 +260,7 @@
-
+
diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php
index adbd96624649d..d265159bc630b 100644
--- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php
+++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php
@@ -7,8 +7,12 @@
namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment;
use Magento\Backend\App\Action;
-use Magento\Sales\Model\Order\Email\Sender\ShipmentSender;
+use Magento\Sales\Model\Order\Shipment\Validation\QuantityValidator;
+/**
+ * Class Save
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class Save extends \Magento\Backend\App\Action
{
/**
@@ -29,21 +33,26 @@ class Save extends \Magento\Backend\App\Action
protected $labelGenerator;
/**
- * @var ShipmentSender
+ * @var \Magento\Sales\Model\Order\Email\Sender\ShipmentSender
*/
protected $shipmentSender;
/**
- * @param Action\Context $context
+ * @var \Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface
+ */
+ private $shipmentValidator;
+
+ /**
+ * @param \Magento\Backend\App\Action\Context $context
* @param \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader $shipmentLoader
* @param \Magento\Shipping\Model\Shipping\LabelGenerator $labelGenerator
- * @param ShipmentSender $shipmentSender
+ * @param \Magento\Sales\Model\Order\Email\Sender\ShipmentSender $shipmentSender
*/
public function __construct(
- Action\Context $context,
+ \Magento\Backend\App\Action\Context $context,
\Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader $shipmentLoader,
\Magento\Shipping\Model\Shipping\LabelGenerator $labelGenerator,
- ShipmentSender $shipmentSender
+ \Magento\Sales\Model\Order\Email\Sender\ShipmentSender $shipmentSender
) {
$this->shipmentLoader = $shipmentLoader;
$this->labelGenerator = $labelGenerator;
@@ -119,7 +128,14 @@ public function execute()
$shipment->setCustomerNote($data['comment_text']);
$shipment->setCustomerNoteNotify(isset($data['comment_customer_notify']));
}
-
+ $errorMessages = $this->getShipmentValidator()->validate($shipment, [QuantityValidator::class]);
+ if (!empty($errorMessages)) {
+ $this->messageManager->addError(
+ __("Shipment Document Validation Error(s):\n" . implode("\n", $errorMessages))
+ );
+ $this->_redirect('*/*/new', ['order_id' => $this->getRequest()->getParam('order_id')]);
+ return;
+ }
$shipment->register();
$shipment->getOrder()->setCustomerNoteNotify(!empty($data['send_email']));
@@ -168,4 +184,19 @@ public function execute()
$this->_redirect('sales/order/view', ['order_id' => $shipment->getOrderId()]);
}
}
+
+ /**
+ * @return \Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface
+ * @deprecated
+ */
+ private function getShipmentValidator()
+ {
+ if ($this->shipmentValidator === null) {
+ $this->shipmentValidator = $this->_objectManager->get(
+ \Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface::class
+ );
+ }
+
+ return $this->shipmentValidator;
+ }
}
diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/ShipmentLoader.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/ShipmentLoader.php
index b452c88887c9e..c4efe6f6507d5 100644
--- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/ShipmentLoader.php
+++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/ShipmentLoader.php
@@ -1,6 +1,5 @@
method('getFormKeyValidator')
->will($this->returnValue($this->formKeyValidator));
+ $this->shipmentValidatorMock = $this->getMockBuilder(ShipmentValidatorInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
$this->saveAction = $objectManagerHelper->getObject(
\Magento\Shipping\Controller\Adminhtml\Order\Shipment\Save::class,
[
@@ -218,7 +230,8 @@ protected function setUp()
'context' => $this->context,
'shipmentLoader' => $this->shipmentLoader,
'request' => $this->request,
- 'response' => $this->response
+ 'response' => $this->response,
+ 'shipmentValidator' => $this->shipmentValidatorMock
]
);
}
@@ -346,6 +359,11 @@ public function testExecute($formKeyIsValid, $isPost)
->will($this->returnValue($orderId));
$this->prepareRedirect($path, $arguments);
+ $this->shipmentValidatorMock->expects($this->once())
+ ->method('validate')
+ ->with($shipment, [QuantityValidator::class])
+ ->willReturn([]);
+
$this->saveAction->execute();
$this->assertEquals($this->response, $this->saveAction->getResponse());
}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php
index cb384134a7c68..60c9f54ea132c 100644
--- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderInvoiceCreateTest.php
@@ -10,7 +10,7 @@
*/
class OrderInvoiceCreateTest extends \Magento\TestFramework\TestCase\WebapiAbstract
{
- const SERVICE_READ_NAME = 'salesOrderInvoiceV1';
+ const SERVICE_READ_NAME = 'salesInvoiceOrderV1';
const SERVICE_VERSION = 'V1';
/**
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php
new file mode 100644
index 0000000000000..8de7c4dc7f65b
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipOrderTest.php
@@ -0,0 +1,100 @@
+objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+ $this->shipmentRepository = $this->objectManager->get(
+ \Magento\Sales\Api\ShipmentRepositoryInterface::class
+ );
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Sales/_files/order_new.php
+ */
+ public function testShipOrder()
+ {
+ /** @var \Magento\Sales\Model\Order $existingOrder */
+ $existingOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
+ ->loadByIncrementId('100000001');
+
+ $serviceInfo = [
+ 'rest' => [
+ 'resourcePath' => '/V1/order/' . $existingOrder->getId() . '/ship',
+ 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
+ ],
+ 'soap' => [
+ 'service' => self::SERVICE_READ_NAME,
+ 'serviceVersion' => self::SERVICE_VERSION,
+ 'operation' => self::SERVICE_READ_NAME . 'execute',
+ ],
+ ];
+
+ $requestData = [
+ 'orderId' => $existingOrder->getId(),
+ 'items' => [],
+ 'comment' => [
+ 'comment' => 'Test Comment',
+ 'is_visible_on_front' => 1,
+ ],
+ 'tracks' => [
+ [
+ 'track_number' => 'TEST_TRACK_0001',
+ 'title' => 'Simple shipment track',
+ 'carrier_code' => 'UPS'
+ ]
+ ]
+ ];
+
+ /** @var \Magento\Sales\Api\Data\OrderItemInterface $item */
+ foreach ($existingOrder->getAllItems() as $item) {
+ $requestData['items'][] = [
+ 'order_item_id' => $item->getItemId(),
+ 'qty' => $item->getQtyOrdered(),
+ ];
+ }
+
+ $result = $this->_webApiCall($serviceInfo, $requestData);
+
+ $this->assertNotEmpty($result);
+
+ try {
+ $this->shipmentRepository->get($result);
+ } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+ $this->fail('Failed asserting that Shipment was created');
+ }
+
+ /** @var \Magento\Sales\Model\Order $updatedOrder */
+ $updatedOrder = $this->objectManager->create(\Magento\Sales\Model\Order::class)
+ ->loadByIncrementId('100000001');
+
+ $this->assertNotEquals(
+ $existingOrder->getStatus(),
+ $updatedOrder->getStatus(),
+ 'Failed asserting that Order status was changed'
+ );
+ }
+}
diff --git a/lib/internal/Magento/Framework/EntityManager/CustomAttributesMapper.php b/lib/internal/Magento/Framework/EntityManager/CustomAttributesMapper.php
index 9147d47f3d9dd..fe3a199da86a3 100644
--- a/lib/internal/Magento/Framework/EntityManager/CustomAttributesMapper.php
+++ b/lib/internal/Magento/Framework/EntityManager/CustomAttributesMapper.php
@@ -55,8 +55,9 @@ public function __construct(
*/
public function entityToDatabase($entityType, $data)
{
- $metadata = $this->metadataPool->getMetadata($entityType);
- if (!$metadata->getEavEntityType()) {
+ if (!$this->metadataPool->hasConfiguration($entityType)
+ || !$this->metadataPool->getMetadata($entityType)->getEavEntityType()
+ ) {
return $data;
}
if (isset($data[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES])) {
diff --git a/lib/internal/Magento/Framework/EntityManager/Test/Unit/CustomAttributesMapperTest.php b/lib/internal/Magento/Framework/EntityManager/Test/Unit/CustomAttributesMapperTest.php
index a39ad4afaa6ee..56977af1dd6eb 100644
--- a/lib/internal/Magento/Framework/EntityManager/Test/Unit/CustomAttributesMapperTest.php
+++ b/lib/internal/Magento/Framework/EntityManager/Test/Unit/CustomAttributesMapperTest.php
@@ -48,12 +48,18 @@ public function testEntityToDatabase()
$metadataPool = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class)
->disableOriginalConstructor()
- ->setMethods(['getMetadata'])
+ ->setMethods(['getMetadata', 'hasConfiguration'])
->getMock();
+ $metadataPool->expects($this->any())
+ ->method('hasConfiguration')
+ ->willReturn(true);
$metadataPool->expects($this->any())
->method('getMetadata')
->with($this->equalTo(\Magento\Customer\Api\Data\AddressInterface::class))
->will($this->returnValue($metadata));
+ $metadataPool->expects($this->once())
+ ->method('hasConfiguration')
+ ->willReturn(true);
$searchCriteriaBuilder = $this->getMockBuilder(\Magento\Framework\Api\SearchCriteriaBuilder::class)
->disableOriginalConstructor()
@@ -76,6 +82,7 @@ public function testEntityToDatabase()
'metadataPool' => $metadataPool,
'searchCriteriaBuilder' => $searchCriteriaBuilder
]);
+
$actual = $customAttributesMapper->entityToDatabase(
\Magento\Customer\Api\Data\AddressInterface::class,
[
diff --git a/lib/internal/Magento/Framework/EntityManager/TypeResolver.php b/lib/internal/Magento/Framework/EntityManager/TypeResolver.php
index 28e2bdaa70942..2718162e80d66 100644
--- a/lib/internal/Magento/Framework/EntityManager/TypeResolver.php
+++ b/lib/internal/Magento/Framework/EntityManager/TypeResolver.php
@@ -20,7 +20,8 @@ class TypeResolver
*/
private $typeMapping = [
\Magento\SalesRule\Model\Rule::class => \Magento\SalesRule\Api\Data\RuleInterface::class,
- \Magento\SalesRule\Model\Rule\Interceptor::class => \Magento\SalesRule\Api\Data\RuleInterface::class
+ \Magento\SalesRule\Model\Rule\Interceptor::class => \Magento\SalesRule\Api\Data\RuleInterface::class,
+ \Magento\SalesRule\Model\Rule\Proxy::class => \Magento\SalesRule\Api\Data\RuleInterface::class
];
/**
@@ -50,8 +51,7 @@ public function resolve($type)
$dataInterfaces = [];
foreach ($interfaceNames as $interfaceName) {
if (strpos($interfaceName, '\Api\Data\\')) {
- $dataInterfaces[] = isset($this->config[$interfaceName])
- ? $this->config[$interfaceName] : $interfaceName;
+ $dataInterfaces[] = $interfaceName;
}
}
@@ -64,7 +64,9 @@ public function resolve($type)
$this->typeMapping[$className] = $dataInterface;
}
}
-
+ if (empty($this->typeMapping[$className])) {
+ $this->typeMapping[$className] = reset($dataInterfaces);
+ }
return $this->typeMapping[$className];
}
}