Cara Menambahkan Bidang Kustom ke Opsi di Ekstensi Opsi Produk Tingkat Lanjut

Diterbitkan: 2020-09-08

Dari artikel ini, Anda akan mempelajari cara membuat bidang "GTIN" untuk opsi kustom produk, menampilkannya di halaman depan produk, dan menampilkannya dalam urutan.

Tanpa basa-basi lagi, mari kita lanjutkan ke panduan langkah demi langkah.

Daftar isi

  • Langkah 1. Buat Modul Baru
  • Langkah 2. Tambahkan Bidang Baru Kami ke Basis Data
  • Langkah #3. Tambahkan Logika untuk Bekerja dengan Backend
  • Langkah #4. Tampilkan Bidang kami di Halaman Produk Front-End
  • Langkah #5. Tambahkan Data Atribut kami ke Detail Pesanan di Database
  • Langkah #6. Tampilkan Data pada Halaman Pesanan di Panel Admin

Langkah 1. Buat Modul Baru

Kami menjelaskan secara rinci cara membuat modul di artikel ini. Jadi, lewati bagian ini, dan langsung ke kode yang Anda perlukan untuk membuat add-on:

1.komposer.json

 { "name": "mageworx/module-optiongtin", "description": "N/A", "require": { "magento/framework" : ">=100.1.0 <101", "magento/module-catalog": ">=101.0.0 <104" }, "type": "magento2-module", "version": "1.0.0", "license": [ "OSL-3.0", "AFL-3.0" ], "autoload": { "files": [ "registration.php" ], "psr-4": { "VendorName\\OptionGtin\\": "" } } }

2.etc/module.xml

 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="VendorName_OptionGtin" setup_version="1.0.0"> <sequence> <module name="Magento_Catalog"/> <module name="MageWorx_OptionBase"/> </sequence> </module> </config>

3.registrasi.php

 <?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'VendorName_OptionGtin', __DIR__ );

Langkah 2. Tambahkan Bidang Baru Kami ke Basis Data

Setelah kita membuat modul kosong, saatnya untuk membuat bidang "GTIN" baru dan menambahkannya ke database di dalam tabel yang sesuai. Saat kami menambahkan bidang untuk nilai opsi, kami membutuhkan tabel "catalog_product_option".

Mari kita buat file berikut:

app/code/VendorName/OptionGtin/Setup/InstallSchema.php

 <?php namespace VendorName\OptionGtin\Setup; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; use Magento\Framework\DB\Ddl\Table; class InstallSchema implements InstallSchemaInterface { public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); $setup->getConnection()->addColumn( $setup->getTable('catalog_product_option'), 'gtin', [ 'type' => Table::TYPE_TEXT, 'nullable' => true, 'default' => null, 'comment' => 'Gtin (added by VendorName Option Gtin)', ] ); $setup->endSetup(); } }

Langkah #3. Tambahkan Logika untuk Bekerja dengan Backend

Kami akan menggunakan mekanisme pool-modifier untuk menambahkan bidang baru kami.

Sekarang, tambahkan file berikut:

app/code/VendorName/OptionGtin/etc/adminhtml/di.xml

 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <virtualType name="MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\Pool"> <arguments> <argument name="modifiers" xsi:type="array"> <item name="mageworx-option-gtin" xsi:type="array"> <item name="class" xsi:type="string">VendorName\OptionGtin\Ui\DataProvider\Product\Form\Modifier\OptionGtin</item> <item name="sortOrder" xsi:type="number">72</item> </item> </argument> </arguments> </virtualType> </config>

Di sini, mari tambahkan pengubah kita ke kumpulan bersama dari ekstensi Opsi Produk Lanjutan―”MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\Pool”. “VendorName\OptionGtin\Ui\DataProvider\Product\Form\Modifier\OptionGtin” adalah pengubah kelas kami.

