Cum să adăugați un atribut personalizat de produs ca filtru pentru tarifele de expediere
Publicat: 2020-04-07Adesea, în Magento 2, numărul de atribute standard ale produsului care pot fi utilizate pentru a seta condiții este limitat. Ar fi necesară personalizare suplimentară pentru a satisface nevoile afacerii.
Din acest articol, veți învăța cum să realizați acest lucru și să adăugați atribute personalizate ale produsului ca filtru pentru tarifele de expediere.
Note:
- Vezi exemplul complet de cod pe GitHub.
- Prima parte a exemplului, care adaugă atributul „Greutate volum” ca filtru la tarifele de expediere, este disponibilă aici.
- Este necesar modulul original Magento 2 Shipping Suite Ultimate.
Să trecem direct la discutarea ce anume trebuie făcut pentru a atinge obiectivul.
Cuprins
- Ghid pas cu pas pentru adăugarea unui atribut personalizat de produs
- Pasul 1. Creați un modul nou adăugând fișierele de bază
- Pasul 2. Creați o structură de modul
- Pasul 3. Interfața utilizator
- Formă
- Grilă
Ghid pas cu pas pentru adăugarea unui atribut personalizat de produs
Pasul 1. Creați un modul nou adăugând fișierele de bază
Începeți cu denumirea modulului:
> app/code/MageWorx/ShippingRateByProductAttribute/registration.php <?php /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'MageWorx_ShippingRateByProductAttribute', __DIR__ );
Apoi, detectați și declarați cerințele acestuia. De asemenea, va trebui să dați un nume modulului nostru pentru compozitor, să setați versiunea și să adăugați o scurtă descriere:
> app/code/MageWorx/ShippingRateByProductAttribute/composer.json { "name": "mageworx/module-shipping-rate-by-product-attribute", "description": "Shipping Rules Extension: Adds product attribute to the Rates", "require": { "magento/module-shipping": ">=100.1.0 < 101", "magento/module-ui": ">=100.1.0 < 102", "mageworx/module-shippingrules": ">=2.7.1" }, "type": "magento2-module", "version": "1.0.0", "license": [ "OSL-3.0", "AFL-3.0" ], "autoload": { "files": [ "registration.php" ], "psr-4": { "MageWorx\\ShippingRateByProductAttribute\\": "" } } }
În plus, putem seta numele și versiunea inițială în configurația Magento 2, declara secvența:
> app/code/MageWorx/ShippingRateByProductAttribute/etc/module.xml <?xml version="1.0"?> <!-- /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="MageWorx_ShippingRateByProductAttribute" setup_version="1.0.0"> <sequence> <module name="MageWorx_ShippingRules" /> </sequence> </module> </config>
Pasul 2. Creați o structură de modul
Să presupunem că avem un atribut de produs numit „shippingnew”, care a fost creat din partea admin. Este un tip de intrare derulant și are câteva opțiuni numite „A, B, C, D”, etc. Aceste opțiuni descriu modul în care ne expediem articolele pe zone. Fiecare valoare are propriul preț, iar produsele cu cel mai mare preț vor modifica costul metodei de expediere în timpul plății.
În primul rând, trebuie să creăm un tabel separat pentru condițiile extinse ale tarifelor de expediere. Mai târziu, le vom adăuga folosind atributele obișnuite de extensie ale modelului (modelul „Rata de expediere” extinde „\Magento\Framework\Model\AbstractExtensibleModel` ).
> app/code/MageWorx/ShippingRateByProductAttribute/Setup/InstallSchema.php <?php /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ namespace MageWorx\ShippingRateByProductAttribute\Setup; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; /** * Class InstallSchema */ class InstallSchema implements InstallSchemaInterface { /** * Installs DB schema for a module * * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context * @return void * @throws \Zend_Db_Exception */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $installer = $setup; $installer->startSetup(); $ratesTable = $installer->getTable(\MageWorx\ShippingRules\Model\Carrier::RATE_TABLE_NAME); /** * Create table 'mageworx_shippingrules_rates_shippingnew' */ $table = $installer->getConnection()->newTable( $installer->getTable('mageworx_shippingrules_rates_shippingnew') )->addColumn( 'rate_id', Table::TYPE_INTEGER, null, ['unsigned' => true, 'nullable' => false], 'Rate Id' )->addColumn( 'shippingnew', Table::TYPE_TEXT, '120', ['nullable' => false], 'shippingnew attribute value' )->addForeignKey( $installer->getFkName('mageworx_shippingrules_rates_shippingnew', 'rate_id', $ratesTable, 'rate_id'), 'rate_id', $ratesTable, 'rate_id', Table::ACTION_CASCADE )->addIndex( $installer->getIdxName( 'mageworx_shippingrules_rates_product_attributes', ['rate_id', 'shippingnew'], \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE ), ['rate_id', 'shippingnew'], ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] )->setComment( 'Product Attributes For Shipping Suite Rates' ); $installer->getConnection()->createTable($table); } }
Am numit tabelul nostru după cum urmează: `'mageworx_shippingrules_rates_shippingnew'`. Are doar 2 coloane. Una dintre ele este folosită ca cheie străină. Este coloana `rate_id`, care va fi legată cu tabelul obișnuit `mageworx_shippingrules_rates` din modulul MageWorx Shipping Suite Ultimate pentru Magento 2. O altă coloană va conține valori din atributul `shippingnew`.
Înainte de a face un observator să încarce/salva/ștergem datele noastre personalizate în tabel, trebuie să creăm cel puțin două modele - model obișnuit și model de resurse.
> app/code/MageWorx/ShippingRateByProductAttribute/Model/ShippingNew.php <?php /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ namespace MageWorx\ShippingRateByProductAttribute\Model; use Magento\Framework\Model\AbstractModel; /** * Class ShippingNew */ class ShippingNew extends AbstractModel { /** * Prefix of model events names * * @var string */ protected $_eventPrefix = 'mageworx_shippingnew'; /** * Parameter name in event * * In observe method you can use $observer->getEvent()->getObject() in this case * * @var string */ protected $_eventObject = 'shippingnew'; /** * Set resource model and Id field name * * @return void */ protected function _construct() { parent::_construct(); $this->_init('MageWorx\ShippingRateByProductAttribute\Model\ResourceModel\ShippingNew'); $this->setIdFieldName('rate_id'); } }
Note:
- ` _eventPrefix ` va fi folosit pentru a detecta evenimentele modelului nostru.
- `_eventObject` va fi folosit pentru a stoca date în obiectul eveniment. Folosind acest nume putem obține modelul nostru de la obiectul eveniment.
- `$this->_init( 'MageWorx\ShippingRateByProductAttribute\Model\ResourceModel\ ShippingNew' );` leagă modelul nostru cu modelul de resurse corespunzător.
- `$this->setIdFieldName( 'rate_id' );` descrie ce câmp din tabel trebuie folosit ca cheie (de obicei îl numim id)
> app/code/MageWorx/ShippingRateByProductAttribute/Model/ResourceModel/ShippingNew.php <?php /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ namespace MageWorx\ShippingRateByProductAttribute\Model\ResourceModel; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; /** * Class ShippingNew */ class ShippingNew extends AbstractDb { /** * Resource initialization * * @return void */ protected function _construct() { $this->_init('mageworx_shippingrules_rates_shippingnew', 'rate_id'); } /** * @param $rateId * @param int $shippingNew * @return int * @throws \Magento\Framework\Exception\LocalizedException */ public function insertUpdateRecord($rateId, int $shippingNew) { $rowsAffected = $this->getConnection()->insertOnDuplicate( $this->getMainTable(), [ 'rate_id' => $rateId, 'shippingnew' => $shippingNew ] ); return $rowsAffected; } /** * @param $rateId * @return int * @throws \Magento\Framework\Exception\LocalizedException */ public function deleteRecord($rateId) { $rowsAffected = $this->getConnection()->delete( $this->getMainTable(), [ 'rate_id = ?' => $rateId ] ); return $rowsAffected; } }
Note:
- $this->_init( 'mageworx_shippingrules_rates_shippingnew' , 'rate_id' ); setați numele tabelului principal și numele câmpului ID.
- funcția publică insertUpdateRecord($rateId, int $shippingNew) este metoda, care ne poate ajuta să actualizăm valoarea atributului din tabelul nostru personalizat.
- funcția publică deleteRecord($rateId) este concepută pentru a elimina coloana.
Mai târziu, vom folosi acele metode în observatorii noștri.
Acum, să adăugăm noile noastre date ca atribut de extensie la modelul Ratei de expediere:
> app/code/MageWorx/ShippingRateByProductAttribute/etc/extension_attributes.xml <?xml version="1.0"?> <!-- /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd"> <!-- Rate Extension --> <extension_attributes for="MageWorx\ShippingRules\Api\Data\RateInterface"> <attribute code="shippingnew" type="int"> <join reference_table="mageworx_shippingrules_rates_shippingnew" reference_field="rate_id" join_on_field="rate_id"> <field>shippingnew</field> </join> </attribute> </extension_attributes> </config>
De asemenea, ar trebui să avem grijă de operațiunile regulate ale stării noastre personalizate:
> app/code/MageWorx/ShippingRateByProductAttribute/etc/events.xml <?xml version="1.0"?> <!-- /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> <!-- Add Extension Attributes to the Rates Collection --> <!-- Save custom attribute value during rate saving --> <event name="mageworx_shippingrules_rate_save_after"> <observer name="mageworx_save_shippingnew_attribute" instance="MageWorx\ShippingRateByProductAttribute\Observer\SaveShippingNewRateAttribute" /> </event> <!-- Add custom attribute value to the rates collection --> <event name="rates_collection_render_filters_before"> <observer name="mageworx_add_shippingnew_attribute" instance="MageWorx\ShippingRateByProductAttribute\Observer\AddShippingNewToRatesCollection" /> </event> <!-- Take care of filtering the rates grid --> <event name="mageworx_suitable_rates_collection_load_before"> <observer name="mageworx_filter_rates_by_shippingnew_attribute" instance="MageWorx\ShippingRateByProductAttribute\Observer\FilterRatesCollectionByShippingNewAttribute" /> </event> <!-- 3 event observers for the Export/Import rates with custom attribute in conditions --> <event name="mageworx_rates_export_collection_join_linked_tables_after"> <observer name="mageworx_join_shipping_new_table_to_export_rates_collection" instance="MageWorx\ShippingRateByProductAttribute\Observer\JoinShippingNewTableToExportRatesCollection" /> </event> <event name="mageworx_filter_rates_data_before_insert"> <observer name="mageworx_remove_shipping_new_before_insert" instance="MageWorx\ShippingRateByProductAttribute\Observer\RemoveShippingNewBeforeInsert" /> </event> <event name="mageworx_shippingrules_import_insert_rates"> <observer name="mageworx_shippingrules_import_insert_update_shipping_new" instance="MageWorx\ShippingRateByProductAttribute\Observer\InsertUpdateShippingNewDuringImport" /> </event> </config>
Primul eveniment este pentru salvarea/actualizarea/ștergerea valorii atributului personalizat în condiția ratelor.
Cele doua două evenimente sunt pentru adăugarea acestei valori de atribut la colecție.
Ultimele trei evenimente sunt pentru funcționalitatea Import/Export.
Să le analizăm unul câte unul mai detaliat:
> app/code/MageWorx/ShippingRateByProductAttribute/Observer/SaveShippingNewRateAttribute.php <?php /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ namespace MageWorx\ShippingRateByProductAttribute\Observer; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Exception\LocalizedException; use MageWorx\ShippingRules\Api\Data\RateInterface; /** * Class SaveShippingNewRateAttribute * * Saves custom attribute (`shippingnew`) values after model was saved */ class SaveShippingNewRateAttribute implements ObserverInterface { /** * @var \MageWorx\ShippingRateByProductAttribute\Model\ResourceModel\ShippingNew */ private $resource; /** * @var \Magento\Framework\Message\ManagerInterface */ private $messagesManager; /** * SaveVolumeWeightRateAttribute constructor. * * @param \MageWorx\ShippingRateByProductAttribute\Model\ResourceModel\ShippingNew $resource * @param \Magento\Framework\Message\ManagerInterface $messagesManager */ public function __construct( \MageWorx\ShippingRateByProductAttribute\Model\ResourceModel\ShippingNew $resource, \Magento\Framework\Message\ManagerInterface $messagesManager ) { $this->resource = $resource; $this->messagesManager = $messagesManager; } /** * @param Observer $observer * @return void */ public function execute(Observer $observer) { /** @var RateInterface $model */ $model = $observer->getEvent()->getData('rate'); if (!$model instanceof RateInterface) { return; } $shippingNewValue = $model->getData('shippingnew') !== '' ? $model->getData('shippingnew') : null; if ($shippingNewValue === null) { try { $this->resource->deleteRecord($model->getRateId()); } catch (LocalizedException $deleteException) { $this->messagesManager->addErrorMessage( __('Unable to delete the Shipping Category for the Rate %1', $model->getRateId()) ); } } else { try { $this->resource->insertUpdateRecord($model->getRateId(), $shippingNewValue); } catch (LocalizedException $saveException) { $this->messagesManager->addErrorMessage( __('Unable to save the Shipping Category for the Rate %1', $model->getRateId()) ); } } return; } }
Este la fel de simplu. Când salvăm o rată, trebuie să avem grijă să salvăm și valoarea atributului personalizat. În cazul în care valoarea sa este egală cu `null`, ștergeți o înregistrare.
> app/code/MageWorx/ShippingRateByProductAttribute/Observer/AddShippingNewToRatesCollection.php <?php /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ namespace MageWorx\ShippingRateByProductAttribute\Observer; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; /** * Class AddShippingNewToRatesCollection * * Adds custom attribute to the rates collection. * It will be used later during quote validation. */ class AddShippingNewToRatesCollection implements ObserverInterface { /** * Join custom table to the rates collection to obtain the `shippingnew` attribute anywhere in the code. * * @param Observer $observer * @return void */ public function execute(Observer $observer) { /** @var \MageWorx\ShippingRules\Model\ResourceModel\Rate\Collection $collection */ $collection = $observer->getEvent()->getData('collection'); if (!$collection instanceof \MageWorx\ShippingRules\Model\ResourceModel\Rate\Collection) { return; } if ($collection->isLoaded()) { return; } $joinTable = $collection->getTable('mageworx_shippingrules_rates_shippingnew'); $collection->getSelect() ->joinLeft( $joinTable, '`main_table`.`rate_id` = `' . $joinTable . '`.`rate_id`', ['shippingnew'] ); } }
Pentru a face validarea disponibilă, atunci când un client merge la casă sau la estimarea tarifelor de expediere, să ne alăturăm tabelului nostru cu atributul personalizat la tabelul de tarife obișnuite.
> app/code/MageWorx/ShippingRateByProductAttribute/Observer/FilterRatesCollectionByShippingNewAttribute.php <?php /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ namespace MageWorx\ShippingRateByProductAttribute\Observer; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; /** * Class FilterRatesCollectionByShippingNewAttribute * * Filter rates collection before we load it by custom attribute: shippingnew. * * For more details * * @see \MageWorx\ShippingRules\Model\Carrier\Artificial::getSuitableRatesAccordingRequest() * */ class FilterRatesCollectionByShippingNewAttribute implements ObserverInterface { /** * @param Observer $observer * @return void */ public function execute(Observer $observer) { /** @var \MageWorx\ShippingRules\Model\ResourceModel\Rate\Collection $collection */ $collection = $observer->getEvent()->getData('rates_collection'); if (!$collection instanceof \MageWorx\ShippingRules\Model\ResourceModel\Rate\Collection) { return; } /** @var \Magento\Quote\Model\Quote\Address\RateRequest $request */ $request = $observer->getEvent()->getData('request'); if (!$request instanceof \Magento\Quote\Model\Quote\Address\RateRequest) { return; } /** @var \Magento\Quote\Model\Quote\Item[] $items */ $items = $request->getAllItems() ?? []; $shippingCategories = []; foreach ($items as $item) { $value = $item->getProduct()->getData('shippingnew'); if ($value !== null) { $shippingCategories[] = $value; } } $shippingCategories = array_unique($shippingCategories); $joinTable = $collection->getTable('mageworx_shippingrules_rates_shippingnew'); $collection->getSelect() ->joinLeft( ['sn' => $joinTable], '`main_table`.`rate_id` = `sn`.`rate_id`', ['shippingnew'] ); $collection->getSelect()->where( "`sn`.`shippingnew` IN (?)", $shippingCategories ); } }
Acesta este cel mai complicat observator din stiva noastră. Este conceput pentru a colecta toate valorile atributelor (`$shippingCategories`) din coșul unui client și adaugă valoarea atributului ca filtru la colecția de tarife obișnuite (tabelul nostru este deja alăturat). Pentru a rămâne simplu, l-am numit „Filtru”. Când lucrul este terminat, un client va vedea tarifele reale de expediere pentru articolele curente din coș.
Alți 3 observatori de evenimente sunt proiectați să adauge și să primească date personalizate în timpul exportului și importului tarifelor de expediere. Îi sărim codul în postarea de blog, dar va fi disponibil în depozit cu codul sursă.
Pasul 3. Interfața utilizator
Este timpul să adăugăm atributul nostru la grilă și la forma tarifelor de expediere.
Formă
> app/code/MageWorx/ShippingRateByProductAttribute/view/adminhtml/ui_component/mageworx_shippingrules_rate_form.xml <?xml version="1.0" encoding="UTF-8"?> <!-- /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ --> <form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> <fieldset name="conditions"> <field name="shippingnew"> <argument name="data" xsi:type="array"> <item name="options" xsi:type="object">MageWorx\ShippingRateByProductAttribute\Model\Config\Source\ShippingCategory</item> <item name="config" xsi:type="array"> <item name="label" xsi:type="string" translate="true">Shipping Category</item> <item name="dataType" xsi:type="string">int</item> <item name="formElement" xsi:type="string">select</item> <item name="dataScope" xsi:type="string">shippingnew</item> <item name="source" xsi:type="string">mageworx_shippingrules_rate_form.custom_attributes</item> </item> </argument> </field> </fieldset> </form>
Grilă
> app/code/MageWorx/ShippingRateByProductAttribute/view/adminhtml/ui_component/mageworx_shippingrules_rates_regular_listing.xml <?xml version="1.0"?> <!-- Copyright MageWorx. All rights reserved. See LICENSE.txt for license details. --> <listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Ui/etc/ui_configuration.xsd"> <columns name="mageworx_shippingrules_rates_columns"> <column name="shippingnew"> <argument name="data" xsi:type="array"> <item name="options" xsi:type="object">MageWorx\ShippingRateByProductAttribute\Model\Config\Source\ShippingCategory</item> <item name="config" xsi:type="array"> <item name="filter" xsi:type="string">select</item> <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item> <item name="dataType" xsi:type="string">select</item> <item name="label" xsi:type="string" translate="true">Shipping Category</item> <item name="visible" xsi:type="boolean">true</item> <item name="sortOrder" xsi:type="number">40</item> <item name="editor" xsi:type="string">select</item> </item> </argument> </column> </columns> </listing>
După cum puteți vedea, folosim modelul sursă personalizat în acele fișiere. Să-l creăm. Acesta va încărca un atribut corespunzător (`shippingnew`) și ne va oferi toate valorile disponibile.
> app/code/MageWorx/ShippingRateByProductAttribute/Model/Config/Source/ShippingCategory.php <?php /** * Copyright MageWorx. All rights reserved. * See LICENSE.txt for license details. */ namespace MageWorx\ShippingRateByProductAttribute\Model\Config\Source; use Magento\Framework\Exception\LocalizedException; /** * Class ShippingCategory * * Obtain options for specified product attribute */ class ShippingCategory extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource { /** * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface */ protected $productAttributeRepository; /** * @var \Psr\Log\LoggerInterface */ protected $logger; /** * ShippingCategory constructor. * * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository * @param \Psr\Log\LoggerInterface $logger */ public function __construct( \Magento\Catalog\Api\ProductAttributeRepositoryInterface $productAttributeRepository, \Psr\Log\LoggerInterface $logger ) { $this->productAttributeRepository = $productAttributeRepository; $this->logger = $logger; } /** * @inheritDoc */ public function getAllOptions() { if (empty($this->_options)) { try { /** @var \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute */ $attribute = $this->productAttributeRepository->get('shippingnew'); $this->_options = $attribute->usesSource() ? $attribute->getSource()->getAllOptions() : []; } catch (LocalizedException $localizedException) { $this->logger->critical($localizedException->getLogMessage()); } } return $this->_options; } }
Piesa de cod este destul de simplă. Folosim depozitul de atribute pentru a ne încărca atributul și apoi obținem toate opțiunile (valorile) din acesta. Rețineți că atributul cu codul `shippingnew` trebuie creat în panoul de administrare și trebuie să aibă un tip de intrare drop-down cu opțiuni (valori) predefinite. Puteți face asta din meniul „Magazine > Atribute > Produs”. Nu uitați să adăugați acest atribut la setul de atribute pe care îl utilizați pentru produse.
Când totul este gata, trebuie doar să activăm modulul și să rulăm `setup:upgrade`. Cache-ul va fi golit automat.
Accesați grila de tarife („Magazine > Tarife de livrare”) și veți vedea noua coloană:
Această condiție va fi disponibilă în formularul de tarife:
Dacă setăm setarea „Calcul prețului cu tarife multiple” la „Utilizați rata cu preț maxim” în Metoda de livrare corespunzătoare, tariful cu prețul cel mai mare va fi utilizat în timpul calculării prețului de expediere.
Iată un mic exemplu despre cum funcționează în format de capturi de ecran:
- Configurați-vă produsele
- Stabiliți tarifele
- Configurați algoritmul de calcul al prețului (în formularul pentru metoda de livrare)
- Verificați prețul de livrare pentru metoda corespunzătoare cu produsele selectate în coș (pe front-end).
Acesta nu este tot ceea ce este capabil modulul Shipping Suite. Simțiți-vă liber să vă jucați cu setările pentru a obține rezultatul dorit.
Voi fi bucuros să răspund la orice întrebări! Astfel, nu ezitați să lăsați comentariile dumneavoastră în câmpul dedicat de mai jos.