-
-
Save rodde177/bd683e2bcd34787962699501c9c6c349 to your computer and use it in GitHub Desktop.
Programatically Create Order
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @author Frank Clark | |
*/ | |
namespace Vendor\Namspace\Model\Subscription\Order; | |
use Vendor\Namspace\Model\ResourceModel\Subscriptions\CollectionFactory; | |
use Vendor\Namspace\Api\SubscriptionsOrdersRepositoryInterface; | |
use Vendor\Namspace\Model\SubscriptionsOrdersFactory; | |
use Vendor\Namspace\Model\Subscriptions; | |
use Vendor\Namspace\Logger\Logger; | |
use Magento\Backend\Model\Search\Order; | |
use Magento\Sales\Model\OrderRepository; | |
use Magento\Sales\Model\Order\Item as SalesOrderItem; | |
use Magento\Quote\Model\Quote; | |
use Magento\Quote\Model\QuoteFactory; | |
use Magento\Quote\Model\QuoteManagement; | |
use Magento\Quote\Api\Data\CurrencyInterface; | |
use Magento\Sales\Model\AdminOrder\Product\Quote\Initializer; | |
use Magento\Store\Model\Store; | |
use Magento\Customer\Model\CustomerFactory; | |
use Magento\Customer\Api\CustomerRepositoryInterface; | |
use Magento\Framework\Stdlib\DateTime\DateTimeFactory; | |
use Magento\Framework\DataObject; | |
use Magento\Framework\Exception\LocalizedException; | |
use Magento\Catalog\Api\ProductRepositoryInterface; | |
use Magento\Catalog\Model\Product; | |
/** | |
* Class Release | |
* | |
* @package Vendor\Namspace\Model\Subscription\Order\Release | |
*/ | |
class Release | |
{ | |
const SUCCESS = 1; | |
const FAILURE = 2; | |
// Subscriptions | |
protected $_subscriptionsCollectionFactory; | |
protected $_subscriptionsOrdersRepository; | |
protected $_subscriptionsOrdersFactory; | |
// Order | |
protected $_orderRepository; | |
// Quote | |
protected $_quoteFactory; | |
protected $_quoteManagement; | |
protected $_currencyInterface; | |
protected $_quoteInitializer; | |
// Customer | |
protected $_customerFactory; | |
protected $_customerInterface; | |
// Misc | |
protected $_dateTime; | |
protected $_logger; | |
protected $_store; | |
protected $_productRepository; | |
protected $_productModel; | |
protected $_subscriptionsReleased = array(); | |
private $_order = false; | |
public function __construct( | |
CollectionFactory $subscriptionsCollectionFactory, | |
DateTimeFactory $dateTime, | |
Logger $logger, | |
OrderRepository $orderRepository, | |
QuoteFactory $quoteFactory, | |
QuoteManagement $quoteManagement, | |
Store $store, | |
CustomerFactory $customerFactory, | |
CustomerRepositoryInterface $customerInterface, | |
CurrencyInterface $currencyInterface, | |
ProductRepositoryInterface $productRepository, | |
SubscriptionsOrdersRepositoryInterface $subscriptionsOrdersRepository, | |
SubscriptionsOrdersFactory $subscriptionsOrdersFactory, | |
Initializer $quoteInitializer, | |
Product $productModel | |
) { | |
$this->_subscriptionsCollectionFactory = $subscriptionsCollectionFactory; | |
$this->_dateTime = $dateTime; | |
$this->_logger = $logger; | |
$this->_orderRepository = $orderRepository; | |
$this->_quoteFactory = $quoteFactory; | |
$this->_quoteManagement = $quoteManagement; | |
$this->_store = $store; | |
$this->_customerFactory = $customerFactory; | |
$this->_customerInterface = $customerInterface; | |
$this->_productRepository = $productRepository; | |
$this->_subscriptionsOrdersRepository = $subscriptionsOrdersRepository; | |
$this->_subscriptionsOrdersFactory = $subscriptionsOrdersFactory; | |
$this->_quoteInitializer = $quoteInitializer; | |
$this->_productModel = $productModel; | |
} | |
public function releaseSubscriptions() | |
{ | |
$subscriptionsReleased = array(); | |
$releasableSubscriptions = $this->getReleasableSubscriptions(); | |
// If there is nothing to release - stop right here | |
if (!$releasableSubscriptions) { | |
$this->_logger->info('Nothing to release'); | |
return $this; | |
} | |
/** | |
* @var $subscription Subscriptions | |
*/ | |
foreach ($releasableSubscriptions as $subscription) { | |
// Check if this subscription is due for release | |
$isDue = $this->getIsDue($subscription); | |
if ($isDue) { | |
// Begin release process for subscription | |
$order = $this->releaseSubscription($subscription); | |
if ($order && $order instanceof \Magento\Sales\Model\Order) { | |
// Update released array for logging | |
$subscriptionsReleased[] = $subscription->getId(); | |
// Update subscription date: current_cycle, last_cycle_date etc. | |
$subscription->updateSubscription($order->getCreatedAt()); | |
// Set data on order for collection loading etc | |
$order->setHasSubscription(1); | |
$order->setParentSubscription($subscription->getId()); | |
// Save the order so above gets set properly - Could be an observer to prevent double saving | |
$order->save(); | |
// Add an entry to subscriptions orders table | |
$subscriptionOrder = $this->_subscriptionsOrdersFactory->create(); | |
$subscriptionOrder->setData( | |
[ | |
'order_id' => $order->getId(), | |
'subscription_id' => $subscription->getId(), | |
'cycle' => $subscription->getCurrentCycle(), | |
'created_at' => $order->getCreatedAt() | |
] | |
); | |
$this->_subscriptionsOrdersRepository->save($subscriptionOrder); | |
} | |
} | |
} | |
if ($subscriptionsReleased) { | |
$this->_logger->info( | |
'Subscriptions released', $subscriptionsReleased | |
); | |
} else { | |
$this->_logger->info('No subscriptions released'); | |
} | |
return $this; | |
} | |
public function releaseSubscription($subscription) | |
{ | |
/** | |
* @var $subscription Subscriptions | |
*/ | |
$this->_logger->info( | |
'Beginning release of subscription', | |
array('subscription_id' => $subscription->getId()) | |
); | |
// Load the subscriptions original order | |
$originalOrder = $this->getOriginalOrder($subscription); | |
// Get the order item for this particular subscription | |
$originalOrderItemId = $subscription->getOriginalOrderItemId(); | |
// Check we have everything we need | |
if ($originalOrder && $originalOrderItemId) { | |
// Begin releasing subscription | |
$newOrder = $this->createOrder( $subscription, $originalOrder, $originalOrderItemId ); | |
//die('test'); | |
if ($newOrder) { | |
return $newOrder; | |
} else { | |
// we were unable to create the order do something | |
} | |
} | |
return false; | |
} | |
public function createOrder(Subscriptions $subscription, $originalOrder, $originalOrderItemId) | |
{ | |
/** | |
* @var $originalOrder \Magento\Sales\Model\Order | |
* @var $quote \Magento\Quote\Model\Quote | |
* @var $customer | |
*/ | |
// Firstly make sure we can load the customer | |
$customerId = $originalOrder->getCustomerId(); | |
//$customer = $this->_customerFactory->create()->load($customerId); | |
$customer = $this->_customerInterface->getById($customerId); | |
if ($customer->getEmail()) { | |
// Get some data from the original order that we are going to need | |
// @todo : load isn't deprecated it might be later | |
$originalStoreId = $originalOrder->getStoreId(); | |
$store = $this->_store->load($originalStoreId); | |
// Create a blank quote | |
$quote = $this->_quoteFactory->create()->setStoreId($originalStoreId); | |
// Assign the customer to the quote | |
$quote->assignCustomer($customer); | |
//@todo : fix error with below - must implement interface Magento\Quote\Api\Data\CurrencyInterface | |
//$currency = $originalOrder->getBaseCurrencyCode(); | |
//$quote->setCurrency($currency); | |
//temporary - currency needs to be the purchased currency | |
$quote->setCurrency(); | |
// Build Billing Address | |
$billingAddress = clone $originalOrder->getBillingAddress(); | |
$billingAddress->unsetData('entity_id')->unsetData('parent_id') | |
->unsetData('customer_address_id')->unsetData('customer_id') | |
->unsetData('quote_address_id'); | |
// Build Shipping Address | |
// @todo : Customers are going to need the ability to change their shipping address come back and fix this once that's done | |
$shippingAddress = clone $originalOrder->getShippingAddress(); | |
$shippingAddress->unsetData('entity_id'); | |
// Insert the address details | |
$quote->getBillingAddress()->addData($billingAddress->getData()); | |
$quote->getShippingAddress()->addData($shippingAddress->getData()); | |
/** | |
* @var $subscriptionOrderItem \Magento\Sales\Model\Order\Item | |
*/ | |
// Load the subscription product from original order | |
$subscriptionOrderItem = $originalOrder->getItemById( | |
$originalOrderItemId | |
); | |
// Make sure we have a product | |
if ($subscriptionOrderItem instanceof SalesOrderItem) { | |
// Get params required to add product | |
$params = $this->getProductParams($subscriptionOrderItem); | |
// Load the product | |
$product = $this->getSubscriptionProduct( | |
$subscriptionOrderItem, $originalStoreId | |
); | |
// Product may have been deleted | |
if (!$product) { | |
$message = sprintf( | |
__('Could not load product id %s for subscription %s'), | |
$subscription->getProductId(), $subscription->getId() | |
); | |
$this->_logger->critical($message); | |
throw new LocalizedException($message); | |
} | |
//$quote->addProduct($product, $params); | |
$this->_quoteInitializer->init($quote, $product, $params); | |
// Set the product price based on original order incase product price changed | |
try { | |
$this->setProductPrices( | |
$quote, $product, $subscription->getProductCost() | |
); | |
} catch (LocalizedException $e) { | |
$message = __( | |
'Unable set custom price - cannot release subscription' | |
); | |
$this->_logger->critical($message); | |
throw new LocalizedException($message, $e); | |
} | |
$quote->setTotalsCollectedFlag(false)->collectTotals(); | |
} else { | |
$message = __( | |
'Could not load original order item for subscription : ' | |
) . $subscription->getId(); | |
$this->_logger->critical($message); | |
throw new LocalizedException(__($message)); | |
} | |
// Collect shipping rates | |
$quote->getShippingAddress()->setCollectShippingRates(true) | |
->collectShippingRates(); | |
// Apply the correct shipping rate to this order | |
$this->getShippingRate($quote, $originalOrder); | |
// Set payment method - work still to be done here | |
$quote->setPaymentMethod($this->_getPaymentMethod()); | |
// Create the quote | |
$quote->save(); | |
// Import payment data - Don't understand this yet | |
$quote->getPayment()->importData( | |
array('method' => $this->_getPaymentMethod()) | |
); | |
// Collect quote totals | |
$quote->collectTotals()->save(); | |
/** | |
* @var $order \Magento\Sales\Model\Order | |
*/ | |
// Convert the quote to an order | |
$order = $this->_quoteManagement->submit($quote); | |
// Prevent default order confirmation, we will make our own | |
$order->setEmailSent(0); | |
// Make sure the order has been created properly | |
if (!$order->getRealOrderId()) { | |
$order = false; | |
} | |
$this->_logger->info( | |
sprintf( | |
__('Order %s succesfully created for subscription %s'), | |
$order->getRealOrderId(), $subscription->getId() | |
) | |
); | |
return $order; | |
} else { | |
$message = __('The customer no longer exists for subscription: ') | |
. $subscription->getId(); | |
$this->_logger->critical($message); | |
throw new LocalizedException(__($message)); | |
} | |
} | |
/** | |
* Check elapsed days since last release | |
* | |
* @todo : Figure out how to do php diff on time - magento2 makes it hard | |
* | |
* @param Subscriptions $subscription | |
* | |
* @return bool | |
*/ | |
public function getIsDue(Subscriptions $subscription) | |
{ | |
$lastCycle = $subscription->getLastCycleDate(); | |
$frequency = $subscription->getCycleFrequency(); | |
// Convert lastCycle date/time to just date - We only care how many days have passed | |
// Convert both to Unix Timestamp easier to do maths | |
$lastCycle = strtotime( | |
$this->_dateTime->create()->date('Y-m-d', $lastCycle) | |
); | |
$today = strtotime($this->_dateTime->create()->date('Y-m-d')); | |
$diff = $today - $lastCycle; | |
$daysMath = 60 * 60 * 24; | |
$daysSinceLastRelease = $diff / $daysMath; | |
// Check if elapsed days is greater than or equal to the subscriptions frequency | |
if ($daysSinceLastRelease >= $frequency) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* @return CollectionFactory | |
*/ | |
public function getReleasableSubscriptions() | |
{ | |
/** | |
* @var $subscriptions \Vendor\Namspace\Model\ResourceModel\Subscriptions\CollectionFactory | |
*/ | |
$subscriptions = $this->_subscriptionsCollectionFactory | |
->create() | |
->addReleasableFilter(); | |
return $subscriptions; | |
} | |
/** | |
* @param SalesOrderItem $subscriptionOrderItem | |
* | |
* @return DataObject | |
*/ | |
public function getProductParams(SalesOrderItem $subscriptionOrderItem) | |
{ | |
// Get Buy request | |
$originalBuyRequest = $subscriptionOrderItem->getBuyRequest(); | |
// Create a new Varien Object | |
$params = new \Magento\Framework\DataObject(); | |
// Set the product ID based on previous order | |
$params->setData('product', $originalBuyRequest->getProduct()); | |
if ($subscriptionOrderItem->getProductType() == 'configurable') { | |
// Add super attribut if product is configurable | |
$params->setData( | |
'super_attribute', $originalBuyRequest->getSuperAttribute() | |
); | |
} | |
// Set quantity to that or original order | |
$params->setData('qty', $originalBuyRequest->getQty()); | |
return $params; | |
} | |
/** | |
* Load the full Subscription product. | |
* | |
* @param SalesOrderItem $subscriptionOrderItem | |
* @param null $originalStoreId | |
* | |
* @return \Magento\Catalog\Api\Data\ProductInterface | |
*/ | |
public function getSubscriptionProduct(SalesOrderItem $subscriptionOrderItem, | |
$originalStoreId = null | |
) { | |
//@todo check product stock before releasing if out of stock change status | |
// Get Product ID from Original Order | |
$productId = $subscriptionOrderItem->getProductId(); | |
// Load Product with original order store id | |
//$product = $this->_productRepository->getById($productId, false, $originalStoreId); | |
$product = $this->_productModel->load($productId); | |
return $product; | |
} | |
/** | |
* Get the order with which the original subsription was purchased | |
* | |
* @param null|\Vendor\Namspace\Model\Subscriptions $subscription | |
* | |
* @return bool|\Magento\Sales\Api\Data\OrderInterface | |
*/ | |
public function getOriginalOrder(Subscriptions $subscription = null) | |
{ | |
/** | |
* @var $subscription \Vendor\Namspace\Model\Subscriptions | |
* @var $order \Magento\Sales\Model\Order | |
*/ | |
$order = false; | |
$originalOrderId = $subscription->getOriginalOrderId(); | |
try { | |
$order = $this->_orderRepository->get($originalOrderId); | |
} catch (\Exception $e) { | |
//This subscription can't be release | |
//@todo set status to on hold and do something about it. | |
//Area code not set: Area code must be set before starting a session. | |
$this->_logger->error( | |
__('could not load order'), | |
array( | |
'subscription_id' => $subscription->getId(), | |
'original_order_id' => $subscription->getOriginalOrderId() | |
), | |
$e->getMessage() | |
); | |
} | |
return $order; | |
} | |
/** | |
* Product prices can change - this ensure that the customer always gets charged the same | |
* | |
* @param Quote $quote | |
* @param $addedProduct | |
* @param $productPrice | |
* | |
* @return $this | |
*/ | |
public function setProductPrices(Quote $quote, $addedProduct, $productPrice) | |
{ | |
/** | |
* @var $item \Magento\Quote\Model\Quote\Item | |
* @var $addedProduct \Magento\Catalog\Model\Product | |
*/ | |
// Get the type id of the product being added to the subscription | |
$addedProductType = $addedProduct->getTypeId(); | |
$addedProductPrice = $addedProduct->getPrice(); | |
$items = $quote->getAllItems(); | |
// If the product added is a simple - set the price on it. | |
if ($addedProductType == 'simple') { | |
foreach ($items as $item) { | |
// Check if price is different from that stored against subscription | |
if ($productPrice != $item->getPrice()) { | |
// If they aren't the same set a custom pric | |
$item->setCustomPrice($productPrice); | |
$item->setOriginalCustomPrice($productPrice); | |
$item->getProduct()->setIsSuperMode(true); | |
} | |
} | |
//If the product added is configurable - find its simple item and update the price | |
} elseif ($addedProductType == 'configurable') { | |
foreach ($items as $item) { | |
// Find the simple product option | |
if ($parent = $item->getParentItem()) { | |
// Check the price against that stored against the subscription | |
if ($productPrice != $item->getProduct()->getPrice()) { | |
// If they aren't the same set a custom price | |
$parent->setCustomPrice($productPrice); | |
$parent->setOriginalCustomPrice($productPrice); | |
$parent->getProduct()->setIsSuperMode(true); | |
} | |
} | |
} | |
} | |
return $this; | |
} | |
protected function _getPaymentMethod() | |
{ | |
return 'checkmo'; | |
} | |
/** | |
* Get shipping rate of original order if exists. Else get the next best. | |
* | |
* @param $quote | |
* @param $originalOrder | |
*/ | |
public function getShippingRate($quote, $originalOrder) | |
{ | |
/** | |
* @var $quote \Magento\Quote\Model\Quote | |
*/ | |
if ($quote->getShippingAddress()->getShippingRateByCode( | |
$originalOrder->getShippingMethod() | |
) | |
) { | |
$quote->getShippingAddress()->setShippingMethod( | |
$originalOrder->getShippingMethod() | |
); | |
} else { | |
// Otherwise select a shipping method for them | |
$shippingRates = $quote->getShippingAddress() | |
->getShippingRatesCollection(); | |
if ($shippingRates->count() == 0) { | |
// throw an exception for no shipping rates | |
} else { | |
$cheapestRate = $shippingRates->getFirstItem(); | |
} | |
// If there are multiple shipping rates available find the cheapest | |
if ($shippingRates->count() > 1) { | |
/* @var $rate \Magento\Quote\Model\Quote\Address\Rate */ | |
foreach ( | |
$quote->getShippingAddress()->getShippingRatesCollection() | |
as $rate | |
) { | |
if ($rate->getPrice() < $cheapestRate->getPrice()) { | |
$cheapestRate = $rate; | |
} | |
} | |
} | |
$quote->getShippingAddress()->setShippingMethod( | |
$cheapestRate->getCode() | |
); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment