I have working on magento 1.6.1.0 version. I have not found any event to call after shipping generate or after order status completed.
Then i call our module observer when order status is completed.
After order status complete, i want to update a customer attribute value.
please give me answer of this problem.
I have search and do various things but they are not useful.
The first place to start would be the sales_order_save_after event. This certainly will work, but will be called any time the order is updated and saved. Therefore the logic must consider when the order is newly created & complete straightaway, or when the order is marked as complete later on (the latter being the most common). You may need to adjust logic and acceptable end-state values for orders based on cancellations, multiple orders, etc.
/**
* Update customer attribute when order is completed.
*
* Need to catch two conditions:
* 1) Order is new AND `status` = complete
* 2) Order exists but `status` is changed to complete
*
* #param $obs Varien_Event_Observer
*/
public function adjustCustomerAfterComplete($obs)
{
/* #var $order Mage_Sales_Model_Order */
$order = $obs->getOrder();
if ($order->getStatus() === $order::STATE_COMPLETE
&& $order->getOrigData('status' !== $order::STATE_COMPLETE))
{
Mage::getModel('customer/customer')
->load($order->getCustomerId())
->setCustomAttr('new val') //custom attr code
->save();
//Another approach if you don't need events, etc.:
/*
$obj = new Varien_Object(
array(
'entity_id'=>$order->getCustomerId(),
'custom_attr'=>'new val'
)
);
Mage::getResourceModel('customer/customer')
->saveAttribute($obj,'custom_attr');
*/
}
}
Related
I'm banging my head to find a solution to this but still i'm unable, I'm looking for a design vice solution not a hack to fix the issue.
I have following classes
class CourseService{
public function getCourse($courceId){
$course = $this->courseRepo->getCourse($courseId);
$restrictions = $this->invoiceService->getRestrictions($course->courseid);
$course->restrictions = [];
if($restrictions != null){
$course->restrictions = $restrictions;
}
}
}
Now this course service is injected in the constructor of the StudentService because when students need to enroll to a cource i use this course service there.
also you can see that I have used CourseRepo to get Course object and then InvoiceService to say which fields are restricted to update, basically restrictions attributes gives an array of strings defining which fields are not allowed to edit and I expect UI developer will use it to disable those fields, and I had to inject InvoiceService because there are some processing to do to the raw db records that are fetched from the InvoiceRepo so invoice repo is encapsulated in the invoiceService
now lets look at the InvoiceService
Class InvoiceService{
public function getAmountToPay($courseid, $studentid){
//now I need to inject StduentService inorder to get student info which needed for the calculation
}
}
but I can't inject StudentService into here because StudentService -> CourceService -> InvoiceService
Options I see and the consequences
One option I see is to get rid of InvoiceService from the CourseService and use InvoiceService in the place where the getCourse() get called and then modify the result but the problem is, CourseService is used mainly in controllers and next thing is that getCourse() get called from many controllers and service and expects the restrictions to be there so if I want to get rid of the InvoiceService then I'll have many places to add the removing lines and it crates a code repetition.
I can move getAmountToPay() to student service but then that service has already doing many student related tasks and i'm happy to extract just the invoice part to another service so I have a clear place to look when I need to check for bugs on invoices.
Student service:
First of all you have to see - actually to decide - that a student service uses an invoice service, not the reciprocal. When I enroll myself as a history student, I go to the registration/students office first. They are calling the financial/invoice office to ask about how much should I pay. The financial office checks in the database and returns the response regarding the amount to be payed by me.
Course service:
...The time passed by. Now I'm a student. I don't need to go to the registration office anymore. If I have to know something about my courses I go to the secretariat/course service. They'll give me all the informations I need about my courses. But, if I want to visit some special archeology course, where one must pay something, the course service will call the financial/invoice service to ask about that for me. They, in turn, will return the infos. The same applies if the course service wants to know about some financial restrictions I should have: they call the financial/invoice service.
Invoice service - student service, invoice service - course service:
Now, what should happen, if the invoice service needs infos about a student or a course? Should it call the student service, or the course service for that? The answer is no. The invoice service should receive a student id, a course id, a domain object Student, or a domain object Course as constructor/methods dependencies, but not the corresponding service(s). And it will fetch the infos it needs by itself. More of it, the invoice service should work with its specific invoice/financial tables, not with the course tables or the student details tables (except their id's).
Conclusions:
To enroll a student is the job of the StudentService. Though the
CourseService can assist the enrollment process.
StudentService verifies the amount to be paid by a student by calling
the InvoiceService. I know you don't want to have getAmountToPay()
inside the StudentService, but it's a natural workflow. You may think
of separate the other many things, for which the StudentService is
responsible, to another services.
The CourseService is responsible for finding a course, together with
the course restrictions, for which it calls the InvoiceService. So,
the CourseService will be assisted by the InvoiceService.
Down under I passed you the PHP version of my vision. I renamed some functions, to give you a better perspective.
Good luck!
P.S: I hope I understood right, that the sense of "invoice sevice" is a "financial department" one. Sorry, but I'm not a native english speaker, so I can't know all the senses.
<?php
class StudentService {
protected $courseService;
protected $invoiceService;
/**
* Even if the course service uses the invoice service,
* doesn't mean that the student service shouldn't use it too.
*
* #param CourseService $courseService
* #param InvoiceService $invoiceService
*/
public function __construct(CourseService $courseService, InvoiceService $invoiceService) {
$this->courseService = $courseService;
$this->invoiceService = $invoiceService;
}
/**
* Enroll a student to a course.
*
* #param integer $studentId
* #param integer $courseId
* #return bool Enrolled or not.
*/
public function enrollToCourse($studentId, $courseId) {
//... Use here the CourseService too - for what you said regarding the enrollment.
$enrolled = $this->studentRepo->enrollToCourse($studentId, $courseId);
return $enrolled;
}
/**
* Get the amount to be payed by a student on the enrollment moment.
*
* #param integer $studentId
* #param integer $courseid
* #return integer Amount to be payed.
*/
public function getAmountToPayOnEnrollment($studentId, $courseid) {
$amount = $this->invoiceService->getAmountToPayOnEnrollment($studentId, $courseid);
return $amount;
}
}
class CourseService {
protected $invoiceService;
/**
* Invoice service is used to get the (financial) restrictions for a course.
*
* #param InvoiceService $invoiceService
*/
public function __construct(InvoiceService $invoiceService) {
$this->invoiceService = $invoiceService;
}
/**
* Get a course and its corresponding (financial) restrictions list.
*
* #param integer $courseId
* #return Course Course domain object.
*/
public function getCourse($courseId) {
$course = $this->courseRepo->getCourse($courseId);
$course->restrictions = $this->getRestrictionsForCourse($course->courseId);
return $course;
}
/**
* Get the (financial) restrictions for a specified course.
*
* #param integer $courseId
* #return array Restrictions list.
*/
public function getRestrictionsForCourse($courseId) {
$restrictions = $this->invoiceService->getRestrictionsForCourse($courseId);
return $restrictions;
}
}
Class InvoiceService {
/**
* No student service needed!
*/
public function __construct() {
//...
}
/**
* Again, no student service needed: the invoice service
* fetches by itself the needed infos from the database.
*
* Get the amount to be payed by a student on the enrollment moment.
*
* #param integer $studentId
* #param integer $courseid
* #return integer Amount to be payed.
*/
public function getAmountToPayOnEnrollment($studentId, $courseid) {
$amount = $this->invoiceRepo->getAmountToPayOnEnrollment($studentId, $courseid);
return $amount;
}
/**
* Get the (financial) restrictions for a course.
*
* #param integer $studentId
* #param integer $courseid
* #return array Restrictions list.
*/
public function getRestrictionsForCourse($courseid) {
$restrictions = $this->invoiceRepo->getRestrictionsForCourse($courseid);
return isset($restrictions) ? $restrictions : [];
}
/*
* Quote: "Some processing to do to the raw
* db records that are fetched from the InvoiceRepo".
*/
//...
}
I would change your invoiceService to not depend on student service. Pass in what you need to the invoiceService. The logic of what to do with those student details can stay in invoiceService, but the content can be passed in.
I am only a beginner in Magento. I need your help to solve this issue.
My Magento store is automatically generating invoice mail after the payment. The payment is made through PayPal (standard). Also the order status is changed to 'complete' not 'pending'. So I am not able to generate invoice manually. I need to generate the invoice manually from the admin side ,only after viewing the product orders. The order status should be 'complete' only after the manual invoice generation. Can any one please help me to solve this issue.
Thanks in advance
It's pretty simple. Just go to System->Configuration->Sales Emails and under the tab "Invoice" set Enable to NO.
Cheers,
If you wanted to disable auto invoice invoice programatically then plese follow below steps
In your custom module create di.xml file and add preference
<preference for="Magento\Sales\Model\Order\Payment\Processor" type="Vendor\Module\Model\Express\Processor"/>
Create new file at Vendor\Module\Model\Express\ with Processor.php and add below code:
<?php
declare(strict_types = 1);
namespace Vendor\Module\Model\Express;
use Magento\Sales\Api\Data\OrderPaymentInterface;
class Processor extends \Magento\Sales\Model\Order\Payment\Processor
{
const PAYMENT_METHOD_PAYPAL_EXPRESS = "paypal_express";
/**
* Process capture operation
*
* #param OrderPaymentInterface $payment
* #param InvoiceInterface $invoice
* #return OrderPaymentInterface|Payment
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function capture(OrderPaymentInterface $payment, $invoice)
{
if ($payment->getMethodInstance()
->getCode() != self::PAYMENT_METHOD_PAYPAL_EXPRESS)
{
return $this
->captureOperation
->capture($payment, $invoice);
}
}
/**
* Registers capture notification.
*
* #param OrderPaymentInterface $payment
* #param string|float $amount
* #param bool|int $skipFraudDetection
* #return OrderPaymentInterface
*/
public function registerCaptureNotification(OrderPaymentInterface $payment, $amount, $skipFraudDetection = false)
{
if ($payment->getMethodInstance()
->getCode() != self::PAYMENT_METHOD_PAYPAL_EXPRESS)
{
return $this
->registerCaptureNotification
->registerCaptureNotification($payment, $amount, $skipFraudDetection);
}
}
}
Note: It's for Magento 2X
Go to Store->Settings->Configuration->Sales->Sales Emails and under the tab "Invoice" set Enable to NO.
Happy coding :)
Use this:
http://www.magentocommerce.com/magento-connect/8870.html
I've used in few projects and it works nice.
I am new to Magento. I want to build an observer which on cancellation of an order will perform a query to my database and will decide whether the order is cancellable or not (This is decided on the basis of a certain state.). If it can't be cancelled, then it should break the cancel event and display a message that the order cannot be cancelled.
Which event I should choose, order_cancel_after or sales_order_item_cancel, and how can I break out of this event in between?
Thanks in advance. :)
There is no general answer to this, it depends on the context where the event is triggered and what happens there afterwards.
The events don't have an interface to "stop" them and they are not tied to the actual "event" (i.e. order cancellation) other than by name.
So you will have to look at the code of Mage_Sales_Model_Order_Item where sales_order_item_cancel gets triggered (order_cancel_after is obviously the wrong place to look because at that point the order is already cancelled):
/**
* Cancel order item
*
* #return Mage_Sales_Model_Order_Item
*/
public function cancel()
{
if ($this->getStatusId() !== self::STATUS_CANCELED) {
Mage::dispatchEvent('sales_order_item_cancel', array('item'=>$this));
$this->setQtyCanceled($this->getQtyToCancel());
$this->setTaxCanceled($this->getTaxCanceled() + $this->getBaseTaxAmount() * $this->getQtyCanceled() / $this->getQtyOrdered());
$this->setHiddenTaxCanceled($this->getHiddenTaxCanceled() + $this->getHiddenTaxAmount() * $this->getQtyCanceled() / $this->getQtyOrdered());
}
return $this;
}
You see that there is no additional check after the event was dispatched, but it would be possible to set the qty_to_cancel attributes to 0 to uneffect the cancelling.
Your observer method:
public function salesOrderItemCancel(Varien_Event_Observer $observer)
{
$item = $observer->getEvent()->getItem();
if (!$this->_isCancellable($item->getOrder())) {
$item->setQtyToCancel(0);
$this->_showErrorMessage();
}
}
Note that you don't have to set tax_canceled or hidden_tax_canceled because they depend on qty_canceled and thus will stay 0.
I need to save some cms pages and delete others in a single transaction.
So, how to I make this:
$page1->save();
$page2->delete();
A single transaction? For reference, both $page1 and $page2 come from Mage::getModel('cms/page'). Also, I found an excellent answer here that tells me how to do two saves in a transaction, but not how to do both a save and delete. How can it be done?
If you must do this in a single transaction, just call isDeleted(true) on those items which you wish to be deleted:
//Build out previous items, then for each which should be deleted...
$page2->isDeleted(true);
$transaction = Mage::getModel('core/resource_transaction');
$transaction->addObject($page1)
$transaction->addObject($page2)
//$transaction->addObject(...) etc...
$transaction->save();
Thought I should add an explanation (from Mage_Core_Model_Abstract::save() [link]):
/**
* Save object data
*
* #return Mage_Core_Model_Abstract
*/
public function save()
{
/**
* Direct deleted items to delete method
*/
if ($this->isDeleted()) {
return $this->delete();
}
// ...
}
I'm in the process of developing an extension for Magento 1.5.1.0, which allows me to add catalog price rules to products which quantity in stock is reduced to zero. I have added an attribute to my attribute-set called auto_discount_active. This attribute is my on/off switch which works as condition for my price rule.
I wrote an Observer that reacts on the events sales_order_place_after and catalog_product_save_before. It's task is to check wether to stock quantity of the current product has been changed and set my custom attribute to on or off.
The method which handles the catalog_product_save_before event works fine. After saving an article in the backend, the price rule becomes (in)active like it should. The code looks like following:
class Company_AutoDiscount_Model_Observer
{
public function updateAutoDiscount($observer)
{
/**
* #var Varien_Event
*/
$event = $observer->getEvent();
$product = $event->getProduct();
$data = $product->getStockData();
$discount = $data['qty'] < 1 ? true : false;
$attributes = $product->getAttributes();
$attribute = $attributes["auto_discount_active"];
if ($product->getAutoDiscountAllowed())
{
$product->setAutoDiscountActive($discount);
}
return $this;
}
}
Now I want to do the same thing, if someone places an order in my shop. That for I use the event sales_order_place_after which works so far. But after changing the custom attributes value, the price rules are not updated. My observer method looks like this:
public function updateAutoDiscountAfterOrder($observer)
{
/**
* #var Varien_Event
*/
$event = $observer->getEvent();
$order = $event->getOrder();
foreach ($order->getItemsCollection() as $item)
{
$productId = $item->getProductId();
$productIds[] = $productId;
$product = Mage::getModel('catalog/product')->setStoreId($order->getStoreId())->load($productId);
$data = $product->getStockData();
$discount = $data['qty'] < 1 ? true : false;
if ($product->getAutoDiscountAllowed())
{
$product->setAutoDiscountActive($discount);
$product->save();
}
Mage::getModel('catalogrule/rule')->applyAllRulesToProduct($productId);
}
return $this;
}
After placing an order and saving the bought article manually in the backend without changes, the price rule gets updated. But I have get the update working in my observer method.
What do I have to do to get the catalog price rule being assigned, after changing the custom attribute?
Thx in advance!
Okay, I want to advise you on some fairly major code optimisations.
You can reduce your collection size and remove the conditional logic inside your loop by using:
$order->getItemsCollection()->addFieldToFilter('is_in_stock', 0);
You could also update all the attributes with a much faster method than save(), by using:
Mage::getSingleton('catalog/product_action')
->updateAttributes($order->getItemsCollection()->addFieldToFilter('is_in_stock', 0)->getAllIds(), array('auto_discount_active' => 1), 0);
Also, bear in mind, you'll also need to apply your observer to any product stock level modification, ie. product save, import, credit memo (refund) - so its a fairly expansive area. You would probably be better served rewriting the stock class, as there isn't too many events dispatched that will give you enough scope to cover this.
Finally, to perform the assignation of rules, I would suggest extending the resource model for the rule (Mage/CatalogRule/Model/Mysql4/Rule.php) so that you can pass in your array of product ids (to save it iterating through the entire catalogue).
You could simply extend getRuleProductIds() to take a Mage::registry variable (if set) with your product ids from the collection above. Then after running the code above, you could just execute
Mage::getModel('catalogrule/rule')->load(myruleid)->save();
Which will re-index and apply rules to new products as necessary - for only the products that have changed.
I would imagine this method cutting overheads by an extremely significant amount.