📜 ⬆️ ⬇️

Create a module "New Mail" for Magento (Part 2)

Table of contents


  1. Create a new mail module for Magento (Part 1) , where we add a new delivery method to Magento
  2. We create the “New Mail” module for Magento (part 2), where we teach Magento to store and synchronize the warehouse base with New Mail


After the break, associated with the launch of the project for a harmful customer, I will continue the job. Let me remind you that all sources can be found on GitHub: github.com/alexkuk/Ak_NovaPoshta , they are supplemented in the course of development.

In this part, we will get the API key and write the synchronization of warehouses and cities from New Mail to the Magento database.
')
As a result, we get the following table in the admin panel:


API New Mail


It seems that New Mail is hiding its API as soon as it can. Even I learned about its existence from third-party forums.

The first thing you need to do to gain access is to register with the loyalty program in the New Mail office. As a result, you will receive a login and password to access your personal account. On the page of this office there are also no references to the API, but good people on the Internet point to the following address: orders.novaposhta.ua/api.php?todo=api_form .

Hooray! We have documentation and even a form for testing requests. But we also need a key. Here again, the help of kind people was needed - in order to see your key, you need to go to this address: orders.novaposhta.ua/api.php?todo=api_get_key_ajax .

Access to the API is, back to Magento.

Add configuration options


Make a configurable URL and API key. Also, when working with the API, it will be useful to write your disconnectable log

Add the following fields to system.xml:

<api_url translate="label"> <label>API URL</label> <frontend_type>text</frontend_type> <sort_order>120</sort_order> <show_in_default>1</show_in_default> <show_in_website>0</show_in_website> <show_in_store>0</show_in_store> </api_url> <api_key translate="label"> <label>API key</label> <frontend_type>text</frontend_type> <sort_order>130</sort_order> <show_in_default>1</show_in_default> <show_in_website>0</show_in_website> <show_in_store>0</show_in_store> </api_key> <enable_log translate="label"> <label>Enable log</label> <frontend_type>select</frontend_type> <source_model>adminhtml/system_config_source_yesno</source_model> <sort_order>140</sort_order> <show_in_default>1</show_in_default> <show_in_website>0</show_in_website> <show_in_store>0</show_in_store> </enable_log> 


In the config.xml add the default values:
 <config> ... <default> <carriers> <novaposhta> ... <api_url>http://orders.novaposhta.ua/xml.php</api_url> <enable_log>0</enable_log> </novaposhta> </carriers> </default> ... </config> 


In the helper, we implement the method of accessing configuration values ​​and the method of writing to the log. Such small things, used in different parts of the module, are conveniently taken out to the helper. It should also be understood that Mage :: helper ('novaposhta') returns the singleton of our helper.
 class Ak_NovaPoshta_Helper_Data extends Mage_Core_Helper_Abstract { protected $_logFile = 'novaposhta.log'; /** * @param $string * * @return Ak_NovaPoshta_Helper_Data */ public function log($string) { if ($this->getStoreConfig('enable_log')) { Mage::log($string, null, $this->_logFile); } return $this; } /** * @param string $key * @param null $storeId * * @return mixed */ public function getStoreConfig($key, $storeId = null) { return Mage::getStoreConfig("carriers/novaposhta/$key", $storeId); } } 


Cooking DB


Add our tables to the database. To do this, we use the built-in update mechanism in Magento (for more details, read this article codemagento.com/2011/02/altering-the-database-through-setup-scripts ).

First we describe the added resources and entities, and also add the novaposhta_setup resource in the config.xml:
 ... <global> <models> <novaposhta> <class>Ak_NovaPoshta_Model</class> <resourceModel>novaposhta_resource</resourceModel> </novaposhta> <novaposhta_resource> <class>Ak_NovaPoshta_Model_Resource</class> <entities> <city> <table>novaposhta_city</table> </city> <warehouse> <table>novaposhta_warehouse</table> </warehouse> </entities> <novaposhta_resource> </models> ... <resources> <novaposhta_setup> <setup> <module>Ak_NovaPoshta</module> </setup> </novaposhta_setup> </resources> </global> ... 


Add the upgrade script app / code / community / Ak / NovaPoshta / sql / novaposhta_setup / mysql4-upgrade-1.0.0-1.0.1.php, in which we create the tables we need.
 /* @var $installer Mage_Core_Model_Resource_Setup */ $installer = $this; $installer->startSetup(); $installer->run(" CREATE TABLE {$this->getTable('novaposhta_city')} ( `id` int(10) unsigned NOT NULL, `name_ru` varchar(100), `name_ua` varchar(100), `updated_at` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (`id`), INDEX `name_ru` (`name_ru`), INDEX `name_ua` (`name_ua`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE {$this->getTable('novaposhta_warehouse')} ( `id` int(10) unsigned NOT NULL, `city_id` int(10) unsigned NOT NULL, `address_ru` varchar(200), `address_ua` varchar(200), `phone` varchar(100), `weekday_work_hours` varchar(20), `weekday_reseiving_hours` varchar(20), `weekday_delivery_hours` varchar(20), `saturday_work_hours` varchar(20), `saturday_reseiving_hours` varchar(20), `saturday_delivery_hours` varchar(20), `max_weight_allowed` int(4), `longitude` float(10,6), `latitude` float(10,6), `number_in_city` int(3) unsigned NOT NULL, `updated_at` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (`id`), CONSTRAINT FOREIGN KEY (`city_id`) REFERENCES `{$this->getTable('novaposhta_city')}` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; "); $installer->endSetup(); 


