diff --git a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.js b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.js index 1cfb57b0..b1255bee 100644 --- a/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.js +++ b/ecommerce_integrations/shopify/page/shopify_import_products/shopify_import_products.js @@ -1,376 +1,376 @@ -frappe.provide('shopify'); - -frappe.pages['shopify-import-products'].on_page_load = function (wrapper) { - let page = frappe.ui.make_app_page({ - parent: wrapper, - title: 'Import Shopify Products', - single_column: true - }); - - new shopify.ProductImporter(wrapper); - -} - -shopify.ProductImporter = class { - - constructor(wrapper) { - - this.wrapper = $(wrapper).find('.layout-main-section'); - this.page = wrapper.page; - this.init(); - this.syncRunning = false; - - } - - init() { - frappe.run_serially([ - () => this.addMarkup(), - () => this.fetchProductCount(), - () => this.addTable(), - () => this.checkSyncStatus(), - () => this.listen(), - ]); - } - - async checkSyncStatus() { - - const { message: jobs } = await frappe.call({ method: 'frappe.core.page.background_jobs.background_jobs.get_info' }); - - this.syncRunning = jobs.find(job => job.job_name == 'shopify.job.sync.all.products') !== undefined; - - if (this.syncRunning) { - this.toggleSyncAllButton(); - this.logSync(); - } - - } - - addMarkup() { - - const _markup = $(` -
-
-
-
Products in Shopify
-
-
Loading...
-
- -
-
-
-
-
-
Synchronization Details
-
-
- -
-
-
-

-

-

in Shopify

-
-
-

-

-

in ERPNext

-
-
-

-

-

Synced

-
-
-
-
- - - -
-
-
- `); - - this.wrapper.append(_markup); - - } - - async fetchProductCount() { - - try { - const { message: { erpnextCount, shopifyCount, syncedCount } } = await frappe.call({ method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.get_product_count' }); - - this.wrapper.find('#count-products-shopify').text(shopifyCount); - this.wrapper.find('#count-products-erpnext').text(erpnextCount); - this.wrapper.find('#count-products-synced').text(syncedCount); - - } catch (error) { - frappe.throw(__('Error fetching product count.')); - } - - } - - async addTable() { - - const listElement = this.wrapper.find('#shopify-product-list')[0]; - this.shopifyProductTable = new frappe.DataTable(listElement, { - columns: [ - // { - // name: 'Image', - // align: 'center', - // }, - { - name: 'ID', - align: 'left', - editable: false, - focusable: false, - }, - { - name: 'Name', - editable: false, - focusable: false, - }, - { - name: 'SKUs', - editable: false, - focusable: false, - }, - { - name: 'Status', - align: 'center', - editable: false, - focusable: false, - }, - { - name: 'Action', - align: 'center', - editable: false, - focusable: false, - }, - ], - data: await this.fetchShopifyProducts(), - layout: 'fixed', - }); - - this.wrapper.find('.shopify-datatable-footer').show(); - - } - - async fetchShopifyProducts(from_ = null) { - - try { - const { message: { products, nextUrl, prevUrl } } = await frappe.call({ method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.get_shopify_products', args: { from_ } }); - this.nextUrl = nextUrl; - this.prevUrl = prevUrl; - - const shopifyProducts = products.map((product) => ({ - // 'Image': product.image && product.image.src && ``, - 'ID': product.id, - 'Name': product.title, - 'SKUs': product.variants && product.variants.map(a => `${a.sku}`).join(', '), - 'Status': this.getProductSyncStatus(product.synced), - 'Action': !product.synced ? - `` : - ``, - })); - - return shopifyProducts; - } catch (error) { - frappe.throw(__('Error fetching products.')); - } - - } - - getProductSyncStatus(status) { - - return status ? - `Synced` : - `Not Synced`; - - } - - listen() { - - // sync a product from table - this.wrapper.on('click', '.btn-sync', e => { - - const _this = $(e.currentTarget); - - _this.prop('disabled', true).text('Syncing...'); - - const product = _this.attr('data-product'); - this.syncProduct(product) - .then(status => { - - if (!status) { - frappe.throw(__('Error syncing product')); - _this.prop('disabled', false).text('Sync'); - return; - } - - _this.parents('.dt-row') - .find('.indicator-pill') - .replaceWith(this.getProductSyncStatus(true)); - - _this.replaceWith(``); - - }); - - }); - - this.wrapper.on('click', '.btn-resync', e => { - const _this = $(e.currentTarget); - - _this.prop('disabled', true).text('Syncing...'); - - const product = _this.attr('data-product'); - this.resyncProduct(product) - .then(status => { - - if (!status) { - frappe.throw(__('Error syncing product')); - return; - } - - _this.parents('.dt-row') - .find('.indicator-pill') - .replaceWith(this.getProductSyncStatus(true)); - - _this.prop('disabled', false).text('Re-sync'); - - }) - .catch(ex => { - _this.prop('disabled', false).text('Re-sync'); - frappe.throw(__('Error syncing Product')); - }); - }); - - // pagination - this.wrapper.on('click', '.btn-prev,.btn-next', e => this.switchPage(e)); - - // sync all products - this.wrapper.on('click', '#btn-sync-all', e => this.syncAll(e)); - - } - - async syncProduct(product) { - - const { message: status } = await frappe.call({ - method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.sync_product', - args: { product }, - }); - - if (status) - this.fetchProductCount(); - - return status; - - } - - async resyncProduct(product) { - - const { message: status } = await frappe.call({ - method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.resync_product', - args: { product }, - }); - - if (status) - this.fetchProductCount(); - - return status; - - } - - async switchPage({ currentTarget }) { - - const _this = $(currentTarget); - - $('.btn-paginate').prop('disabled', true); - this.shopifyProductTable.showToastMessage('Loading...'); - - const newProducts = await this.fetchShopifyProducts( - _this.hasClass('btn-next') ? this.nextUrl : this.prevUrl - ); - - this.shopifyProductTable.refresh(newProducts); - - $('.btn-paginate').prop('disabled', false); - this.shopifyProductTable.clearToastMessage(); - - } - - syncAll() { - - this.checkSyncStatus(); - this.toggleSyncAllButton(); - - if (this.syncRunning) { - frappe.msgprint(__('Sync already in progress')); - } else { - frappe.call({ method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.import_all_products' }) - } - - // sync progress - this.logSync(); - - } - - logSync() { - - const _log = $('#shopify-sync-log'); - _log.parents('.card').show(); - _log.text(''); // clear logs - - // define counters here to prevent calling jquery every time - const _syncedCounter = $('#count-products-synced'); - const _erpnextCounter = $('#count-products-erpnext'); - - frappe.realtime.on('shopify.key.sync.all.products', ({ message, synced, done, error }) => { - - message = `
${message}
`; - _log.append(message); - _log.scrollTop(_log[0].scrollHeight) - - if (synced) this.updateSyncedCount(_syncedCounter, _erpnextCounter); - - if (done) { - frappe.realtime.off('shopify.key.sync.all.products'); - this.toggleSyncAllButton(false); - this.fetchProductCount(); - this.syncRunning = false; - } - - }) - - } - - toggleSyncAllButton(disable = true) { - - const btn = $('#btn-sync-all'); - - const _toggleClass = d => d ? 'btn-success' : 'btn-primary'; - const _toggleText = () => disable ? 'Syncing...' : 'Sync Products'; - - btn.prop('disabled', disable) - .addClass(_toggleClass(disable)) - .removeClass(_toggleClass(!disable)) - .text(_toggleText()); - - } - - updateSyncedCount(_syncedCounter, _erpnextCounter) { - let _synced = parseFloat(_syncedCounter.text()); - let _erpnext = parseFloat(_erpnextCounter.text()); - - _syncedCounter.text(_synced + 1); - _erpnextCounter.text(_erpnext + 1); - - } -} +frappe.provide('shopify'); + +frappe.pages['shopify-import-products'].on_page_load = function (wrapper) { + let page = frappe.ui.make_app_page({ + parent: wrapper, + title: 'Import Shopify Products', + single_column: true + }); + + new shopify.ProductImporter(wrapper); + +} + +shopify.ProductImporter = class { + + constructor(wrapper) { + + this.wrapper = $(wrapper).find('.layout-main-section'); + this.page = wrapper.page; + this.init(); + this.syncRunning = false; + + } + + init() { + frappe.run_serially([ + () => this.addMarkup(), + () => this.fetchProductCount(), + () => this.addTable(), + () => this.checkSyncStatus(), + () => this.listen(), + ]); + } + + async checkSyncStatus() { + + const { message: jobs } = await frappe.db.get_list("RQ Job", {filters: {"status": ("in", ("queued", "started"))}}); + + this.syncRunning = jobs.find(job => job.job_name == 'shopify.job.sync.all.products') !== undefined; + + if (this.syncRunning) { + this.toggleSyncAllButton(); + this.logSync(); + } + + } + + addMarkup() { + + const _markup = $(` +
+
+
+
Products in Shopify
+
+
Loading...
+
+ +
+
+
+
+
+
Synchronization Details
+
+
+ +
+
+
+

-

+

in Shopify

+
+
+

-

+

in ERPNext

+
+
+

-

+

Synced

+
+
+
+
+ + + +
+
+
+ `); + + this.wrapper.append(_markup); + + } + + async fetchProductCount() { + + try { + const { message: { erpnextCount, shopifyCount, syncedCount } } = await frappe.call({ method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.get_product_count' }); + + this.wrapper.find('#count-products-shopify').text(shopifyCount); + this.wrapper.find('#count-products-erpnext').text(erpnextCount); + this.wrapper.find('#count-products-synced').text(syncedCount); + + } catch (error) { + frappe.throw(__('Error fetching product count.')); + } + + } + + async addTable() { + + const listElement = this.wrapper.find('#shopify-product-list')[0]; + this.shopifyProductTable = new frappe.DataTable(listElement, { + columns: [ + // { + // name: 'Image', + // align: 'center', + // }, + { + name: 'ID', + align: 'left', + editable: false, + focusable: false, + }, + { + name: 'Name', + editable: false, + focusable: false, + }, + { + name: 'SKUs', + editable: false, + focusable: false, + }, + { + name: 'Status', + align: 'center', + editable: false, + focusable: false, + }, + { + name: 'Action', + align: 'center', + editable: false, + focusable: false, + }, + ], + data: await this.fetchShopifyProducts(), + layout: 'fixed', + }); + + this.wrapper.find('.shopify-datatable-footer').show(); + + } + + async fetchShopifyProducts(from_ = null) { + + try { + const { message: { products, nextUrl, prevUrl } } = await frappe.call({ method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.get_shopify_products', args: { from_ } }); + this.nextUrl = nextUrl; + this.prevUrl = prevUrl; + + const shopifyProducts = products.map((product) => ({ + // 'Image': product.image && product.image.src && ``, + 'ID': product.id, + 'Name': product.title, + 'SKUs': product.variants && product.variants.map(a => `${a.sku}`).join(', '), + 'Status': this.getProductSyncStatus(product.synced), + 'Action': !product.synced ? + `` : + ``, + })); + + return shopifyProducts; + } catch (error) { + frappe.throw(__('Error fetching products.')); + } + + } + + getProductSyncStatus(status) { + + return status ? + `Synced` : + `Not Synced`; + + } + + listen() { + + // sync a product from table + this.wrapper.on('click', '.btn-sync', e => { + + const _this = $(e.currentTarget); + + _this.prop('disabled', true).text('Syncing...'); + + const product = _this.attr('data-product'); + this.syncProduct(product) + .then(status => { + + if (!status) { + frappe.throw(__('Error syncing product')); + _this.prop('disabled', false).text('Sync'); + return; + } + + _this.parents('.dt-row') + .find('.indicator-pill') + .replaceWith(this.getProductSyncStatus(true)); + + _this.replaceWith(``); + + }); + + }); + + this.wrapper.on('click', '.btn-resync', e => { + const _this = $(e.currentTarget); + + _this.prop('disabled', true).text('Syncing...'); + + const product = _this.attr('data-product'); + this.resyncProduct(product) + .then(status => { + + if (!status) { + frappe.throw(__('Error syncing product')); + return; + } + + _this.parents('.dt-row') + .find('.indicator-pill') + .replaceWith(this.getProductSyncStatus(true)); + + _this.prop('disabled', false).text('Re-sync'); + + }) + .catch(ex => { + _this.prop('disabled', false).text('Re-sync'); + frappe.throw(__('Error syncing Product')); + }); + }); + + // pagination + this.wrapper.on('click', '.btn-prev,.btn-next', e => this.switchPage(e)); + + // sync all products + this.wrapper.on('click', '#btn-sync-all', e => this.syncAll(e)); + + } + + async syncProduct(product) { + + const { message: status } = await frappe.call({ + method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.sync_product', + args: { product }, + }); + + if (status) + this.fetchProductCount(); + + return status; + + } + + async resyncProduct(product) { + + const { message: status } = await frappe.call({ + method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.resync_product', + args: { product }, + }); + + if (status) + this.fetchProductCount(); + + return status; + + } + + async switchPage({ currentTarget }) { + + const _this = $(currentTarget); + + $('.btn-paginate').prop('disabled', true); + this.shopifyProductTable.showToastMessage('Loading...'); + + const newProducts = await this.fetchShopifyProducts( + _this.hasClass('btn-next') ? this.nextUrl : this.prevUrl + ); + + this.shopifyProductTable.refresh(newProducts); + + $('.btn-paginate').prop('disabled', false); + this.shopifyProductTable.clearToastMessage(); + + } + + syncAll() { + + this.checkSyncStatus(); + this.toggleSyncAllButton(); + + if (this.syncRunning) { + frappe.msgprint(__('Sync already in progress')); + } else { + frappe.call({ method: 'ecommerce_integrations.shopify.page.shopify_import_products.shopify_import_products.import_all_products' }) + } + + // sync progress + this.logSync(); + + } + + logSync() { + + const _log = $('#shopify-sync-log'); + _log.parents('.card').show(); + _log.text(''); // clear logs + + // define counters here to prevent calling jquery every time + const _syncedCounter = $('#count-products-synced'); + const _erpnextCounter = $('#count-products-erpnext'); + + frappe.realtime.on('shopify.key.sync.all.products', ({ message, synced, done, error }) => { + + message = `
${message}
`; + _log.append(message); + _log.scrollTop(_log[0].scrollHeight) + + if (synced) this.updateSyncedCount(_syncedCounter, _erpnextCounter); + + if (done) { + frappe.realtime.off('shopify.key.sync.all.products'); + this.toggleSyncAllButton(false); + this.fetchProductCount(); + this.syncRunning = false; + } + + }) + + } + + toggleSyncAllButton(disable = true) { + + const btn = $('#btn-sync-all'); + + const _toggleClass = d => d ? 'btn-success' : 'btn-primary'; + const _toggleText = () => disable ? 'Syncing...' : 'Sync Products'; + + btn.prop('disabled', disable) + .addClass(_toggleClass(disable)) + .removeClass(_toggleClass(!disable)) + .text(_toggleText()); + + } + + updateSyncedCount(_syncedCounter, _erpnextCounter) { + let _synced = parseFloat(_syncedCounter.text()); + let _erpnext = parseFloat(_erpnextCounter.text()); + + _syncedCounter.text(_synced + 1); + _erpnextCounter.text(_erpnext + 1); + + } +}