Skip to content

Instantly share code, notes, and snippets.

@TheFrankman
Last active August 16, 2017 17:22
Show Gist options
  • Save TheFrankman/21fdf70883d4e2d717333ac67f1e0f2d to your computer and use it in GitHub Desktop.
Save TheFrankman/21fdf70883d4e2d717333ac67f1e0f2d to your computer and use it in GitHub Desktop.
Programatically Create Order
<?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