It remains to raise the version of the module to 1.0.1 in our config.xml, clear the cache, run Magento, and you can check if the tables were created in the database. They were created, we go further.

Create models, resources and collections


We add the city and warehouse entities. In order to work with them, we need to create the corresponding models Ak_NovaPoshta_Model_City and Ak_NovaPoshta_Model_Warehouse. In order to save them in the database, create the resources Ak_NovaPoshta_Model_Resource_City and Ak_NovaPoshta_Model_Resource_Warehouse. To associate a model with a resource in a model class in a pseudo-constructor, call the _init () method with the alias of the resource class as a parameter:
 class Ak_NovaPoshta_Model_City extends Mage_Core_Model_Abstract { public function _construct() { $this->_init('novaposhta/city'); } … } 


In the resource, call the _init () of the resource to which we will transfer the alias of the database table and the name of the primary key field.
 class Ak_NovaPoshta_Model_Resource_City extends Mage_Core_Model_Resource_Db_Abstract { public function _construct() { $this->_init('novaposhta/city', 'id'); } } 


Also add the Ak_NovaPoshta_Model_Resource_City_Collection and Ak_NovaPoshta_Model_Resource_Warehouse_Collection collections. In the _init () method call we pass the model alias. Example Ak_NovaPoshta_Model_Resource_City_Collection:
 class Ak_NovaPoshta_Model_Resource_City_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract { public function _construct() { $this->_init('novaposhta/city'); } } 


API client model


Create a model Ak_NovaPoshta_Model_Api_Client, which will hide the logic of working with the API. Client code: github.com/alexkuk/Ak_NovaPoshta/blob/master/app/code/community/Ak/NovaPoshta/Model/Api/Client.php
Our new client has two public methods: getCityWarehouses () returns cities that have offices of New Mail, getWarehouses () returns a list of warehouses throughout Ukraine. The data is returned as a SimpleXMLElement object.

Import


Add the Ak_NovaPoshta_Model_Import model: github.com/alexkuk/Ak_NovaPoshta/blob/master/app/code/community/Ak/NovaPoshta/Model/Import.php . It makes no sense to describe in detail the import process. I will dwell only on some things.

I added two arrays $ _dataMapCity and $ _dataMapWarehouse, which associate the field names returned by the API with the field names in our database. After receiving the response from the API, we bring the answer to the desired form using the _applyMap () method:
 $cities = $this->_applyMap($cities, $this->_dataMapCity); 


In order to write data to the database when importing, I do not use the City and Warehouse models, but directly execute a SQL query, first breaking it up into parts. I execute the request using the core_write resource:

  /** * @return Varien_Db_Adapter_Interface */ protected function _getConnection() { return Mage::getSingleton('core/resource')->getConnection('core_write'); } 


To test the Import model, I threw the test.php script into the root of Magento. In it, we initialize Magento by calling the Mage :: app () method, after which you can use the Mage factory:
 require 'app/Mage.php'; Mage::app('default'); Mage::getModel('novaposhta/import')->runWarehouseAndCityMassImport(); 


Run import on CRON


The import is ready and debugged, it would be good now to run it periodically on CRON. Magento has its own CRON subsystem. You can read , for example, here: www.magentocommerce.com/wiki/1_-_installation_and_configuration/how_to_setup_a_cron_job . In a nutshell: we add a cron job to the familiar Unix cron, which will run the cron.php or cron.sh script, which in turn launches the CRON Magento subsystem. As part of this call, all the tasks added by the modules via config.xml are executed.

So, let's add our task in the config.xml:
  <crontab> <jobs> <novaposhta_import_city_and_warehouse> <schedule> <cron_expr>1 2 * * *</cron_expr> </schedule> <run> <model>ak_novaposhta/import::runWarehouseAndCityMassImport</model> </run> </novaposhta_import_city_and_warehouse> </jobs> </crontab> 


Add a warehouse table to the admin panel


To create a grid like in the picture above, we need two classes of the block: the grid container class and the grid class itself. The container inherited from Mage_Adminhtml_Block_Widget_Grid_Container determines the appearance and behavior of the buttons, and also displays the Mage_Adminhtml_Block_Widget_Grid itself.

Oh yeah, you still need a controller :)

