Magento中产品库存不报警解决方案
最近我们的一个客户联系我们,告诉我们产品报警功能不再工作。在调查后,我发现最后的一封产品报警邮件是几个月前的。在此期间,我们已经在网站升级到Magento的EE,起初我想,也许在升级过程中出了问题。另一种可能是,客户也许修改了Transactional Email Template。在看完日志文件后,我没发现任何相关的邮件。系统虽然没有发送其它电子邮件。
在调查过程中,我看论坛上的其它开发者有相似的问题也没解决的。在追寻和查找后,我们发现问题所在。
简单说来,这个事件在Magento处理产品报库存警的集合方法里。我们的客户有超过40000封订阅和超过30000个需要将“缺货”状态改为“有货”。所以当Magento尝试从product_alert_stock表读取40000条记录到集合里的时候,由于内存限制而失败了。可能你的product_alert_stock表有超过20000记录时,产品库存报警功能就失效了。
解决方案相对简单。通过以1000为集合创建分页来遍历超过30000条记录。正如Magento在系统中所做的那样。新的功能是通过遍历所有的这些记录,循环出status=0(未处理)的记录,检查产品现在是否有货。当true=> set status=1就发送电子邮件通知客户。
那么,让我们创建我们的模块,Alwayly_ProductAlert,这将提高Magento方法。
1. Create file in app/etc/modules/ Alwayly_ProductAlert.xml with the following content:
<?xml version="1.0"?>
<config>
<modules>
<Alwayly_ProductAlert>
<active>true</active>
<codePool>local</codePool>
</Alwayly_ProductAlert>
</modules>
</config>
2. Create file in app/code/local/Alwayly/ProductAlert/etc/ config.xml with the following content:
<?xml version="1.0"?>
<config>
<modules>
<Alwayly_ProductAlert>
<version>1.0.0.0</version>
</Alwayly_ProductAlert>
</modules>
<global>
<models>
<productalert>
<rewrite>
<observer>Alwayly_ProductAlert_Model_Observer</observer>
</rewrite>
</productalert>
</models>
</global>
</config>
你可以看到我们重写了Mage_ProductAlert_Model_Observer类
Create file in /www/app/code/local/Alwayly/ProductAlert/Model/ Observer.php with the following content:
<?php
/*
* ProductAlert observer
*/
class Alwayly_ProductAlert_Model_Observer extends Mage_ProductAlert_Model_Observer
{
/**
* Process stock emails
*
* @param Mage_ProductAlert_Model_Email $email
* @return Mage_ProductAlert_Model_Observer
*/
protected function _processStock(Mage_ProductAlert_Model_Email $email)
{
$email->setType('stock');
foreach ($this->_getWebsites() as $website) {
/* @var $website Mage_Core_Model_Website */
if (!$website->getDefaultGroup() || !$website->getDefaultGroup()->getDefaultStore()) {
continue;
}
if (!Mage::getStoreConfig(
self::XML_PATH_STOCK_ALLOW,
$website->getDefaultGroup()->getDefaultStore()->getId()
)) {
continue;
}
try {
$wholeCollection = Mage::getModel('productalert/stock')
->getCollection()
// ->addWebsiteFilter($website->getId())
->addFieldToFilter('website_id', $website->getId())
->addFieldToFilter('status', 0)
;
// $wholeCollection->getSelect()->order('alert_stock_id DESC');
/* table: !product_alert_stock!
alert_stock_id: 1
customer_id: 1
product_id: 1
website_id: 1
add_date: 2013-04-26 12:08:30
send_date: 2013-04-26 12:28:16
send_count: 2
status: 1
*/
}
catch (Exception $e) {
Mage::log('error-1-collection $e=' . $e->getMessage(), false, 'product_alert_stock_error.log', true);
$this->_errors[] = $e->getMessage();
return $this;
}
$previousCustomer = null;
$email->setWebsite($website);
try {
$originalCollection = $wholeCollection;
$count = null;
$page = 1;
$lPage = null;
$break = false;
while ($break !== true) {
$collection = clone $originalCollection;
$collection->setPageSize(1000);
$collection->setCurPage($page);
$collection->load();
if (is_null($count)) {
$count = $collection->getSize();
$lPage = $collection->getLastPageNumber();
}
if ($lPage == $page) {
$break = true;
}
Mage::log('page=' . $page, false, 'check_page_count.log', true);
Mage::log('collection=' . (string)$collection->getSelect(), false, 'check_page_count.log', true);
$page ++;
foreach ($collection as $alert) {
try {
if (!$previousCustomer || $previousCustomer->getId() != $alert->getCustomerId()) {
$customer = Mage::getModel('customer/customer')->load($alert->getCustomerId());
if ($previousCustomer) {
$email->send();
}
if (!$customer) {
continue;
}
$previousCustomer = $customer;
$email->clean();
$email->setCustomer($customer);
}
else {
$customer = $previousCustomer;
}
$product = Mage::getModel('catalog/product')
->setStoreId($website->getDefaultStore()->getId())
->load($alert->getProductId());
/* @var $product Mage_Catalog_Model_Product */
if (!$product) {
continue;
}
$product->setCustomerGroupId($customer->getGroupId());
if ($product->isSalable()) {
$email->addStockProduct($product);
$alert->setSendDate(Mage::getModel('core/date')->gmtDate());
$alert->setSendCount($alert->getSendCount() + 1);
$alert->setStatus(1);
$alert->save();
}
}
catch (Exception $e) {
Mage::log('error-2-alert $e=' . $e->getMessage(), false, 'product_alert_stock_error.log', true);
$this->_errors[] = $e->getMessage();
}
}
}
Mage::log("\n\n", false, 'check_page_count.log', true);
} catch (Exception $e) {
Mage::log('error-3-steps $e=' . $e->getMessage(), false, 'product_alert_stock_error.log', true);
}
if ($previousCustomer) {
try {
$email->send();
}
catch (Exception $e) {
$this->_errors[] = $e->getMessage();
}
}
}
return $this;
}
/**
* Run process send product alerts
*/
public function process()
{
Mage::log('ProductAlert started @' . now(), false, 'product_alert_workflow.log', true);
$email = Mage::getModel('productalert/email');
/* @var $email Mage_ProductAlert_Model_Email */
$this->_processPrice($email);
$this->_processStock($email);
$this->_sendErrorEmail();
Mage::log('ProductAlert finished @' . now(), false, 'product_alert_workflow.log', true);
return $this;
}
}
你可以看到,我们重写了process()和 _processStock()方法。在process()方法中,我们添加了Mage::log()创建起止时间到var/log/product_alert_workflow.log。这样我们就能知道脚本是否执行完成。同样的,我们增强_processStock()方法的功能,我们增加了一些Mage:log()调用来跟踪,看所有的行为是否按期待的方式表现。
最后,如果你不想等待你的Cron被触发,你可以在Magento根目录下创建一个文件,让我们调用它。
Alwayly_cron.php
<?php
require_once 'app/Mage.php';
Mage::app();
try {
Mage::getModel('productalert/observer')->process();
} catch (Exception $e) {
Mage::log('error-0-start $e=' . $e->getMessage() . ' @' . now(), false, 'product_alert_stock_error.log', true);
}
就是这些了,这个解决思路同样适用于产品价格报警。