Magento 2: Create Invoice Programmatically From Order Observer - php

I'm testing on Magento 2.2.3 and I've created an observer for the event sales_order_save_after which I'm using to automatically create an invoice.
Here is the current error that I'm receiving after placing an order:
Order saving error: Rolled back transaction has not been completed correctly.
And my MyCompany/MyModule/Observer/SalesOrderSaveAfter.php
<?php
namespace MyCompany\MyModule\Observer;
use Magento\Framework\Event\ObserverInterface;
class SalesOrderSaveAfter implements ObserverInterface
{
protected $_invoiceService;
protected $_transactionFactory;
public function __construct(
\Magento\Sales\Model\Service\InvoiceService $invoiceService,
\Magento\Framework\DB\TransactionFactory $transactionFactory
) {
$this->_invoiceService = $invoiceService;
$this->_transactionFactory = $transactionFactory;
}
public function execute(\Magento\Framework\Event\Observer $observer)
{
$order = $observer->getEvent()->getOrder();
try {
if(!$order->canInvoice()) {
return null;
}
if(!$order->getState() == 'new') {
return null;
}
$invoice = $this->_invoiceService->prepareInvoice($order);
$invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_ONLINE);
$invoice->register();
$transaction = $this->_transactionFactory->create()
->addObject($invoice)
->addObject($invoice->getOrder());
$transaction->save();
} catch (\Exception $e) {
$order->addStatusHistoryComment('Exception message: '.$e->getMessage(), false);
$order->save();
return null;
}
}
}
If I remove the transaction portion of the code, eg:
$transaction = $this->_transactionFactory->create()
->addObject($invoice)
->addObject($invoice->getOrder());
$transaction->save();
then the order will pass through with the products marked as invoiced, but no invoice is actually created or saved to the order.
Any ideas what I could be missing?

https://magento.stackexchange.com/questions/217045/magento-2-how-to-automatically-create-invoice-from-order-observer
The answer to this is that I was using the wrong event. With the event sales_order_save_after the order hasn't been committed to the Database yet.
I changed my event to fire on checkout_submit_all_after and my observer is now working.

Related

Yii2 how to resave the modal in afterSave

I have a model ProductOffer inside of it I use afterSave to generate the coupon.
Right now the status is null and in aftersave I want to update it.
public function afterSave($insert, $changedAttributes) {
if (floatval($this->offer) >= floatval($this->product->threshold_price)) {
$coupon = false;
$createCoupon = "";
$ctr = 1;
while ($coupon == false) {
$createCoupon = $this->createCoupon(
"Offer for " . $this->customer_name . ' #' . $this->id,
$this->product->sale_price - $this->offer,
$this->product_id
);
if ($createCoupon || $ctr > 3) {
$coupon = true;
}
$ctr++;
}
$this->status = self::STATUS_ACCEPTED_COUPON_GENERATED;
$this->coupon_code = $createCoupon->code;
// todo this
// echo "Accepted automatically then send email to customer as the same time to merchant email";
} else {
$this->status = self::STATUS_REJECTED;
}
return parent::afterSave($insert, $changedAttributes);
}
So here at afterSave I want to update the status of record and save the coupon code.
What I wan't to do is simply like this.
public function afterSave($insert, $changedAttributes) {
// So basically I want to update the status in afterSave
$this->status = "What ever value rejected or accepted it depends of the outcome of generating coupon";
$this->coupon = "AddTheCoupon";
// Save or Update
$this->save();
return parent::afterSave($insert, $changedAttributes);
}
But It seems not working for me and if you going to analyze it, it seems to do endless updating of the data since every save() it will pass through the afterSave().
Is there other way to do it?
Thanks!
You should use the updateAttributes method, which skips all the events.
See reference updateAttributes(['some_field']).
/** After record is saved
*/
public function afterSave($insert, $changedAttributes)
{
parent::afterSave($insert, $changedAttributes);
$this->some_field = 'new_value';
$this->updateAttributes(['some_field']);
}

Eloquent Model Saves in DB but not found in code