Kode yang memungkinkan menambahkan bidang kita ke formulir app/code/VendorName/OptionGtin/Ui/DataProvider/Product/Form/Modifier/OptionGtin.php disediakan di bawah ini:

 <?php namespace VendorName\OptionGtin\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions; use Magento\Ui\Component\Form\Element\Input; use Magento\Ui\Component\Form\Element\DataType\Number; use Magento\Ui\Component\Form\Field; use MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\ModifierInterface; class OptionGtin extends AbstractModifier implements ModifierInterface { /** * @var array */ protected $meta = []; /** * {@inheritdoc} */ public function modifyData(array $data) { return $data; } /** * {@inheritdoc} */ public function modifyMeta(array $meta) { $this->meta = $meta; $this->addFields(); return $this->meta; } /** * Adds fields to the meta-data */ protected function addFields() { $groupCustomOptionsName = CustomOptions::GROUP_CUSTOM_OPTIONS_NAME; $optionContainerName = CustomOptions::CONTAINER_OPTION; $commonOptionContainerName = CustomOptions::CONTAINER_COMMON_NAME; // Add fields to the option $optionFeaturesFields = $this->getOptionGtinFieldsConfig(); $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children'] [$optionContainerName]['children'][$commonOptionContainerName]['children'] = array_replace_recursive( $this->meta[$groupCustomOptionsName]['children']['options']['children']['record']['children'] [$optionContainerName]['children'][$commonOptionContainerName]['children'], $optionFeaturesFields ); } /** * The custom option fields config * * @return array */ protected function getOptionGtinFieldsConfig() { $fields['gtin'] = $this->getGtinFieldConfig(); return $fields; } /** * Get gtin field config * * @return array */ protected function getGtinFieldConfig() { return [ 'arguments' => [ 'data' => [ 'config' => [ 'label' => __('GTIN'), 'componentType' => Field::NAME, 'formElement' => Input::NAME, 'dataType' => Number::NAME, 'dataScope' => 'gtin', 'sortOrder' => 65 ], ], ], ]; } /** * Check is current modifier for the product only * * @return bool */ public function isProductScopeOnly() { return false; } /** * Get sort order of modifier to load modifiers in the right order * * @return int */ public function getSortOrder() { return 32; } }

Sekarang, mari coba instal ekstensi dan periksa apakah semuanya ditampilkan:

  • php bin/magento modul: aktifkan VendorName_OptionGtin
  • pengaturan bin/magento php: tingkatkan
  • php bin/cache magento: flush

Bidang baru kami telah berhasil ditambahkan:

Bagaimana Menambahkan Bidang Kustom ke Opsi Produk Tingkat Lanjut | Blog Mageworx

Langkah #4. Tampilkan Bidang kami di Halaman Produk Front-End

Ekstensi Opsi Produk Lanjutan Mageworx sudah memiliki semuanya untuk ditampilkan dan berfungsi dengan atribut yang ditambahkan modul kami. Yang perlu kita lakukan adalah menambahkan atribut baru ke dataset bersama.

Modul MageWorx_OptionBase kami sudah menggunakan metode getExtendedOptionsConfig() . Ini mengumpulkan dan menampilkan semua atribut khusus dalam satu blok di front-end. Buka kelas app/code/MageWorx/OptionBase/Block/Product/View/Options.php untuk melihat bagaimana penerapannya.

Mari kita mulai dengan membuat model dengan atribut kita:

app/code/VendorName/OptionGtin/Model/Attriburte/Option/Gtin.php

 <?php namespace VendorName\OptionGtin\Model\Attribute\Option; use MageWorx\OptionBase\Model\Product\Option\AbstractAttribute; class Gtin extends AbstractAttribute { /** * @return string */ public function getName() { return 'gtin'; } }

Sekarang, gunakan mekanisme "injeksi ketergantungan" dan tambahkan atribut kami ke kumpulan data atribut bersama dari ekstensi Opsi Produk Lanjutan.

app/code/VendorName/OptionGtin/etc/di.xml

 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <!-- Data --> <type name="MageWorx\OptionBase\Model\Product\Option\Attributes"> <arguments> <argument name="data" xsi:type="array"> <item name="gtin" xsi:type="object">VendorName\OptionGtin\Model\Attribute\Option\Gtin</item> </argument> </arguments> </type> </config>

Dengan kata lain, dengan membuka kelas MageWorx\OptionBase\Model\Product\Option\Attributes , Anda akan melihat bahwa itu hanya mengumpulkan semua objek atribut ke dataset bersama.

Untuk menampilkan data atribut "GTIN" baru kami, kami telah memutuskan untuk menggunakan fungsi firstrun() dari app/code/MageWorx/OptionFeatures/view/base/web/js/catalog/product/features.js . Itu sudah memiliki semua implementasi yang diperlukan yang paling sesuai dengan contoh kita. Untuk menghindari menimpa seluruh file, kami akan menerapkan mekanisme "campuran JavaScript", yang akan membantu kami mengubah fungsi yang diperlukan saja.

Buat file berikut, dan tentukan mixin kita di sana: app/code/VendorName/OptionGtin/view/frontend/requirejs-config.js

 var config = { config: { mixins: { 'MageWorx_OptionFeatures/js/catalog/product/features': { 'VendorName_OptionGtin/js/catalog/product/features-gtin-mixin' : true } } } };

Di sini, MageWorx_OptionFeatures/js/catalog/product/features adalah root untuk file kita, metode mana yang perlu kita tulis ulang. VendorName_OptionGtin/js/catalog/product/features-gtin-mixin adalah file, di mana kita akan menulis ulang metodenya.

Jadi, mari kita buat: app/code/VendorName/OptionGtin/view/frontend/web/js/catalog/product/features-gtin-mixin.js

 define([ 'jquery', 'jquery/ui', 'mage/utils/wrapper' ], function ($, wrapper) { 'use strict'; return function (widget) { $.widget('mageworx.optionFeatures', widget, { /** * Triggers one time at first run (from base.js) * @param optionConfig * @param productConfig * @param base * @param self */ firstRun: function firstRun(optionConfig, productConfig, base, self) { //shareable link $('#mageworx_shareable_hint_icon').qtip({ content: { text: this.options.shareable_link_hint_text }, style: { classes: 'qtip-light' }, position: { target: false } }); $('#mageworx_shareable_link').on('click', function () { try { self.copyTextToClipboard(self.getShareableLink(base)); $('.mageworx-shareable-link-container').hide(); $('.mageworx-shareable-link-success-container').show(); setTimeout(function () { $('.mageworx-shareable-link-container').show(); $('.mageworx-shareable-link-success-container').hide(); }, 2000); } catch (error) { console.log('Something goes wrong. Unable to copy'); } }); setTimeout(function () { // Qty input $('.mageworx-option-qty').each(function () { $(this).on('change', function () { var optionInput = $("[data-selector='" + $(this).attr('data-parent-selector') + "']"); optionInput.trigger('change'); }); }); }, 500); // Option\Value Description & tooltip var extendedOptionsConfig = typeof base.options.extendedOptionsConfig != 'undefined' ? base.options.extendedOptionsConfig : {}; for (var option_id in optionConfig) { if (!optionConfig.hasOwnProperty(option_id)) { continue; } var description = extendedOptionsConfig[option_id]['description'], gtin = extendedOptionsConfig[option_id]['gtin'], gtinTitle = "Global Trade Item Number: ", $option = base.getOptionHtmlById(option_id); if (1 > $option.length) { console.log('Empty option container for option with id: ' + option_id); continue; } var $label = $option.find('label'); if(gtin != null && gtin.length > 0) { if ($label.length > 0) { $label .first() .after($('<p class="option-gtin-text"><span>' + gtinTitle + '</span>' + gtin + '</p>')); } else { $label = $option.find('span'); $label .first() .parent() .after($('<p class="option-gtin-text"><span>' + gtinTitle + '</span>' + gtin + '</p>')); } } if (this.options.option_description_enabled && !_.isEmpty(extendedOptionsConfig[option_id]['description'])) { if (this.options.option_description_mode == this.options.option_description_modes.tooltip) { var $element = $option.find('label span') .first(); if ($element.length == 0) { $element = $option.find('fieldset legend span') .first(); } $element.css('border-bottom', '1px dotted black'); $element.qtip({ content: { text: description }, style: { classes: 'qtip-light' }, position: { target: false } }); } else if (this.options.option_description_mode == this.options.option_description_modes.text) { if ($label.length > 0) { $label .first() .after($('<p class="option-description-text">' + description + '</p>')); } else { $label = $option.find('span'); $label .first() .parent() .after($('<p class="option-description-text">' + description + '</p>')); } } else { console.log('Unknown option mode'); } } if (this.options.value_description_enabled) { this._addValueDescription($option, optionConfig, extendedOptionsConfig); } } } }); return $.mageworx.optionFeatures; }; });

Secara umum, kita dapat menjalankan perintah berikut sekarang:

  • php bin/cache magento: flush
  • php bin/magento setup:static-content:deploy (hanya untuk mode produksi)

dan lihat apa yang kita punya. Tapi pertama-tama, tambahkan beberapa gaya ke atribut baru kita dan buat itu terlihat bagus di front-end.

Buat tata letak dan tentukan file gaya baru kami di sana: app/code/VendorName/OptionGtin/view/frontend/layout/catalog_product_view.xml

 <?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <head> <css src="VendorName_OptionGtin::css/gtin.css"/> </head> </page>

Saatnya membuat file gaya: app/code/VendorName/OptionGtin/view/frontend/web/css/gtin.css

 .option-gtin-text span { color: #6cc308; font-weight: 700; }

Sekarang, mari kita jalankan perintah yang dijelaskan sebelumnya dan periksa hasilnya:

Bagaimana Menambahkan Bidang Kustom ke Opsi Produk Tingkat Lanjut | Blog Mageworx

Langkah #5. Tambahkan Data Atribut kami ke Detail Pesanan di Database

Ketika pelanggan melakukan pembelian, pesanan akan dibuat. Detail tentang item yang ditambahkan disertakan dalam tabel sales_order_item . Tabel ini memiliki bidang product_options yang berisi informasi tentang parameter yang dipilih dari item yang ditambahkan. Di situlah kita harus menambahkan data atribut baru kita.

Saat pesanan dibuat, acara sales_quote_address_collect_totals_before dipicu. Kami akan menggunakannya untuk menambahkan data kami ke opsi produk.

Mari kita definisikan acara dengan membuat: app/code/VendorName/OptionGtin/etc/events.xml

 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> <event name="sales_quote_address_collect_totals_before"> <observer name="mageworx_optiongtin_add_gtin_to_order" instance="VendorName\OptionGtin\Observer\AddGtinToOrder" /> </event> </config>

Kemudian, buat pengamat kita: app/code/VendorName/OptionGtin/Observer/AddGtinToOrder.php

 <?php namespace VendorName\OptionGtin\Observer; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\Catalog\Model\ProductRepository as ProductRepository; use MageWorx\OptionBase\Helper\Data as BaseHelper; class AddGtinToOrder implements ObserverInterface { /** * @var BaseHelper */ protected $baseHelper; protected $productRepository; /** * AddGtinToOrder constructor. * @param BaseHelper $baseHelper * @param ProductRepository $productRepository */ public function __construct( BaseHelper $baseHelper, ProductRepository $productRepository ) { $this->baseHelper = $baseHelper; $this->productRepository = $productRepository; } /** * Add product to quote action * Processing: gtin * * @param Observer $observer * @return $this */ public function execute(Observer $observer) { $quoteItems = $observer->getQuote()->getAllItems(); /** @var \Magento\Quote\Model\Quote\Item $quoteItem */ foreach ($quoteItems as $quoteItem) { $buyRequest = $quoteItem->getBuyRequest(); $optionIds = array_keys($buyRequest->getOptions()); $productOptions = $this->productRepository->getById($buyRequest->getProduct())->getOptions(); $quoteItemOptionGtins = []; $optionGtins = []; foreach ($productOptions as $option) { if ($option->getGtin()) { $quoteItemOptionGtins[$option->getOptionId()] = $option->getGtin(); } } foreach ($optionIds as $optionId) { $optionGtins[$optionId] = $optionId; } $optionGtins = array_intersect_key($quoteItemOptionGtins, $optionGtins); $infoBuyRequest = $quoteItem->getOptionByCode('info_buyRequest'); $buyRequest->setData('gtin', $optionGtins); $infoBuyRequest->setValue($this->baseHelper->encodeBuyRequestValue($buyRequest->getData())); $quoteItem->addOption($infoBuyRequest); } } }

Di sini, dengan bantuan pengamat, kami mendapatkan daftar semua item dalam pesanan dan menambahkan data atribut "GTIN" kami ke apa yang disebut $infoBuyRequest .

Untuk memeriksa apakah semuanya telah dilakukan dengan benar, buat pesanan dengan produk, opsi mana yang memiliki data "GTIN". Anda dapat memeriksa apakah data telah ditambahkan di sales_order_item table -> product_options field:

Bagaimana Menambahkan Bidang Kustom ke Opsi Produk Tingkat Lanjut | Blog Mageworx

Langkah #6. Tampilkan Data pada Halaman Pesanan di Panel Admin

Ada berbagai cara untuk menampilkan informasi yang diperlukan dalam template siap pakai. Misalnya, menggunakan "js". Kami bekerja dengan "js" di artikel ini. Mari bekerja dengan template itu sendiri untuk perubahan, dan coba tulis ulang!

Ubah app/code/VendorName/OptionGtin/etc/adminhtml/di.xml yang telah dibuat sebelumnya dengan menambahkan plugin di sana:

 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <virtualType name="MageWorx\OptionBase\Ui\DataProvider\Product\Form\Modifier\Pool"> <arguments> <argument name="modifiers" xsi:type="array"> <item name="mageworx-option-gtin" xsi:type="array"> <item name="class" xsi:type="string">VendorName\OptionGtin\Ui\DataProvider\Product\Form\Modifier\OptionGtin</item> <item name="sortOrder" xsi:type="number">72</item> </item> </argument> </arguments> </virtualType> <!-- Plugins--> <type name="Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn"> <plugin name="mageworx-optiongtin-add-default-column" type="VendorName\OptionGtin\Plugin\AddDefaultColumn" sortOrder="5" disabled="false" /> </type> </config>

Buat plugin itu sendiri:

app/code/VendorName/OptionGtin/Plugin/AddDefaultColumn.php

 <?php namespace VendorName\OptionGtin\Plugin; class AddDefaultColumn { /** * @param \Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn $subject * @param $result * @return array */ public function afterGetOrderOptions(\Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn $subject, $result) { if ($options = $subject->getItem()->getProductOptions()) { if (isset($result)) { foreach ($result as &$option) { if (array_key_exists($option['option_id'], $options['info_buyRequest']['gtin'])) { $option['gtin'] = $options['info_buyRequest']['gtin'][$option['option_id']]; } } } } return $result; } }

Plugin ini menambahkan informasi tentang atribut baru kami untuk opsi pesanan, yang datanya ada.

vendor/magento/module-sales/view/adminhtml/templates/items/column/name.phtml bertanggung jawab untuk menampilkan informasi tentang opsi produk pada halaman pesanan di panel admin.

Mari kita tulis ulang untuk menampilkan "GTIN" kita. Untuk itu, kita perlu menulis ulang blok “column_name”, atau lebih tepatnya template-nya. Buat tata letak dan templat:

app/code/VendorName/OptionGtin/view/adminhtml/layout/sales_order_view.xml

 <?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="column_name"> <action method="setTemplate"> <argument name="template" xsi:type="string">VendorName_OptionGtin::items/column/name.phtml</argument> </action> </referenceBlock> </body> </page>

app/code/VendorName/OptionGtin/view/adminhtml/templates/items/column/name.phtml

 <?php /* @var $block \Magento\Sales\Block\Adminhtml\Items\Column\Name */ ?> <?php if ($_item = $block->getItem()) : ?> <div class="product-title"> <?= $block->escapeHtml($_item->getName()) ?> </div> <div class="product-sku-block"> <span><?= $block->escapeHtml(__('SKU'))?>:</span> <?= /* @noEscape */ implode('<br />', $this->helper(\Magento\Catalog\Helper\Data::class)->splitSku($block->escapeHtml($block->getSku()))) ?> </div> <?php if ($block->getOrderOptions()) : ?> <dl class="item-options"> <?php foreach ($block->getOrderOptions() as $_option) : ?> <dt><?= $block->escapeHtml($_option['label']) ?>:</dt> <dd> <?php if (isset($_option['custom_view']) && $_option['custom_view']) : ?> <?= /* @noEscape */ $block->getCustomizedOptionValue($_option) ?> <?php else : ?> <?php $optionValue = $block->getFormattedOption($_option['value']); ?> <?php $dots = 'dots' . uniqid(); ?> <?php $ . uniqid(); ?> <?= $block->escapeHtml($optionValue['value'], ['a', 'br']) ?><?php if (isset($optionValue['remainder']) && $optionValue['remainder']) : ?> <span> ...</span> <span><?= $block->escapeHtml($optionValue['remainder'], ['a']) ?></span> <script> require(['prototype'], function() { $('<?= /* @noEscape */ $id; ?>').hide(); $('<?= /* @noEscape */ $id; ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $id; ?>').show();}); $('<?= /* @noEscape */ $id; ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $dots; ?>').hide();}); $('<?= /* @noEscape */ $id; ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $id; ?>').hide();}); $('<?= /* @noEscape */ $id; ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $dots; ?>').show();}); }); </script> <?php endif; ?> <?php endif; ?> </dd> <dt> <?php if (isset($_option['gtin']) && $_option['gtin']) : ?> <span>GTIN:</span> <?php endif; ?> </dt> <dd> <?php if (isset($_option['gtin']) && $_option['gtin']) : ?> <span> <?= $block->escapeHtml($_option['gtin']) ?></span> <?php endif; ?> </dd> <?php endforeach; ?> </dl> <?php endif; ?> <?= $block->escapeHtml($_item->getDescription()) ?> <?php endif; ?>

Jika semuanya telah dilakukan dengan benar, dibersihkan, dan dikompilasi, maka Anda akan melihat hasil berikut:

Bagaimana Menambahkan Bidang Kustom ke Opsi Produk Tingkat Lanjut | Blog Mageworx

Kami harap Anda menemukan artikel ini bermanfaat. Jika Anda memiliki kesulitan atau masalah, jangan ragu untuk memberi tahu kami di kolom komentar di bawah.

Pesan Demo Langsung dengan Mageworx