So, Ak_NovaPoshta_Block_Adminhtml_Warehouses:
 class Ak_NovaPoshta_Block_Adminhtml_Warehouses extends Mage_Adminhtml_Block_Widget_Grid_Container { public function __construct() { // $this->_blockGroup  $this->_controller   ,   _prepareLayout()     (novaposhta/adminhtml_warehouses).      _prepareLayout(). $this->_blockGroup = 'novaposhta'; $this->_controller = 'adminhtml_warehouses'; this->_headerText = $this->__('Manage warehouses'); parent::__construct(); //   add,    ,         $this->_removeButton('add'); //   ,     $this->_addButton('synchronize', array( 'label' => $this->__('Synchronize with API'), 'onclick' => 'setLocation(\'' . $this->getUrl('*/*/synchronize') .'\')' )); } } 


Grid class:
 class Ak_NovaPoshta_Block_Adminhtml_Warehouses_Grid extends Mage_Adminhtml_Block_Widget_Grid { public function __construct() { parent::__construct(); $this->setDefaultSort('city_id'); $this->setId('warehousesGrid'); $this->setDefaultDir('asc'); $this->setSaveParametersInSession(true); } protected function _prepareCollection() { /** @var $collection Ak_NovaPoshta_Model_Resource_Warehouse_Collection */ $collection = Mage::getModel('novaposhta/warehouse') ->getCollection(); $this->setCollection($collection); return parent::_prepareCollection(); } protected function _prepareColumns() { //    $this->addColumn('id', array( 'header' => $this->__('ID'), 'align' =>'right', 'width' => '50px', 'index' => 'id' ) ); $this->addColumn('address_ru', array( 'header' => $this->__('Address (ru)'), 'index' => 'address_ru' ) ); $this->addColumn('city_id', array( 'header' => $this->__('City'), 'index' => 'city_id', 'type' => 'options', //      City      “”  'options' => Mage::getModel('novaposhta/city')->getOptionArray() ) ); $this->addColumn('phone', array( 'header' => $this->__('Phone'), 'index' => 'phone' ) ); $this->addColumn('max_weight_allowed', array( 'header' => $this->__('Max weight'), 'index' => 'max_weight_allowed' ) ); return parent::_prepareColumns(); } //  false -         public function getRowUrl($row) { return false; } } 


Now the controller. Since the controller is for admin, we inherit from Mage_Adminhtml_Controller_Action.
 class Ak_NovaPoshta_WarehousesController extends Mage_Adminhtml_Controller_Action { /** *        */ public function indexAction() { $this->_title($this->__('Sales'))->_title($this->__('Nova Poshta Warehouses')); $this->_initAction() ->_addContent($this->getLayout()->createBlock('novaposhta/adminhtml_warehouses')) ->renderLayout(); return $this; } /** *    */ public function synchronizeAction() { try { Mage::getModel('novaposhta/import')->runWarehouseAndCityMassImport(); // ,  success message    $this->_getSession()->addSuccess($this->__('City and Warehouse API synchronization finished')); } catch (Exception $e) { // ,  error message    $this->_getSession()->addError($this->__('Error during synchronization: %s', $e->getMessage())); } //       $this->_redirect('*/*/index'); return $this; } /** * Initialize action * * @return Ak_NovaPoshta_WarehousesController */ protected function _initAction() { $this->loadLayout() ->_setActiveMenu('sales/novaposhta/warehouses') ->_addBreadcrumb($this->__('Sales'), $this->__('Sales')) ->_addBreadcrumb($this->__('Nova Poshta Warehouses'), $this->__('Nova Poshta Warehouses')) ; return $this; } } 


But that is not all. First, we need to add a route to config.xml so that Magento can find our controller.
 <config> ... <admin> <routers> <novaposhta> <use>admin</use> <args> <module>Ak_NovaPoshta</module> <frontName>novaposhta</frontName> </args> </novaposhta> </routers> </admin> ... </config> 


Secondly, we need to add an item to the admin menu and add it to the ACL. All this is entered in adminhtml.xml:
 <?xml version="1.0"?> <config> <menu> <sales> <children> <novaposhta translate="title" module="novaposhta"> <sort_order>200</sort_order> <title>Nova Poshta</title> <children> <warehouses translate="title" module="novaposhta"> <sort_order>10</sort_order> <title>Warehouses</title> <action>novaposhta/warehouses/</action> </warehouses> </children> </novaposhta> </children> </sales> </menu> <acl> <resources> <admin> <children> <sales> <children> <novaposhta translate="title" module="novaposhta"> <title>Nova Poshta</title> <sort_order>200</sort_order> <children> <warehouses translate="title" module="novaposhta"> <sort_order>10</sort_order> <title>Warehouses</title> </warehouses> </children> </novaposhta> </children> </sales> </children> </admin> </resources> </acl> </config> 


Is done


We have synchronization and there is a fairly convenient interface for viewing warehouses. The next task is to display the New Mail warehouses in a convenient way for selection in the Shipping Method step of the ordering process, by default, to display only warehouses in the user's city.

I would be happy for comments, questions, suggestions :)

PS Thanks to the user kokoc for a useful comment. To work with the structure of the database tables, in the upgrade script it is better to use Varien_Db_Ddl_Table. I was too lazy and caught on the hot :) Writing in the form of an SQL query seems to me more readable, but the abstraction is more important.

Source: https://habr.com/ru/post/162313/


All Articles