Thanks in advance for any help. I have an Invoice Model which has a one-to-many relationship with the payment model and when I loop through an invoice's payments to add all the $payment->net and subtract it from the $invoice->cost to see the balance that is left. The payment that was just made in the same call doesn't appear in $invoice->payments, it feels like its cached.
Invoice.php
public function payments()
{
return $this->hasMany('App\Payment');
}
public function net() :float
{
$payments = $this->payments;
$net = $this->cost;
foreach ($payments as $payment) {
$net += $payment->net;
}
return $net;
}
public function balance() :float
{
return ($this->cost - $this->net());
}
Payment.php
public function invoice()
{
return $this->belongsTo('App\Invoice');
}
PaymentController.php
$invoice = Invoice::findOrFail($id);
// Check ownership
if(!$this->getCurrentUser()->isSuperuser() && $this->getCurrentUser()->id === $invoice->user_id) {
throw new ModelNotFoundException();
}
$payment = new Payment($request->all());
$payment->ref = Payment::generateRef($invoice->id, $request->input('type'));
// Check for overpayment
if($invoice->balance() < $payment->net) {
throw new BadInputException('Payment exceeds balance.');
}
if($payment = $invoice->payments()->save($payment)) {
if($invoice->balance() == 0) {
$invoice->status = Invoice::CLOSED;
$invoice->save();
}
}
return $payment;
This is not the solution I was looking for but I don't like to expend too much time in little problems like this. I'll try to understand why is the model caching later.
The first time I run $invoice->balance() the value gets cached and the function won't take into consideration the new payment made when evaluating the new balance.
so the next time I ran $invoice->balance() i just manually subtract the new net payment.
if($invoice->balance() - $payment->net == 0) {
$invoice->status = Invoice::CLOSED;
$invoice->save();
}

How to add custom data in cart in magento

I want to save costume data in cart item, I have check data has been save in data base but when I am getting then it will return null.
I have add event for add costume data into cart.
Observer.php
public function checkoutCartProductAddAfter(Varien_Event_Observer $observer){
try {
$data = $this->_getRequest()->getPost();
$item = $observer->getEvent()->getQuoteItem();
$item->setData('customize_data', $data['customize_data']);
$item->setData('customize_image', $data['customize_image']);
$item->save();
}
catch (Exception $e) {
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
}
}
I want to change image in cart page so I have create below file.
<?php
class ProductCustomizer_ProductCustomizer_Block_Checkout_Cart_Item_Renderer extends Mage_Checkout_Block_Cart_Item_Renderer{
public function getProductThumbnail()
{
$customize_data = $this->getItem()->getData('customize_data');
$customize_image = $this->getItem()->getData('customize_image');
Mage::log('customize_data');
Mage::log($customize_data);
Mage::log('customize_image');
Mage::log($customize_image);
if (!empty($customize_image)) {
return $customize_image;
} else {
return parent::getProductThumbnail();
}
}
}
I am getting below logs in system.log file
2017-01-02T06:38:29+00:00 DEBUG (7): customize_data
2017-01-02T06:38:29+00:00 DEBUG (7):
2017-01-02T06:38:29+00:00 DEBUG (7): customize_image
2017-01-02T06:38:29+00:00 DEBUG (7):
You can do this thing without adding a new column in item table,
Observer.php
public function checkoutCartProductAddAfter(Varien_Event_Observer $observer){
try {
$data = Mage::app()->getRequest()->getPost();
$item = $observer->getQuoteItem();
$additional_info = $item->getadditional_info();
$additional_info = unserialize($additional_info);
$additional_info['customize']['customize_data'] = $data['customize_data'];
$additional_info['customize']['customize_image'] = $data['customize_image'];
$item->setAdditionalInfo(serialize($additional_info));
$item->save();
}
catch (Exception $e) {
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
}
}
CART Page :
class ProductCustomizer_ProductCustomizer_Block_Checkout_Cart_Item_Renderer extends Mage_Checkout_Block_Cart_Item_Renderer{
public function getProductThumbnail()
{
$additional_info = $this->getItem()->getData('additional_info');
$additional_info = unserialize($additional_info);
if(isset($additional_info['customize']) && $additional_info['customize']){
Mage::log('customize_data');
Mage::log($additional_info['customize']['customize_data']);
Mage::log('customize_image');
Mage::log($additional_info['customize']['customize_image']);
return $additional_info['customize']['customize_image'];
}
return parent::getProductThumbnail();
}
}

Create an Event Listener to update another database table

this is my first question so please bear with me.
How can I implement a postPersist Event Listener to update a log table when creating or updating an order in the order table using Sonata.
I understand how to use a prePersist to add information to the same database table as soon as I create a new order. (See the following code snippet)
public function prePersist(LifecycleEventArgs $args)
{
$order = $args->getEntity();
if ($order instanceof PmodOrder) {
$user = $this->serviceContainer->get('security.token_storage')->getToken()->getUser();
if ($user) {
$order->setCreatedBy($user);
$order->setCreatedAt(new \DateTime(date('Y-m-d H:i:s')));
}
}
}
But I don't fully understand how I would do this when updating another table, because it is not the same entity.
The moment an order is created, (I think) a postPersist should update another table with that order's ID and some extra information.
I think something between the lines like this;
public function postPersist(LifecycleEventArgs $args)
{
$log = $args->getEntity();
if ($log instanceof PmodLog) {
$order = ....;
$user = $this->serviceContainer->get('security.token_storage')->getToken()->getUser();
$department = $this->serviceContainer->get('security.token_storage')->getToken()->getUser()->getDepartment();
if ($order) {
$log->setOrder($order);
$log->setCreatedBy($user);
$log->setCreatedAt(new \DateTime(date('Y-m-d H:i:s')));
$log->setDepartment($department);
$log->setAction("created");
}
}
}
I don't get how to get the current order I'm busy with. And how the setAction will be something else when the user modified the order. For example 'edited' or 'approved'. I've been trough the documentation of Sonata with no luck unless I miss read something.
Remember I use Sonata, otherwise this would've been easy to implement in my own Controller Actions.
You can directly add to your entity a listener that create/update your order's logs.
First you create the listener class :
use Doctrine\ORM\Event\LifecycleEventArgs;
class OrderListener
{
public function postPersist(Order $order, LifecycleEventArgs $event)
{
// for example
// if you want to store the date creation :
if($order->getId() == null)
{
$order->setDateCreate(new \DateTime('now'));
}
// if you want to store the last update date :
$order->setDateUpdate(new \DateTime('now'));
//... or whatever you want to store...
}
}
Then register it in a service.yml :
order_listener:
class: YOUR\NAMESPACE\OrderListener
tags:
- { name: doctrine.orm.entity_listener }
Finally, link your entity to the listener (here with annotations) :
/**
* #ORM\EntityListener("YOUR\NAMESPACE\OrderListener")
*/
class Order
{
...
}

How do i make auto shipping programatically in magento?

I tried with following observer code.
...
public function automaticallyInvoiceShipCompleteOrder($observer)
{
$order = $observer->getEvent()->getOrder();
$orders = Mage::getModel('sales/order_invoice')->getCollection()
->addAttributeToFilter('order_id', array('eq'=>$order->getId()));
$orders->getSelect()->limit(1);
if ((int)$orders->count() !== 0) {
return $this;
}
try {
if($order->canShip())
{
$itemQty = $order->getItemsCollection()->count();
$items[] = $itemQty;
// This first definition and 2nd look overlapping, our one is obsolete?
$shipment = Mage::getModel('sales/service_order', $order)->prepareShipment($itemQty);
$ship = new Mage_Sales_Model_Order_Shipment_Api();
$shipmentId = $ship->create($order->getId(), $items, 'Shipment created through ShipMailInvoice', true, true);
//getting Error here
}
}
} catch (Exception $e) {
$order->addStatusHistoryComment(' Exception occurred during automaticallyInvoiceShipCompleteOrder action. Exception message: '.$e->getMessage(), false);
$order->save();
}
return $this;
}
.....
When i place the order, i can capture the order success event using observer. Finally getting "Fatal error: Maximum function nesting level of '100' reached, aborting!" in ajax call itself.
I could not found the solution. Kindly give some advice on this
Each time your order is saved, this observer method is called, which again saves your order due to some error in try block. That's the reason I think it will endlessly execute and after 100th time Fatal error will be thrown.
In your try block's $ship->create(), you need to pass Order Increment ID and not Order Entity ID.
I tried with below code,
public function automaticallyInvoiceShipCompleteOrder($observer)
{
//$order = $observer->getEvent()->getOrder();
$incrementid = $observer->getEvent()->getOrder()->getIncrementId();
$order = Mage::getModel('sales/order')->loadByIncrementId($incrementid);
try {
// Is the order shipable?
if($order->canShip())
{
$shipmentid = Mage::getModel('sales/order_shipment_api')->create($order->getIncrementId(), array());
}
//END Handle Shipment
} catch (Exception $e) {
$order->addStatusHistoryComment(' Exception occurred during automaticallyInvoiceShipCompleteOrder action. Exception message: '.$e->getMessage(), false);
}
return $this;
}
Shipment Created now...

Categories