From f585197045428bcbed6af2174abaf432adf7c8dd Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 23 Jul 2018 11:55:07 +0530 Subject: [PATCH 001/154] Cleanup homepage --- erpnext/public/js/hub/hub_listing.js | 233 +++++++++++++++++++-------- erpnext/public/less/hub.less | 67 +++++++- 2 files changed, 232 insertions(+), 68 deletions(-) diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 0ff79708d7..9ac1b848ea 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -22,7 +22,7 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { const title = this.page_title; let iconHtml = ``; let titleHtml = `${title}`; - this.page.set_title(iconHtml + titleHtml, '', false, title); + this.page.set_title(titleHtml, '', false, title); } setup_fields() { @@ -36,6 +36,8 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { }); } + setup_filter_area() { } + get_meta() { return new Promise(resolve => frappe.call('erpnext.hub_node.get_meta', {doctype: this.doctype}, resolve)); @@ -72,12 +74,12 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { } setup_sort_selector() { - this.sort_selector = new frappe.ui.SortSelector({ - parent: this.filter_area.$filter_list_wrapper, - doctype: this.doctype, - args: this.order_by, - onchange: () => this.refresh(true) - }); + // this.sort_selector = new frappe.ui.SortSelector({ + // parent: this.filter_area.$filter_list_wrapper, + // doctype: this.doctype, + // args: this.order_by, + // onchange: () => this.refresh(true) + // }); } setup_view() { @@ -88,6 +90,21 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { this.page.fields_dict[field].set_value(value); } } + + const $hub_search = $(` +
+ +
` + ); + this.$frappe_list.prepend($hub_search); + const $search_input = $hub_search.find('input'); + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + this.search_value = $search_input.val(); + this.refresh(); + } + }, 300)); } get_args() { @@ -146,7 +163,10 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { if (this.start === 0) { // ${this.getHeaderHtml()} this.$result.html(` -
+
+
+ Recently Published +
${html}
`); @@ -366,23 +386,23 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { } bootstrap_data(response) { - let companies = response.companies.map(d => d.name); - this.custom_filter_configs = [ - { - fieldtype: 'Autocomplete', - label: __('Select Company'), - condition: 'like', - fieldname: 'company_name', - options: companies - }, - { - fieldtype: 'Link', - label: __('Select Country'), - options: 'Country', - condition: 'like', - fieldname: 'country' - } - ]; + // let companies = response.companies.map(d => d.name); + // this.custom_filter_configs = [ + // { + // fieldtype: 'Autocomplete', + // label: __('Select Company'), + // condition: 'like', + // fieldname: 'company_name', + // options: companies + // }, + // { + // fieldtype: 'Link', + // label: __('Select Country'), + // options: 'Country', + // condition: 'like', + // fieldname: 'country' + // } + // ]; } prepareFormFields() { @@ -414,6 +434,10 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { setup_side_bar() { super.setup_side_bar(); + this.setup_new_sidebar(); + + return; + let $pitch = $(`
+
  • + Browse +
  • +
  • + Favorites +
  • +
  • + Become a seller +
  • + + `); + + frappe.call('erpnext.hub_node.get_categories') + .then(r => { + const categories = r.message.map(d => d.value).sort(); + const sidebar_items = [ + `
  • + Category +
  • `, + `
  • + All +
  • `, + ...categories.map(category => ` +
  • + ${category} +
  • + `) + ]; + + this.sidebar.$sidebar.append(` +
      + ${sidebar_items.join('')} +
    + `); + }); + } + update_category(label) { this.current_category = (label=='All Categories') ? undefined : label; this.refresh(); } get_filters_for_args() { - if(!this.filter_area) return; - let filters = {}; - this.filter_area.get().forEach(f => { - let field = f[1] !== 'name' ? f[1] : 'item_name'; - filters[field] = [f[2], f[3]]; - }); - if(this.current_category) { - filters['hub_category'] = this.current_category; + const filter = {}; + + if (this.search_value) { + filter.item_name = ['like', `%${this.search_value}%`]; } - return filters; + + filter.image = ['like', 'http%']; + return filter; + + // if(!this.filter_area) return; + // let filters = {}; + // this.filter_area.get().forEach(f => { + // let field = f[1] !== 'name' ? f[1] : 'item_name'; + // filters[field] = [f[2], f[3]]; + // }); + // if(this.current_category) { + // filters['hub_category'] = this.current_category; + // } + // return filters; } update_data(r) { @@ -485,11 +558,14 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { }); } - item_html(item) { + item_html(item, index) { item._name = encodeURI(item.name); const encoded_name = item._name; const title = strip_html(item[this.meta.title_field || 'name']); - const _class = !item[this.imageFieldName] ? 'no-image' : ''; + + const img_url = item[this.imageFieldName]; + const no_image = !img_url; + const _class = no_image ? 'no-image' : ''; const route = `#Hub/Item/${item.hub_item_code}`; const company_name = item['company_name']; @@ -507,41 +583,66 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { if(i >= ratingAverage) starClass = 'fa-star-o'; ratingHtml += ``; } + let dot_spacer = ''; + let subtitle = ''; + subtitle += comment_when(item.creation); + subtitle += dot_spacer; + + if (ratingAverage > 0) { + subtitle += ratingAverage + ``; + subtitle += dot_spacer; + } + subtitle += company_name; let item_html = ` -
    -
    -
    - - ${title} - -
    -
    - ${ratingHtml} - (${reviewLength}) -
    - -
    -
    - -
    - - +
    +
    +
    + - +
    + ${ratingHtml} + (${reviewLength}) +
    + +
    +
    +
    + `; + item_html = ` +
    +
    +
    +
    ${title}
    +
    ${subtitle}
    +
    +
    + +
    +
    `; diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index bdca28f1e7..ef08fde270 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -6,8 +6,71 @@ body[data-route^="Hub/"] { height: 40px; } - .hub-page-title { - margin-left: 10px; + .layout-main-section { + border: none; + } + + .frappe-list { + padding-top: 25px; + font-size: @text-medium; + + @media (max-width: @screen-xs) { + padding-left: 20px; + padding-right: 20px; + } + } + + .hub-card { + border: 1px solid @border-color; + margin-bottom: 25px; + border-radius: 4px; + overflow: hidden; + cursor: pointer; + } + + .hub-card-header { + padding: 12px 15px; + height: 60px; + } + + .hub-card-body { + height: 200px; + } + + .hub-card-image { + min-width: 100%; + width: 100%; + } + + .hub-search-container { + margin-bottom: 20px; + + input { + font-size: @text-medium; + height: 32px; + } + } + + .hub-sidebar { + padding-top: 25px; + padding-right: 15px; + } + + .hub-sidebar-group { + margin-bottom: 10px; + } + + .hub-sidebar-item { + padding: 5px 8px; + margin-bottom: 3px; + border-radius: 4px; + border: 1px solid transparent; + + cursor: pointer; + + &.active, &:hover:not(.is-title) { + border-color: @border-color; + } } .img-wrapper { From ea2d1b0a191ac95d12845efeb4710508d2260d34 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 23 Jul 2018 18:44:46 +0530 Subject: [PATCH 002/154] New page: marketplace - subpages: home, favourites, item, category --- erpnext/hub_node/__init__.py | 2 +- erpnext/public/js/hub/hub_factory.js | 32 +- erpnext/public/js/hub/hub_listing.js | 459 +++++++++++++++++++++++++-- erpnext/public/less/hub.less | 52 ++- 4 files changed, 503 insertions(+), 42 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 65b1386466..a5e4607122 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -21,7 +21,7 @@ def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=No response = connection.get_list(doctype, limit_start=start, limit_page_length=limit, - filters=filters, fields=fields) + filters=filters, fields=['name']) # Bad, need child tables in response listing = [] diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js index 6451e1d6a9..d54787af36 100644 --- a/erpnext/public/js/hub/hub_factory.js +++ b/erpnext/public/js/hub/hub_factory.js @@ -1,6 +1,32 @@ frappe.provide('erpnext.hub.pages'); -frappe.views.HubFactory = frappe.views.Factory.extend({ +frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views.Factory { + show() { + const page_name = frappe.get_route_str(); + + if (frappe.pages.marketplace) { + frappe.container.change_to('marketplace'); + erpnext.hub.marketplace.refresh(); + } else { + this.make('marketplace'); + } + } + + make(page_name) { + const assets = [ + '/assets/erpnext/js/hub/hub_listing.js' + ]; + + frappe.require(assets, () => { + erpnext.hub.marketplace = new erpnext.hub.Marketplace({ + parent: this.make_page(true, page_name) + }); + }); + } +} + +frappe.views.HubFactory = class HubFactory extends frappe.views.Factory { + make(route) { const page_name = frappe.get_route_str(); const page = route[1]; @@ -60,7 +86,7 @@ frappe.views.HubFactory = frappe.views.Factory.extend({ window.hub_page = erpnext.hub.pages[page_name]; } }); - }, + } render_offline_card() { let html = `
    @@ -77,4 +103,4 @@ frappe.views.HubFactory = frappe.views.Factory.extend({ return; } -}); +} diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 9ac1b848ea..a6a9ac975b 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -1,5 +1,404 @@ frappe.provide('erpnext.hub'); +erpnext.hub.Marketplace = class Marketplace { + constructor({ parent }) { + this.$parent = $(parent); + this.page = parent.page; + + this.setup_header(); + this.make_sidebar(); + this.make_body(); + this.setup_events(); + this.refresh(); + } + + setup_header() { + this.page.set_title(__('Marketplace')); + } + + setup_events() { + this.$parent.on('click', '[data-route]', (e) => { + const $target = $(e.currentTarget); + const route = $target.data().route; + frappe.set_route(route); + }); + } + + make_sidebar() { + this.$sidebar = this.$parent.find('.layout-side-section'); + + this.$sidebar.append(` +
      +
    • + ${__('Browse')} +
    • +
    • + ${__('Favorites')} +
    • +
    • + ${__('Become a seller')} +
    • +
    + `); + + this.make_sidebar_categories(); + } + + make_sidebar_categories() { + frappe.call('erpnext.hub_node.get_categories') + .then(r => { + const categories = r.message.map(d => d.value).sort(); + const sidebar_items = [ + `
  • + ${__('Category')} +
  • `, + `
  • + ${__('All')} +
  • `, + ...categories.map(category => ` +
  • + ${__(category)} +
  • + `) + ]; + + this.$sidebar.append(` +
      + ${sidebar_items.join('')} +
    + `); + + this.update_sidebar(); + }); + } + + make_body() { + this.$body = this.$parent.find('.layout-main-section'); + } + + update_sidebar() { + const route = frappe.get_route_str(); + const $sidebar_item = this.$sidebar.find(`[data-route="${route}"]`); + + const $siblings = this.$sidebar.find('[data-route]'); + $siblings.removeClass('active').addClass('text-muted'); + + $sidebar_item.addClass('active').removeClass('text-muted'); + } + + refresh() { + const route = frappe.get_route(); + this.subpages = this.subpages || {}; + + for (let page in this.subpages) { + this.subpages[page].hide(); + } + + if (route[1] === 'home' && !this.subpages.home) { + this.subpages.home = new erpnext.hub.Home(this.$body); + } + + if (route[1] === 'favourites' && !this.subpages.favourites) { + this.subpages.favourites = new erpnext.hub.Favourites(this.$body); + } + + if (route[1] === 'category' && route[2] && !this.subpages.category) { + this.subpages.category = new erpnext.hub.Category(this.$body); + } + + if (route[1] === 'item' && route[2] && !this.subpages.item) { + this.subpages.item = new erpnext.hub.Item(this.$body); + } + + if (!Object.keys(this.subpages).includes(route[1])) { + frappe.show_not_found(); + return; + } + + this.update_sidebar(); + frappe.utils.scroll_to(0); + this.subpages[route[1]].show(); + } +} + +class SubPage { + constructor(parent) { + this.$parent = $(parent); + this.make_wrapper(); + } + + make_wrapper() { + const page_name = frappe.get_route()[1]; + this.$wrapper = $(`
    `).appendTo(this.$parent); + this.hide(); + } + + show() { + this.refresh(); + this.$wrapper.show(); + } + + hide() { + this.$wrapper.hide(); + } +} + +erpnext.hub.Home = class Home extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.make_search_bar(); + } + + refresh() { + this.get_items_and_render(); + } + + get_items_and_render() { + this.get_items() + .then(r => { + erpnext.hub.hub_item_cache = r.message; + this.render(r.message); + }); + } + + get_items() { + return frappe.call('erpnext.hub_node.get_list', { + doctype: 'Hub Item', + filters: { + image: ['like', 'http%'] + } + }); + } + + make_search_bar() { + const $search = $(` +
    + +
    ` + ); + this.$wrapper.append($search); + const $search_input = $search.find('input'); + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + this.search_value = $search_input.val(); + this.get_items_and_render(); + } + }, 300)); + } + + render(items) { + const html = get_item_card_container_html(items, __('Recently Published')); + this.$wrapper.html(html) + } +} + +erpnext.hub.Favourites = class Favourites extends SubPage { + refresh() { + this.get_favourites() + .then(r => { + this.render(r.message); + }); + } + + get_favourites() { + return frappe.call('erpnext.hub_node.get_item_favourites'); + } + + render(items) { + const html = get_item_card_container_html(items, __('Favourites')); + this.$wrapper.html(html) + } +} + +erpnext.hub.Category = class Category extends SubPage { + refresh() { + this.category = frappe.get_route()[2]; + this.get_items_for_category(this.category) + .then(r => { + this.render(r.message); + }); + } + + get_items_for_category(category) { + return frappe.call('erpnext.hub_node.get_list', { + doctype: 'Hub Item', + filters: { + hub_category: category + } + }); + } + + render(items) { + const html = get_item_card_container_html(items, __(this.category)); + this.$wrapper.html(html) + } +} + +erpnext.hub.Item = class Item extends SubPage { + refresh() { + const hub_item_code = frappe.get_route()[2]; + + this.get_item(hub_item_code) + .then(item => { + this.render(item); + }); + } + + get_item(hub_item_code) { + return new Promise(resolve => { + const item = (erpnext.hub.hub_item_cache || []).find(item => item.name === hub_item_code) + + if (item) { + resolve(item); + } else { + frappe.call('erpnext.hub_node.get_list', { + doctype: 'Hub Item', + filters: { + name: hub_item_code + } + }) + .then(r => { + resolve(r.message[0]); + }); + } + }); + } + + render(item) { + const title = item.item_name || item.name; + const company = item.company_name; + + const who = __('Posted By {0}', [company]); + const when = comment_when(item.creation); + + const city = item.seller_city ? item.seller_city + ', ' : ''; + const country = item.country ? item.country : ''; + const where = `${city}${country}`; + + const dot_spacer = ''; + + const description = item.description || ''; + + const rating_html = get_rating_html(item); + const rating_count = item.reviews.length > 0 ? `(${item.reviews.length} reviews)` : ''; + + const html = ` +
    +
    +
    +
    + +
    +
    +
    +

    ${title}

    +
    +

    ${where}${dot_spacer}${when}

    +

    ${rating_html}${rating_count}

    +
    +
    +
    + ${description ? + `${__('Description')} +

    ${description}

    + ` : __('No description') + } +
    +
    +
    +
    +
    + Seller Information +
    +
    + +
    +
    + ${company} +

    + Contact Seller +

    +
    +
    +
    + `; + + this.$wrapper.html(html); + } +} + + + +function get_item_card_container_html(items, title) { + const html = (items || []).map(item => get_item_card_html(item)).join(''); + + return ` +
    +
    + ${title} +
    + ${html} +
    + `; +} + +function get_item_card_html(item) { + const item_name = item.item_name || item.name; + const title = strip_html(item_name); + + const img_url = item.image; + const company_name = item.company_name; + const route = `marketplace/item/${item.hub_item_code}`; + + let subtitle = [comment_when(item.creation)]; + const rating = get_rating(item); + if (rating > 0) { + subtitle.push(rating + ``) + } + subtitle.push(company_name); + + let dot_spacer = ''; + subtitle = subtitle.join(dot_spacer); + + const item_html = ` +
    +
    +
    +
    ${title}
    +
    ${subtitle}
    +
    +
    + +
    +
    +
    +
    + `; + + return item_html; +} + +function get_rating(item) { + const review_length = (item.reviews || []).length; + return review_length + ? item.reviews + .map(r => r.rating) + .reduce((a, b) => a + b, 0) / review_length + : 0; +} + +function get_rating_html(item) { + const rating = get_rating(item); + let rating_html = ``; + for (var i = 0; i < 5; i++) { + let star_class = 'fa-star'; + if (i >= rating) star_class = 'fa-star-o'; + rating_html += ``; + } + return rating_html; +} + erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { setup_defaults() { super.setup_defaults(); @@ -40,7 +439,7 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { get_meta() { return new Promise(resolve => - frappe.call('erpnext.hub_node.get_meta', {doctype: this.doctype}, resolve)); + frappe.call('erpnext.hub_node.get_meta', { doctype: this.doctype }, resolve)); } set_breadcrumbs() { } @@ -83,7 +482,7 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { } setup_view() { - if(frappe.route_options){ + if (frappe.route_options) { const filters = []; for (let field in frappe.route_options) { var value = frappe.route_options[field]; @@ -146,9 +545,9 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { {{ _("Payment Cancelled") }}
    -

    ${ __("Your payment is cancelled.") }

    +

    ${ __("Your payment is cancelled.")}

    + ${ __("Continue")}
    `; let page = this.page.wrapper.find('.layout-side-section') @@ -172,7 +571,7 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { `); } - if(this.data.length) { + if (this.data.length) { this.doc = this.data[0]; } @@ -226,11 +625,11 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { } get_image_html(encoded_name, src, alt_text) { - return `${ alt_text }`; + return `${alt_text}`; } get_image_placeholder(title) { - return `${ frappe.get_abbr(title) }`; + return `${frappe.get_abbr(title)}`; } loadImage(item) { @@ -241,7 +640,7 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { let placeholder = this.get_image_placeholder(title); let $container = this.$result.find(`.image-field[data-name="${encoded_name}"]`); - if(!item[this.imageFieldName]) { + if (!item[this.imageFieldName]) { $container.prepend(placeholder); $container.addClass('no-image'); } @@ -262,7 +661,7 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { } setup_quick_view() { - if(this.quick_view) return; + if (this.quick_view) return; this.quick_view = new frappe.ui.Dialog({ title: 'Quick View', @@ -312,10 +711,10 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { } setup_like() { - if(this.setup_like_done) return; + if (this.setup_like_done) return; this.setup_like_done = 1; this.$result.on('click', '.btn.like-button', (e) => { - if($(e.target).hasClass('changing')) return; + if ($(e.target).hasClass('changing')) return; $(e.target).addClass('changing'); e.preventDefault(); @@ -326,13 +725,13 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { let values = this.data_dict[name]; let heart = $(e.target); - if(heart.hasClass('like-button')) { + if (heart.hasClass('like-button')) { heart = $(e.target).find('.octicon'); } let remove = 1; - if(heart.hasClass('liked')) { + if (heart.hasClass('liked')) { // unlike heart.removeClass('liked'); } else { @@ -349,7 +748,7 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { }, callback: (r) => { let message = __("Added to Favourites"); - if(remove) { + if (remove) { message = __("Removed from Favourites"); } frappe.show_alert(message); @@ -454,7 +853,7 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { label: 'All Categories', expandable: true, - args: {parent: this.current_category}, + args: { parent: this.current_category }, method: 'erpnext.hub_node.get_categories', on_click: (node) => { this.update_category(node.label); @@ -501,8 +900,8 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { .then(r => { const categories = r.message.map(d => d.value).sort(); const sidebar_items = [ - `
  • - Category + `
  • + ${__('Category')}
  • `, `
  • All @@ -523,7 +922,7 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { } update_category(label) { - this.current_category = (label=='All Categories') ? undefined : label; + this.current_category = (label == 'All Categories') ? undefined : label; this.refresh(); } @@ -573,14 +972,14 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { const ratingAverage = reviewLength ? item.reviews .map(r => r.rating) - .reduce((a, b) => a + b, 0)/reviewLength + .reduce((a, b) => a + b, 0) / reviewLength : -1; let ratingHtml = ``; - for(var i = 0; i < 5; i++) { + for (var i = 0; i < 5; i++) { let starClass = 'fa-star'; - if(i >= ratingAverage) starClass = 'fa-star-o'; + if (i >= ratingAverage) starClass = 'fa-star-o'; ratingHtml += ``; } let dot_spacer = ''; @@ -608,7 +1007,7 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { (${reviewLength})
  • @@ -651,7 +1050,7 @@ erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { }; -erpnext.hub.Favourites = class Favourites extends erpnext.hub.ItemListing { +erpnext.hub.Favourites2 = class Favourites extends erpnext.hub.ItemListing { constructor(opts) { super(opts); this.show(); @@ -702,18 +1101,18 @@ erpnext.hub.Favourites = class Favourites extends erpnext.hub.ItemListing { } update_category(label) { - this.current_category = (label=='All Categories') ? undefined : label; + this.current_category = (label == 'All Categories') ? undefined : label; this.refresh(); } get_filters_for_args() { - if(!this.filter_area) return; + if (!this.filter_area) return; let filters = {}; this.filter_area.get().forEach(f => { let field = f[1] !== 'name' ? f[1] : 'item_name'; filters[field] = [f[2], f[3]]; }); - if(this.current_category) { + if (this.current_category) { filters['hub_category'] = this.current_category; } return filters; @@ -772,10 +1171,10 @@ erpnext.hub.CompanyListing = class CompanyListing extends erpnext.hub.HubListing get_filters_for_args() { let filters = {}; - this.filter_area.get().forEach(f => { - let field = f[1] !== 'name' ? f[1] : 'company_name'; - filters[field] = [f[2], f[3]]; - }); + // this.filter_area.get().forEach(f => { + // let field = f[1] !== 'name' ? f[1] : 'company_name'; + // filters[field] = [f[2], f[3]]; + // }); return filters; } diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index ef08fde270..48d52ca5aa 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -1,18 +1,15 @@ @import "../../../../frappe/frappe/public/less/variables.less"; -body[data-route^="Hub/"] { - .hub-icon { - width: 40px; - height: 40px; +body[data-route^="marketplace/"] { + .layout-side-section { + padding-top: 25px; + padding-right: 25px; } .layout-main-section { border: none; - } - - .frappe-list { - padding-top: 25px; font-size: @text-medium; + padding-top: 25px; @media (max-width: @screen-xs) { padding-left: 20px; @@ -26,17 +23,32 @@ body[data-route^="Hub/"] { border-radius: 4px; overflow: hidden; cursor: pointer; + + &:hover .hub-card-overlay { + display: block; + } } .hub-card-header { padding: 12px 15px; height: 60px; + border-bottom: 1px solid @border-color; } .hub-card-body { + position: relative; height: 200px; } + .hub-card-overlay { + display: none; + position: absolute; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.1); + } + .hub-card-image { min-width: 100%; width: 100%; @@ -73,6 +85,30 @@ body[data-route^="Hub/"] { } } + .hub-item-image { + border: 1px solid @border-color; + border-radius: 4px; + overflow: hidden; + height: 200px; + width: 200px; + display: flex; + align-items: center; + } + + .hub-item-seller img { + width: 50px; + height: 50px; + border-radius: 4px; + border: 1px solid @border-color; + } +} + +body[data-route^="Hub/"] { + .hub-icon { + width: 40px; + height: 40px; + } + .img-wrapper { border: 1px solid #d1d8dd; border-radius: 3px; From 0fbaa725041c35e32127ba30c29c7dbb2e659118 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 25 Jul 2018 00:49:11 +0530 Subject: [PATCH 003/154] Add review area --- .../doctype/hub_settings/hub_settings.py | 2 +- erpnext/public/js/hub/hub_listing.js | 130 +++++++++++++++++- 2 files changed, 126 insertions(+), 6 deletions(-) diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 15ee4b7da3..58b946e1af 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -12,7 +12,7 @@ from six import string_types hub_url = "https://hubmarket.org" # hub_url = "http://159.89.175.122" -# hub_url = "http://erpnext.hub:8000" +# hub_url = "http://erpnext.hub:8001" class OAuth2Session(): def __init__(self, headers): diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index a6a9ac975b..f61272d3f8 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -25,7 +25,7 @@ erpnext.hub.Marketplace = class Marketplace { } make_sidebar() { - this.$sidebar = this.$parent.find('.layout-side-section'); + this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); this.$sidebar.append(`
      @@ -237,9 +237,9 @@ erpnext.hub.Category = class Category extends SubPage { erpnext.hub.Item = class Item extends SubPage { refresh() { - const hub_item_code = frappe.get_route()[2]; + this.hub_item_code = frappe.get_route()[2]; - this.get_item(hub_item_code) + this.get_item(this.hub_item_code) .then(item => { this.render(item); }); @@ -285,6 +285,11 @@ erpnext.hub.Item = class Item extends SubPage { const html = `
      +
      +
      + +
      +
      @@ -321,15 +326,130 @@ erpnext.hub.Item = class Item extends SubPage {

      + +
      + +
      `; this.$wrapper.html(html); + + this.make_review_area(); + this.render_reviews(item); + } + + make_review_area() { + this.comment_area = new frappe.ui.ReviewArea({ + parent: this.$wrapper.find('.timeline-head').empty(), + mentions: [], + on_submit: (val) => { + val.user = frappe.session.user; + val.username = frappe.session.user_fullname; + + frappe.call({ + method: 'erpnext.hub_node.send_review', + args: { + hub_item_code: this.hub_item_code, + review: val + }, + callback: (r) => { + console.log(r); + this.render_reviews(r.message); + this.comment_area.reset(); + }, + freeze: true + }); + } + }); + } + + render_reviews(item) { + this.$wrapper.find('.timeline-items').empty(); + item.reviews.forEach(review => this.render_review(review, item)); + } + + render_review(review, item) { + let username = review.username || review.user || __("Anonymous"); + + let image_html = review.user_image + ? `
      ` + : `
      ${frappe.get_abbr(username)}
      ` + + let edit_html = review.own + ? ` +
      + + ${'data.edit'} + +
      ` + : ''; + + let rating_html = get_rating_html(item); + + const $timeline_items = this.$wrapper.find('.timeline-items'); + + $(this.get_timeline_item(review, image_html, edit_html, rating_html)) + .appendTo($timeline_items); + } + + get_timeline_item(data, image_html, edit_html, rating_html) { + return `
      + + +
      +
      +
      ${edit_html}
      + +
      + + ${image_html} + + +
      + + + ${data.username} + + + + + +
      +
      +
      +
      +

      + ${rating_html} +

      +
      ${data.subject}
      +

      + ${data.content} +

      +
      +
      +
      +
      +
      `; } } - - function get_item_card_container_html(items, title) { const html = (items || []).map(item => get_item_card_html(item)).join(''); From bc8caefc197cecc0ee69facd19820f27c5dcb52d Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Wed, 25 Jul 2018 10:58:56 +0530 Subject: [PATCH 004/154] [hub] add register as a seller page --- erpnext/hub_node/__init__.py | 7 + .../doctype/hub_settings/hub_settings.json | 319 +++--------------- .../doctype/hub_settings/hub_settings.py | 34 +- erpnext/public/js/hub/hub_listing.js | 197 ++++++++++- erpnext/public/less/hub.less | 27 +- 5 files changed, 301 insertions(+), 283 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index a5e4607122..a94f6b1615 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -31,6 +31,13 @@ def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=No return listing + + +@frappe.whitelist() +def get_valid_items(): + items = frappe.get_list('Item') + return items + @frappe.whitelist() def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): doctype = 'Hub Item' diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.json b/erpnext/hub_node/doctype/hub_settings/hub_settings.json index 7c7109c64c..c742c57b2b 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json @@ -14,243 +14,21 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "enabled", + "fieldname": "registered", "fieldtype": "Check", - "hidden": 1, + "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Enabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "suspended", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Suspended", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "enabled", - "fieldname": "hub_username", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hub Username", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_0", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "enabled", - "fieldname": "hub_user_status", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "enabled", - "fieldname": "language", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Language", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "eval:(!doc.enabled)", - "columns": 0, - "depends_on": "", - "fieldname": "seller_profile_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company and Seller Profile", + "label": "Registered", "length": 0, "no_copy": 0, "permlevel": 0, @@ -268,6 +46,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -299,6 +78,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -331,6 +111,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -362,6 +143,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -394,11 +176,44 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "company_logo", + "fieldname": "currency", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Currency", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "logo", "fieldtype": "Attach Image", "hidden": 0, "ignore_user_permissions": 0, @@ -425,11 +240,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "seller_description", + "fieldname": "company_description", "fieldtype": "Text Editor", "hidden": 0, "ignore_user_permissions": 0, @@ -456,6 +272,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -487,38 +304,7 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "users", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Users", - "length": 0, - "no_copy": 0, - "options": "Hub Users", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -551,6 +337,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -582,6 +369,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -614,6 +402,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -647,6 +436,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -679,6 +469,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -711,6 +502,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -744,6 +536,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -777,6 +570,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -817,8 +611,8 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-03-26 00:55:17.929140", - "modified_by": "test1@example.com", + "modified": "2018-07-24 18:53:09.442523", + "modified_by": "cave@aperture.com", "module": "Hub Node", "name": "Hub Settings", "name_case": "", @@ -826,7 +620,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 0, diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 58b946e1af..551423c8a2 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -10,9 +10,10 @@ from frappe import _ from erpnext.utilities.product import get_price, get_qty_in_stock from six import string_types -hub_url = "https://hubmarket.org" +# hub_url = "https://hubmarket.org" # hub_url = "http://159.89.175.122" # hub_url = "http://erpnext.hub:8001" +hub_url = "http://hub.market:8000" class OAuth2Session(): def __init__(self, headers): @@ -80,20 +81,20 @@ class HubSettings(Document): def register(self): """ Create a User on hub.erpnext.org and return username/password """ data = { - 'email': frappe.session.user + 'profile': self.as_json() } post_url = hub_url + '/api/method/hub.hub.api.register' - response = requests.post(post_url, data=data) + response = requests.post(post_url, data=data, headers = {'accept': 'application/json'}) + response.raise_for_status() - message = response.json().get('message') + + if response.ok: + message = response.json().get('message') + else: + frappe.throw(json.loads(response.text)) - if message and message.get('password'): - self.user = frappe.session.user - self.create_hub_connector(message) - self.company = frappe.defaults.get_user_default('company') - self.enabled = 1 - self.save() + return message.get('password') if message else None def unregister(self): """ Disable the User on hub.erpnext.org""" @@ -143,3 +144,16 @@ def reset_hub_settings(last_sync_datetime = ""): def sync(): hub_settings = frappe.get_doc('Hub Settings') hub_settings.sync() + +@frappe.whitelist() +def register_seller(**kwargs): + settings = frappe.get_doc('Hub Settings') + settings.update(kwargs) + password = settings.register() + + print(password) + + # if password: + # self.create_hub_connector(message) + # self.registered = 1 + # self.save() diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index f61272d3f8..218960bb17 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -5,11 +5,16 @@ erpnext.hub.Marketplace = class Marketplace { this.$parent = $(parent); this.page = parent.page; - this.setup_header(); - this.make_sidebar(); - this.make_body(); - this.setup_events(); - this.refresh(); + frappe.db.get_doc('Hub Settings') + .then(doc => { + this.hub_settings = doc; + + this.setup_header(); + this.make_sidebar(); + this.make_body(); + this.setup_events(); + this.refresh(); + }); } setup_header() { @@ -35,7 +40,7 @@ erpnext.hub.Marketplace = class Marketplace {
    • ${__('Favorites')}
    • -
    • +
    • ${__('Become a seller')}
    @@ -110,6 +115,11 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages.item = new erpnext.hub.Item(this.$body); } + if (route[1] === 'register' && !this.subpages.register) { + this.subpages.register = new erpnext.hub.Register(this.$body); + } + + if (!Object.keys(this.subpages).includes(route[1])) { frappe.show_not_found(); return; @@ -189,7 +199,7 @@ erpnext.hub.Home = class Home extends SubPage { render(items) { const html = get_item_card_container_html(items, __('Recently Published')); - this.$wrapper.html(html) + this.$wrapper.append(html) } } @@ -412,7 +422,6 @@ erpnext.hub.Item = class Item extends SubPage { -
    ${edit_html}
    @@ -449,6 +458,176 @@ erpnext.hub.Item = class Item extends SubPage {
    `; } } +erpnext.hub.Register = class Register extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.$register_container = $(`
    `) + .appendTo(this.$wrapper); + this.$form_container = $('
    ') + .appendTo(this.$wrapper); + + // const title = __('Become a Seller'); + // this.$register_container.append($(`
    ${title}
    `)); + } + + refresh() { + // company, company_email, logo, description + this.render(); + } + + // make_input() { + // // + // } + + render() { + this.make_field_group(); + } + + make_field_group() { + const fields = [ + { + fieldtype: 'Link', + fieldname: 'company', + label: __('Company'), + options: 'Company', + onchange: () => { + const value = this.field_group.get_value('company'); + + if (value) { + frappe.db.get_doc('Company', value) + .then(company => { + this.field_group.set_values({ + country: company.country, + company_email: company.email, + currency: company.default_currency + }); + }); + } + } + }, + { + fieldname: 'company_email', + label: __('Email'), + fieldtype: 'Data' + }, + { + fieldname: 'country', + label: __('Country'), + fieldtype: 'Read Only' + }, + { + fieldname: 'currency', + label: __('Currency'), + fieldtype: 'Read Only' + }, + { + fieldtype: 'Text', + label: __('About your Company'), + fieldname: 'company_description' + } + ]; + + this.field_group = new frappe.ui.FieldGroup({ + parent: this.$form_container, + fields + }); + + this.field_group.make(); + + this.$form_container.find('.form-column').append(` +
    + +
    + `); + + this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller')) + + this.$form_container.on('click', '.btn-register', () => { + const form_values = this.field_group.get_values(); + frappe.call('erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', form_values) + .then(() => { + // Reload page and things ... but for now + frappe.msgprint('Registered successfully.'); + }); + }); + } + + make_form() { + const form_html = `
    +
    + Become a Seller +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    +
    +
    `; + + + + this.$form_container.append(form_html); + + this.$form_container.on('submit', (e) => { + e.preventDefault(); + + const formValues = this.$form_container.find('form').serializeArray(); + + console.log(formValues); + }) + } +} + +erpnext.hub.PublishItems = class PublishItems extends SubPage { + refresh() { + this.get_favourites() + .then(r => { + this.render(r.message); + }); + } + + render(items) { + const html = get_item_card_container_html(items, __(this.category)); + this.$wrapper.html(html); + } + + get_valid_items() { + return frappe.call('erpnext.hub_node.get_valid_items'); + } +} function get_item_card_container_html(items, title) { const html = (items || []).map(item => get_item_card_html(item)).join(''); @@ -1335,4 +1514,4 @@ erpnext.hub.CompanyListing = class CompanyListing extends erpnext.hub.HubListing return item_html; } -}; \ No newline at end of file +}; diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 48d52ca5aa..35e1f8de8f 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -17,6 +17,10 @@ body[data-route^="marketplace/"] { } } + input, textarea { + font-size: @text-medium; + } + .hub-card { border: 1px solid @border-color; margin-bottom: 25px; @@ -58,7 +62,6 @@ body[data-route^="marketplace/"] { margin-bottom: 20px; input { - font-size: @text-medium; height: 32px; } } @@ -101,6 +104,28 @@ body[data-route^="marketplace/"] { border-radius: 4px; border: 1px solid @border-color; } + + .register-title { + font-size: @text-regular; + } + + .register-form { + border: 1px solid @border-color; + border-radius: 4px; + padding: 15px 25px; + } + + .form-container { + .frappe-control { + max-width: 100% !important; + } + } + + .form-message { + padding-top: 0; + padding-bottom: 0; + border-bottom: none; + } } body[data-route^="Hub/"] { From cf2d2a5c754e92438572aa43fc6f4dede0dd0c66 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Wed, 25 Jul 2018 20:02:12 +0530 Subject: [PATCH 005/154] [hub][init] add publish items page --- erpnext/hub_node/__init__.py | 14 ++- erpnext/public/js/hub/hub_listing.js | 180 +++++++++++++-------------- erpnext/public/less/hub.less | 40 +++++- 3 files changed, 133 insertions(+), 101 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index a94f6b1615..138ff1e40e 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -31,12 +31,18 @@ def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=No return listing - - +#### LOCAL ITEMS @frappe.whitelist() def get_valid_items(): - items = frappe.get_list('Item') - return items + items = frappe.get_list('Item', fields=["*"]) + valid_items = filter(lambda x: x.image and x.description, items) + + def attach_source_type(item): + item.source_type = "local" + return item + + valid_items = map(lambda x: attach_source_type(x), valid_items) + return valid_items @frappe.whitelist() def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 218960bb17..856d538dd2 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -26,6 +26,8 @@ erpnext.hub.Marketplace = class Marketplace { const $target = $(e.currentTarget); const route = $target.data().route; frappe.set_route(route); + + e.stopPropagation(); }); } @@ -43,6 +45,12 @@ erpnext.hub.Marketplace = class Marketplace {
  • ${__('Become a seller')}
  • +
  • + ${__('Your Profile')} +
  • +
  • + ${__('Publish Products')} +
  • `); @@ -60,6 +68,9 @@ erpnext.hub.Marketplace = class Marketplace { `
  • ${__('All')}
  • `, + `
  • + ${__('Your Products')} +
  • `, ...categories.map(category => `
  • ${__(category)} @@ -119,6 +130,10 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages.register = new erpnext.hub.Register(this.$body); } + if (route[1] === 'publish' && !this.subpages.publish) { + this.subpages.publish = new erpnext.hub.Publish(this.$body); + } + if (!Object.keys(this.subpages).includes(route[1])) { frappe.show_not_found(); @@ -217,7 +232,7 @@ erpnext.hub.Favourites = class Favourites extends SubPage { render(items) { const html = get_item_card_container_html(items, __('Favourites')); - this.$wrapper.html(html) + this.$wrapper.append(html) } } @@ -241,7 +256,7 @@ erpnext.hub.Category = class Category extends SubPage { render(items) { const html = get_item_card_container_html(items, __(this.category)); - this.$wrapper.html(html) + this.$wrapper.append(html) } } @@ -551,77 +566,42 @@ erpnext.hub.Register = class Register extends SubPage { }); }); } - - make_form() { - const form_html = `
    -
    - Become a Seller -
    - -
    -
    -
    -
    - -
    -
    -
    - -
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    -
    -
    -
    `; - - - - this.$form_container.append(form_html); - - this.$form_container.on('submit', (e) => { - e.preventDefault(); - - const formValues = this.$form_container.find('form').serializeArray(); - - console.log(formValues); - }) - } } -erpnext.hub.PublishItems = class PublishItems extends SubPage { +erpnext.hub.Publish = class Publish extends SubPage { + constructor(parent) { + super(parent); + // this.selected_items = []; + } + refresh() { - this.get_favourites() + this.get_valid_items() .then(r => { this.render(r.message); }); } render(items) { - const html = get_item_card_container_html(items, __(this.category)); - this.$wrapper.html(html); + const html = get_item_card_container_html( + items, + + __('Select Products to Publish'), + + __(`Only products with an image and description can be published. + Please update them if an item in your inventory does not appear.`), + + `
    + +
    ` + ); + + const container = $(html); + container.addClass('static').on('click', '.hub-card', (e) => { + const $target = $(e.currentTarget); + $target.toggleClass('active'); + }); + + this.$wrapper.append(container); } get_valid_items() { @@ -629,17 +609,23 @@ erpnext.hub.PublishItems = class PublishItems extends SubPage { } } -function get_item_card_container_html(items, title) { - const html = (items || []).map(item => get_item_card_html(item)).join(''); +function get_item_card_container_html(items, title, subtitle='', search_html='') { + const items_html = (items || []).map(item => get_item_card_html(item)).join(''); - return ` -
    -
    - ${title} -
    - ${html} + const html = `
    +
    + ${title} + +

    ${subtitle}

    + + ${search_html}
    - `; + + ${items_html} + +
    `; + + return html; } function get_item_card_html(item) { @@ -648,8 +634,8 @@ function get_item_card_html(item) { const img_url = item.image; const company_name = item.company_name; - const route = `marketplace/item/${item.hub_item_code}`; + // Subtitle let subtitle = [comment_when(item.creation)]; const rating = get_rating(item); if (rating > 0) { @@ -660,16 +646,36 @@ function get_item_card_html(item) { let dot_spacer = ''; subtitle = subtitle.join(dot_spacer); + // Decide item link + const isLocal = item.source_type === "local"; + const route = !isLocal + ? `marketplace/item/${item.hub_item_code}` + : `Form/Item/${item.item_name}`; + + const card_route = isLocal ? '' : `data-route='${route}'`; + + const show_local_item_button = isLocal + ? `
    + +
    ` + : ''; + const item_html = `
    -
    +
    -
    ${title}
    -
    ${subtitle}
    +
    +
    ${title}
    +
    ${subtitle}
    +
    +
    -
    +
    + ${show_local_item_button}
    @@ -904,23 +910,7 @@ erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { } renderHeader() { - return `
    -
    -
    - Riadco Group - Products by Blah blah -
    -
    -
    -
    - - -
    -
    -
    - ${''} -
    -
    `; + return ``; } get_image_html(encoded_name, src, alt_text) { diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 35e1f8de8f..1243743032 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -21,6 +21,16 @@ body[data-route^="marketplace/"] { font-size: @text-medium; } + .btn-primary { + background-color: #89da28; + border-color: #61ca23; + } + + .btn-primary:hover { + background-color: #61ca23; + border-color: #59b81c; + } + .hub-card { border: 1px solid @border-color; margin-bottom: 25px; @@ -28,15 +38,33 @@ body[data-route^="marketplace/"] { overflow: hidden; cursor: pointer; - &:hover .hub-card-overlay { + &:hover .overlay { display: block; } + + &.active { + .hub-card-header { + background-color: #f4ffe5; + } + + .octicon-check { + display: inline; + } + } } .hub-card-header { padding: 12px 15px; height: 60px; border-bottom: 1px solid @border-color; + + display: flex; + justify-content: space-between; + + .octicon-check { + display: none; + font-size: 20px; + } } .hub-card-body { @@ -44,15 +72,23 @@ body[data-route^="marketplace/"] { height: 200px; } - .hub-card-overlay { + .overlay { display: none; position: absolute; + } + + .hub-card-overlay { top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.1); } + .button-overlay { + top: 155px; + left: 15px; + } + .hub-card-image { min-width: 100%; width: 100%; From 9685d7c37a984a9d3eeb10e9484ddd7716a8c07e Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 26 Jul 2018 01:12:00 +0530 Subject: [PATCH 006/154] [hub] registered vs read-only views --- erpnext/public/js/hub/hub_listing.js | 47 ++++++++++++++++------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 856d538dd2..9061ae62d3 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -8,7 +8,8 @@ erpnext.hub.Marketplace = class Marketplace { frappe.db.get_doc('Hub Settings') .then(doc => { this.hub_settings = doc; - + this.registered = doc.registered; + this.setup_header(); this.make_sidebar(); this.make_body(); @@ -26,7 +27,7 @@ erpnext.hub.Marketplace = class Marketplace { const $target = $(e.currentTarget); const route = $target.data().route; frappe.set_route(route); - + e.stopPropagation(); }); } @@ -34,6 +35,18 @@ erpnext.hub.Marketplace = class Marketplace { make_sidebar() { this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); + const user_specific_items_html = this.registered + ? `
  • + ${__('Your Profile')} +
  • +
  • + ${__('Publish Products')} +
  • ` + + : `
  • + ${__('Become a seller')} +
  • `; + this.$sidebar.append(`
    • @@ -42,15 +55,7 @@ erpnext.hub.Marketplace = class Marketplace {
    • ${__('Favorites')}
    • -
    • - ${__('Become a seller')} -
    • -
    • - ${__('Your Profile')} -
    • -
    • - ${__('Publish Products')} -
    • + ${user_specific_items_html}
    `); @@ -68,9 +73,11 @@ erpnext.hub.Marketplace = class Marketplace { `
  • ${__('All')}
  • `, - `
  • - ${__('Your Products')} -
  • `, + ...(this.registered + ? [`
  • + ${__('Your Products')} +
  • `] + : []), ...categories.map(category => `
  • ${__(category)} @@ -480,7 +487,7 @@ erpnext.hub.Register = class Register extends SubPage { .appendTo(this.$wrapper); this.$form_container = $('
    ') .appendTo(this.$wrapper); - + // const title = __('Become a Seller'); // this.$register_container.append($(`
    ${title}
    `)); } @@ -493,7 +500,7 @@ erpnext.hub.Register = class Register extends SubPage { // make_input() { // // // } - + render() { this.make_field_group(); } @@ -583,11 +590,11 @@ erpnext.hub.Publish = class Publish extends SubPage { render(items) { const html = get_item_card_container_html( - items, + items, __('Select Products to Publish'), - __(`Only products with an image and description can be published. + __(`Only products with an image and description can be published. Please update them if an item in your inventory does not appear.`), `
    @@ -648,13 +655,13 @@ function get_item_card_html(item) { // Decide item link const isLocal = item.source_type === "local"; - const route = !isLocal + const route = !isLocal ? `marketplace/item/${item.hub_item_code}` : `Form/Item/${item.item_name}`; const card_route = isLocal ? '' : `data-route='${route}'`; - const show_local_item_button = isLocal + const show_local_item_button = isLocal ? `
    `; + + const select_all_button = ``; + const deselect_all_button = ``; + + const search_html = `
    + +
    `; + + const subpage_header = $(` +
    +
    + ${title_html} + ${subtitle_html} +
    + ${publish_button_html} +
    + + ${search_html} + + ${select_all_button} + ${deselect_all_button} + `); + + this.$wrapper.append(subpage_header); + + this.setup_events(); + } + + setup_events() { + this.$wrapper.find('.select-all').on('click', () => { + this.$wrapper.find('.hub-card').addClass('active'); + }); + + this.$wrapper.find('.deselect-all').on('click', () => { + this.$wrapper.find('.hub-card').removeClass('active'); + }); } refresh() { @@ -589,26 +625,13 @@ erpnext.hub.Publish = class Publish extends SubPage { } render(items) { - const html = get_item_card_container_html( - items, - - __('Select Products to Publish'), - - __(`Only products with an image and description can be published. - Please update them if an item in your inventory does not appear.`), - - `
    - -
    ` - ); - - const container = $(html); - container.addClass('static').on('click', '.hub-card', (e) => { + const items_container = $(get_item_card_container_html(items)); + items_container.addClass('static').on('click', '.hub-card', (e) => { const $target = $(e.currentTarget); $target.toggleClass('active'); }); - this.$wrapper.append(container); + this.$wrapper.append(items_container); } get_valid_items() { @@ -616,20 +639,14 @@ erpnext.hub.Publish = class Publish extends SubPage { } } -function get_item_card_container_html(items, title, subtitle='', search_html='') { +function get_item_card_container_html(items, title='') { const items_html = (items || []).map(item => get_item_card_html(item)).join(''); const html = `
    -
    +
    ${title} - -

    ${subtitle}

    - - ${search_html}
    - ${items_html} -
    `; return html; diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 1243743032..8c7392ad49 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -31,6 +31,11 @@ body[data-route^="marketplace/"] { border-color: #59b81c; } + .subpage-title.flex { + align-items: flex-start; + justify-content: space-between; + } + .hub-card { border: 1px solid @border-color; margin-bottom: 25px; From 1d1696080e8366a8f5f9e8b8ef34407ec253a65f Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 26 Jul 2018 08:36:33 +0530 Subject: [PATCH 008/154] [hub][publish] add search, fix multiple append --- erpnext/hub_node/__init__.py | 9 +++++++-- erpnext/public/js/hub/hub_listing.js | 29 ++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 138ff1e40e..993737e147 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -33,8 +33,13 @@ def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=No #### LOCAL ITEMS @frappe.whitelist() -def get_valid_items(): - items = frappe.get_list('Item', fields=["*"]) +def get_valid_items(search_value=''): + items = frappe.get_list('Item', fields=["*"], filters={ + 'item_name': ['like', '%' + search_value + '%'] + }) + + print([d.item_name for d in items]) + valid_items = filter(lambda x: x.image and x.description, items) def attach_source_type(item): diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 3107c674be..33df23e0c5 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -186,6 +186,7 @@ erpnext.hub.Home = class Home extends SubPage { } get_items_and_render() { + this.$wrapper.find('.hub-card-container').empty(); this.get_items() .then(r => { erpnext.hub.hub_item_cache = r.message; @@ -238,6 +239,7 @@ erpnext.hub.Favourites = class Favourites extends SubPage { } render(items) { + this.$wrapper.find('.hub-card-container').empty(); const html = get_item_card_container_html(items, __('Favourites')); this.$wrapper.append(html) } @@ -253,6 +255,7 @@ erpnext.hub.Category = class Category extends SubPage { } get_items_for_category(category) { + this.$wrapper.find('.hub-card-container').empty(); return frappe.call('erpnext.hub_node.get_list', { doctype: 'Hub Item', filters: { @@ -490,6 +493,8 @@ erpnext.hub.Register = class Register extends SubPage { } refresh() { + this.$register_container.empty(); + this.$form_container.empty(); this.render(); } @@ -615,15 +620,30 @@ erpnext.hub.Publish = class Publish extends SubPage { this.$wrapper.find('.deselect-all').on('click', () => { this.$wrapper.find('.hub-card').removeClass('active'); }); + + const $search_input = this.$wrapper.find('.hub-search-container input'); + this.search_value = ''; + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + this.search_value = $search_input.val(); + this.get_items_and_render(); + } + }, 300)); } - refresh() { + get_items_and_render() { + this.$wrapper.find('.hub-card-container').empty(); this.get_valid_items() .then(r => { this.render(r.message); }); } + refresh() { + this.get_items_and_render(); + } + render(items) { const items_container = $(get_item_card_container_html(items)); items_container.addClass('static').on('click', '.hub-card', (e) => { @@ -635,7 +655,12 @@ erpnext.hub.Publish = class Publish extends SubPage { } get_valid_items() { - return frappe.call('erpnext.hub_node.get_valid_items'); + return frappe.call( + 'erpnext.hub_node.get_valid_items', + { + search_value: this.search_value + } + ); } } From f26177bdbc6491902478dc7793d660b2a07fecbb Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 26 Jul 2018 10:38:11 +0530 Subject: [PATCH 009/154] [hub] setup publish items flow --- erpnext/hub_node/__init__.py | 15 ++++++++-- erpnext/public/js/hub/hub_listing.js | 45 ++++++++++++++++++++++++---- erpnext/stock/doctype/item/item.py | 4 +-- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 993737e147..c8e40eb8e8 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -38,8 +38,6 @@ def get_valid_items(search_value=''): 'item_name': ['like', '%' + search_value + '%'] }) - print([d.item_name for d in items]) - valid_items = filter(lambda x: x.image and x.description, items) def attach_source_type(item): @@ -49,6 +47,19 @@ def get_valid_items(search_value=''): valid_items = map(lambda x: attach_source_type(x), valid_items) return valid_items +@frappe.whitelist() +def publish_selected_items(items_to_publish, items_to_unpublish): + for item_code in json.loads(items_to_publish): + frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) + + for item_code in json.loads(items_to_unpublish): + frappe.db.set_value('Item', item_code, 'publish_in_hub', 0) + + hub_settings = frappe.get_doc('Hub Settings') + hub_settings.sync() + + return + @frappe.whitelist() def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): doctype = 'Hub Item' diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 33df23e0c5..eb55806784 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -576,11 +576,13 @@ erpnext.hub.Publish = class Publish extends SubPage { make_wrapper() { super.make_wrapper(); const title_html = `${__('Select Products to Publish')}`; - const subtitle_html = `

    - ${__(`Only products with an image and description can be published. + const info = `

    ${__("Status decided by the 'Publish in Hub' field in Item.")}

    `; + const subtitle_html = ` +

    + ${__(`Only products with an image, description and category can be published. Please update them if an item in your inventory does not appear.`)}

    `; - const publish_button_html = ``; @@ -621,6 +623,13 @@ erpnext.hub.Publish = class Publish extends SubPage { this.$wrapper.find('.hub-card').removeClass('active'); }); + this.$wrapper.find('.publish-items').on('click', () => { + this.publish_selected_items() + .then(r => { + frappe.msgprint('check'); + }); + }); + const $search_input = this.$wrapper.find('.hub-search-container input'); this.search_value = ''; @@ -662,6 +671,28 @@ erpnext.hub.Publish = class Publish extends SubPage { } ); } + + publish_selected_items() { + const items_to_publish = []; + const items_to_unpublish = []; + this.$wrapper.find('.hub-card').map(function () { + const active = $(this).hasClass('active'); + + if(active) { + items_to_publish.push($(this).attr("data-id")); + } else { + items_to_unpublish.push($(this).attr("data-id")); + } + }); + + return frappe.call( + 'erpnext.hub_node.publish_selected_items', + { + items_to_publish: items_to_publish, + items_to_unpublish: items_to_unpublish + } + ); + } } function get_item_card_container_html(items, title='') { @@ -680,10 +711,14 @@ function get_item_card_container_html(items, title='') { function get_item_card_html(item) { const item_name = item.item_name || item.name; const title = strip_html(item_name); - const img_url = item.image; + const company_name = item.company_name; + const active = item.publish_in_hub; + + const id = item.hub_item_code || item.item_code; + // Subtitle let subtitle = [comment_when(item.creation)]; const rating = get_rating(item); @@ -713,7 +748,7 @@ function get_item_card_html(item) { const item_html = `
    -
    +
    ${title}
    diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index f9d0e7121c..1829b25e8e 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -75,8 +75,8 @@ class Item(WebsiteGenerator): if not self.description: self.description = self.item_name - if self.is_sales_item and not self.get('is_item_from_hub'): - self.publish_in_hub = 1 + # if self.is_sales_item and not self.get('is_item_from_hub'): + # self.publish_in_hub = 1 def after_insert(self): '''set opening stock and item price''' From d1224ca662df31e8f694ddc7b365dfc0fe0d7149 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 26 Jul 2018 13:39:06 +0530 Subject: [PATCH 010/154] [hub][init] add profile page --- erpnext/hub_node/__init__.py | 14 +++--- erpnext/public/js/hub/hub_listing.js | 65 +++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index c8e40eb8e8..5c86bb927c 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -34,9 +34,14 @@ def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=No #### LOCAL ITEMS @frappe.whitelist() def get_valid_items(search_value=''): - items = frappe.get_list('Item', fields=["*"], filters={ - 'item_name': ['like', '%' + search_value + '%'] - }) + items = frappe.get_list( + 'Item', fields=["*"], + filters={ + 'item_name': ['like', '%' + search_value + '%'], + 'publish_in_hub': 0 + }, + order_by="modified desc" + ) valid_items = filter(lambda x: x.image and x.description, items) @@ -52,9 +57,6 @@ def publish_selected_items(items_to_publish, items_to_unpublish): for item_code in json.loads(items_to_publish): frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - for item_code in json.loads(items_to_unpublish): - frappe.db.set_value('Item', item_code, 'publish_in_hub', 0) - hub_settings = frappe.get_doc('Hub Settings') hub_settings.sync() diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index eb55806784..6d0f3e96ac 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -137,11 +137,14 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages.register = new erpnext.hub.Register(this.$body); } + if (route[1] === 'profile' && !this.subpages.profile) { + this.subpages.profile = new erpnext.hub.Profile(this.$body, this.hub_settings); + } + if (route[1] === 'publish' && !this.subpages.publish) { this.subpages.publish = new erpnext.hub.Publish(this.$body); } - if (!Object.keys(this.subpages).includes(route[1])) { frappe.show_not_found(); return; @@ -572,6 +575,51 @@ erpnext.hub.Register = class Register extends SubPage { } } +erpnext.hub.Profile = class Profile extends SubPage { + constructor(parent, profile_data) { + super(parent); + this.profile_data = profile_data; + } + + make_wrapper() { + super.make_wrapper(); + const profile_html = `
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +

    ${'title'}

    +
    +

    ${'where'}${'dot_spacer'}${'when'}

    +

    ${'rating_html'}${'rating_count'}

    +
    +
    +
    + ${'description' ? + `${__('Description')} +

    ${'description'}

    + ` : __('No description') + } +
    +
    +
    +
    `; + + this.$wrapper.html(profile_html); + } + + refresh() {} + + render() {} +} erpnext.hub.Publish = class Publish extends SubPage { make_wrapper() { super.make_wrapper(); @@ -604,9 +652,6 @@ erpnext.hub.Publish = class Publish extends SubPage {
    ${search_html} - - ${select_all_button} - ${deselect_all_button} `); this.$wrapper.append(subpage_header); @@ -615,13 +660,13 @@ erpnext.hub.Publish = class Publish extends SubPage { } setup_events() { - this.$wrapper.find('.select-all').on('click', () => { - this.$wrapper.find('.hub-card').addClass('active'); - }); + // this.$wrapper.find('.select-all').on('click', () => { + // this.$wrapper.find('.hub-card').addClass('active'); + // }); - this.$wrapper.find('.deselect-all').on('click', () => { - this.$wrapper.find('.hub-card').removeClass('active'); - }); + // this.$wrapper.find('.deselect-all').on('click', () => { + // this.$wrapper.find('.hub-card').removeClass('active'); + // }); this.$wrapper.find('.publish-items').on('click', () => { this.publish_selected_items() From b7504252883d6fb509139436bea0af311e7653ce Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 26 Jul 2018 16:25:22 +0530 Subject: [PATCH 011/154] [hub] sync in progress --- erpnext/hub_node/__init__.py | 17 ++- .../doctype/hub_settings/hub_settings.js | 131 +----------------- .../doctype/hub_settings/hub_settings.json | 34 ++++- erpnext/public/js/hub/hub_listing.js | 65 +++++---- 4 files changed, 86 insertions(+), 161 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 5c86bb927c..f9132397c0 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -2,7 +2,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, requests, json +import frappe, requests, json, time from frappe.utils import now, nowdate, cint from frappe.utils.nestedset import get_root_of from frappe.contacts.doctype.contact.contact import get_default_contact @@ -35,7 +35,8 @@ def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=No @frappe.whitelist() def get_valid_items(search_value=''): items = frappe.get_list( - 'Item', fields=["*"], + 'Item', + fields=["*"], filters={ 'item_name': ['like', '%' + search_value + '%'], 'publish_in_hub': 0 @@ -53,12 +54,14 @@ def get_valid_items(search_value=''): return valid_items @frappe.whitelist() -def publish_selected_items(items_to_publish, items_to_unpublish): - for item_code in json.loads(items_to_publish): - frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) +def publish_selected_items(items_to_publish): + # for item_code in json.loads(items_to_publish): + # frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - hub_settings = frappe.get_doc('Hub Settings') - hub_settings.sync() + time.sleep(3) + + # hub_settings = frappe.get_doc('Hub Settings') + # hub_settings.sync() return diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.js b/erpnext/hub_node/doctype/hub_settings/hub_settings.js index 29d870b371..4bd3333276 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.js +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.js @@ -1,53 +1,23 @@ frappe.ui.form.on("Hub Settings", { refresh: function(frm) { + frm.disable_save(); frm.add_custom_button(__('Logs'), () => frappe.set_route('List', 'Data Migration Run', { data_migration_plan: 'Hub Sync' })); - frm.trigger("enabled"); - if (frm.doc.enabled) { frm.add_custom_button(__('Sync'), () => frm.call('sync')); } }, - onload: function(frm) { - let token = frappe.urllib.get_arg("access_token"); - if(token) { - let email = frm.get_field("user"); - console.log('token', frappe.urllib.get_arg("access_token")); - - get_user_details(frm, token, email); - let row = frappe.model.add_child(frm.doc, "Hub Users", "users"); - row.user = frappe.session.user; - } - - if(!frm.doc.country) { - frm.set_value("country", frappe.defaults.get_default("Country")); - } - if(!frm.doc.company) { - frm.set_value("company", frappe.defaults.get_default("Company")); - } - if(!frm.doc.user) { - frm.set_value("user", frappe.session.user); - } - }, + onload: function(frm) { }, onload_post_render: function(frm) { if(frm.get_field("unregister_from_hub").$input) frm.get_field("unregister_from_hub").$input.addClass("btn-danger"); }, on_update: function(frm) { }, - enabled: function(frm) { - if(!frm.doc.enabled) { - frm.trigger("set_enable_hub_primary_button"); - } else { - frm.page.set_primary_action(__("Save Settings"), () => { - frm.save(); - }); - } - }, hub_user_email: function(frm) { if(frm.doc.hub_user_email){ @@ -55,39 +25,6 @@ frappe.ui.form.on("Hub Settings", { } }, - set_enable_hub_primary_button: (frm) => { - frm.page.set_primary_action(__("Enable Hub"), () => { - if(frappe.session.user === "Administrator") { - frappe.msgprint(__("Please login as another user.")) - } else { - // frappe.verify_password(() => { - - // } ); - - frm.trigger("call_pre_reg"); - // frm.trigger("call_register"); - - } - }); - }, - - call_pre_reg: (frm) => { - this.frm.call({ - doc: this.frm.doc, - method: "pre_reg", - args: {}, - freeze: true, - callback: function(r) { - console.log(r.message); - authorize(frm, r.message.client_id, r.message.redirect_uri); - }, - onerror: function() { - frappe.msgprint(__("Wrong Password")); - frm.set_value("enabled", 0); - } - }); - }, - call_register: (frm) => { this.frm.call({ doc: this.frm.doc, @@ -111,67 +48,3 @@ frappe.ui.form.on("Hub Settings", { }); }, }); - -// let hub_url = 'https://hubmarket.org' -let hub_url = 'http://159.89.175.122' -// let hub_url = 'http://erpnext.hub:8000' - -function authorize(frm, client_id, redirect_uri) { - - // queryStringData is details of OAuth Client (Implicit Grant) on Custom App - var queryStringData = { - response_type : "token", - client_id : client_id, - redirect_uri : redirect_uri - } - - // Get current raw route and build url - const route = "/desk#" + frappe.get_raw_route_str(); - localStorage.removeItem("route"); // Clear previously set route if any - localStorage.setItem("route", route); - - // Go authorize! - let api_route = "/api/method/frappe.integrations.oauth2.authorize?"; - let url = hub_url + api_route + $.param(queryStringData); - window.location.replace(url, 'test'); -} - -function get_user_details(frm, token, email) { - console.log('user_details'); - var route = localStorage.getItem("route"); - if (token && route) { - // Clean up access token from route - frappe.set_route(frappe.get_route().join("/")) - - // query protected resource e.g. Hub Items with token - var call = { - "async": true, - "crossDomain": true, - "url": hub_url + "/api/resource/User", - "method": "GET", - "data": { - // "email": email, - "fields": '["name", "first_name", "language"]', - "limit_page_length": 1 - }, - "headers": { - "authorization": "Bearer " + token, - "content-type": "application/x-www-form-urlencoded" - } - } - $.ajax(call).done(function (response) { - // display openid profile - console.log('response', response); - - let data = response.data[0]; - frm.set_value("enabled", 1); - frm.set_value("hub_username", data.first_name); - frm.set_value("hub_user_status", "Starter"); - frm.set_value("language", data.language); - frm.save(); - - // clear route from localStorage - localStorage.removeItem("route"); - }); - } -} diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.json b/erpnext/hub_node/doctype/hub_settings/hub_settings.json index c742c57b2b..61ef48ca54 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json @@ -44,6 +44,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sync_in_progress", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sync in Progress", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -611,7 +643,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-07-24 18:53:09.442523", + "modified": "2018-07-26 15:56:36.104768", "modified_by": "cave@aperture.com", "module": "Hub Node", "name": "Hub Settings", diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 6d0f3e96ac..7a71c11ea4 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -138,11 +138,14 @@ erpnext.hub.Marketplace = class Marketplace { } if (route[1] === 'profile' && !this.subpages.profile) { - this.subpages.profile = new erpnext.hub.Profile(this.$body, this.hub_settings); + this.subpages.profile = new erpnext.hub.Profile(this.$body, {data: this.hub_settings}); } if (route[1] === 'publish' && !this.subpages.publish) { - this.subpages.publish = new erpnext.hub.Publish(this.$body); + this.subpages.publish = new erpnext.hub.Publish( + this.$body, + {sync_in_progress: this.hub_settings.sync_in_progress} + ); } if (!Object.keys(this.subpages).includes(route[1])) { @@ -157,9 +160,9 @@ erpnext.hub.Marketplace = class Marketplace { } class SubPage { - constructor(parent) { + constructor(parent, options) { this.$parent = $(parent); - this.make_wrapper(); + this.make_wrapper(options); } make_wrapper() { @@ -621,8 +624,14 @@ erpnext.hub.Profile = class Profile extends SubPage { render() {} } erpnext.hub.Publish = class Publish extends SubPage { - make_wrapper() { + make_wrapper(options) { super.make_wrapper(); + this.sync_in_progress = options.sync_in_progress; + + this.load_publish_page(); + } + + load_publish_page() { const title_html = `${__('Select Products to Publish')}`; const info = `

    ${__("Status decided by the 'Publish in Hub' field in Item.")}

    `; const subtitle_html = ` @@ -660,17 +669,10 @@ erpnext.hub.Publish = class Publish extends SubPage { } setup_events() { - // this.$wrapper.find('.select-all').on('click', () => { - // this.$wrapper.find('.hub-card').addClass('active'); - // }); - - // this.$wrapper.find('.deselect-all').on('click', () => { - // this.$wrapper.find('.hub-card').removeClass('active'); - // }); - this.$wrapper.find('.publish-items').on('click', () => { this.publish_selected_items() .then(r => { + this.load_publishing_state(); frappe.msgprint('check'); }); }); @@ -687,6 +689,11 @@ erpnext.hub.Publish = class Publish extends SubPage { } get_items_and_render() { + if(this.sync_in_progress) { + this.load_publishing_state(); + return; + } + this.$wrapper.find('.hub-card-container').empty(); this.get_valid_items() .then(r => { @@ -703,6 +710,18 @@ erpnext.hub.Publish = class Publish extends SubPage { items_container.addClass('static').on('click', '.hub-card', (e) => { const $target = $(e.currentTarget); $target.toggleClass('active'); + + // Get total items + const total_items = this.$wrapper.find('.hub-card.active').length; + const more_than_one = total_items > 1; + this.$wrapper.find('.publish-items') + .html(__('Publish ' + total_items + ' item' + (more_than_one ? 's' : ''))); + + // if($target.hasClass('active')) { + // $target.addClass('active'); + // } else { + // $target.removeClass('active'); + // } }); this.$wrapper.append(items_container); @@ -717,24 +736,22 @@ erpnext.hub.Publish = class Publish extends SubPage { ); } + load_publishing_state() { + this.$wrapper.html(`
    + Publishing +
    `); + } + publish_selected_items() { const items_to_publish = []; - const items_to_unpublish = []; - this.$wrapper.find('.hub-card').map(function () { - const active = $(this).hasClass('active'); - - if(active) { - items_to_publish.push($(this).attr("data-id")); - } else { - items_to_unpublish.push($(this).attr("data-id")); - } + this.$wrapper.find('.hub-card.active').map(function () { + items_to_publish.push($(this).attr("data-id")); }); return frappe.call( 'erpnext.hub_node.publish_selected_items', { - items_to_publish: items_to_publish, - items_to_unpublish: items_to_unpublish + items_to_publish: items_to_publish } ); } From d450c0ac0819b74a265a93808d12dd304968827f Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 26 Jul 2018 17:24:33 +0530 Subject: [PATCH 012/154] [hub][publish] in_progress/empty state --- erpnext/hub_node/__init__.py | 12 ++++++----- .../doctype/hub_settings/hub_settings.py | 8 +++++--- erpnext/public/js/hub/hub_listing.js | 20 +++++++++---------- erpnext/public/less/hub.less | 5 +++++ 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index f9132397c0..7d846d1b94 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -55,13 +55,15 @@ def get_valid_items(search_value=''): @frappe.whitelist() def publish_selected_items(items_to_publish): - # for item_code in json.loads(items_to_publish): - # frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) + for item_code in json.loads(items_to_publish): + frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - time.sleep(3) + # frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1) + # time.sleep(10) + # frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0) - # hub_settings = frappe.get_doc('Hub Settings') - # hub_settings.sync() + hub_settings = frappe.get_doc('Hub Settings') + hub_settings.sync() return diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 551423c8a2..52fdb8c522 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -46,10 +46,12 @@ class HubSettings(Document): doc = frappe.get_doc({ 'doctype': 'Data Migration Run', 'data_migration_plan': 'Hub Sync', - 'data_migration_connector': 'Hub Connector' + 'data_migration_connector': 'Hub Connector', }).insert() + self.sync_in_progress = 1 doc.run() + self.sync_in_progress = 0 def pre_reg(self): site_name = frappe.local.site + ':' + str(frappe.conf.webserver_port) @@ -86,9 +88,9 @@ class HubSettings(Document): post_url = hub_url + '/api/method/hub.hub.api.register' response = requests.post(post_url, data=data, headers = {'accept': 'application/json'}) - + response.raise_for_status() - + if response.ok: message = response.json().get('message') else: diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 7a71c11ea4..bde1e8fe54 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -670,9 +670,9 @@ erpnext.hub.Publish = class Publish extends SubPage { setup_events() { this.$wrapper.find('.publish-items').on('click', () => { + this.load_publishing_state(); this.publish_selected_items() .then(r => { - this.load_publishing_state(); frappe.msgprint('check'); }); }); @@ -716,12 +716,6 @@ erpnext.hub.Publish = class Publish extends SubPage { const more_than_one = total_items > 1; this.$wrapper.find('.publish-items') .html(__('Publish ' + total_items + ' item' + (more_than_one ? 's' : ''))); - - // if($target.hasClass('active')) { - // $target.addClass('active'); - // } else { - // $target.removeClass('active'); - // } }); this.$wrapper.append(items_container); @@ -737,9 +731,9 @@ erpnext.hub.Publish = class Publish extends SubPage { } load_publishing_state() { - this.$wrapper.html(`
    - Publishing -
    `); + this.$wrapper.html(get_empty_state( + 'Publishing items ... You will be notified once published.' + )); } publish_selected_items() { @@ -757,6 +751,12 @@ erpnext.hub.Publish = class Publish extends SubPage { } } +function get_empty_state(message) { + return `
    +

    ${message}

    +
    ` +} + function get_item_card_container_html(items, title='') { const items_html = (items || []).map(item => get_item_card_html(item)).join(''); diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 8c7392ad49..bd503fe1e3 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -156,6 +156,11 @@ body[data-route^="marketplace/"] { padding: 15px 25px; } + .empty-state { + justify-content: center; + padding-top: 159px; + } + .form-container { .frappe-control { max-width: 100% !important; From bcd7077342dd91f378f10f905ed0e25ffcd82229 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 26 Jul 2018 18:20:58 +0530 Subject: [PATCH 013/154] [hub] remove sync_in_progress set --- erpnext/hub_node/doctype/hub_settings/hub_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 52fdb8c522..319c76325e 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -51,7 +51,7 @@ class HubSettings(Document): self.sync_in_progress = 1 doc.run() - self.sync_in_progress = 0 + # self.sync_in_progress = 0 def pre_reg(self): site_name = frappe.local.site + ':' + str(frappe.conf.webserver_port) From d885248309bb82f68da208a5cff8d0caf4a17b5a Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 26 Jul 2018 18:21:33 +0530 Subject: [PATCH 014/154] [hub] remove sync_in _progress set --- erpnext/hub_node/doctype/hub_settings/hub_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 319c76325e..813c881291 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -49,7 +49,7 @@ class HubSettings(Document): 'data_migration_connector': 'Hub Connector', }).insert() - self.sync_in_progress = 1 + # self.sync_in_progress = 1 doc.run() # self.sync_in_progress = 0 From 2ca5afa21499c95ead4749d47e399c4ffb5971e2 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 26 Jul 2018 18:35:04 +0530 Subject: [PATCH 015/154] Move marketplace ui to marketplace.js --- erpnext/public/js/hub/hub_factory.js | 2 +- erpnext/public/js/hub/hub_listing.js | 789 --------------------------- 2 files changed, 1 insertion(+), 790 deletions(-) diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js index d54787af36..c94edf4e4d 100644 --- a/erpnext/public/js/hub/hub_factory.js +++ b/erpnext/public/js/hub/hub_factory.js @@ -14,7 +14,7 @@ frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views. make(page_name) { const assets = [ - '/assets/erpnext/js/hub/hub_listing.js' + '/assets/erpnext/js/hub/marketplace.js' ]; frappe.require(assets, () => { diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index eb55806784..368c723e5b 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -1,792 +1,3 @@ -frappe.provide('erpnext.hub'); - -erpnext.hub.Marketplace = class Marketplace { - constructor({ parent }) { - this.$parent = $(parent); - this.page = parent.page; - - frappe.db.get_doc('Hub Settings') - .then(doc => { - this.hub_settings = doc; - this.registered = doc.registered; - - this.setup_header(); - this.make_sidebar(); - this.make_body(); - this.setup_events(); - this.refresh(); - }); - } - - setup_header() { - this.page.set_title(__('Marketplace')); - } - - setup_events() { - this.$parent.on('click', '[data-route]', (e) => { - const $target = $(e.currentTarget); - const route = $target.data().route; - frappe.set_route(route); - - e.stopPropagation(); - }); - } - - make_sidebar() { - this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); - - const user_specific_items_html = this.registered - ? `
  • - ${__('Your Profile')} -
  • -
  • - ${__('Publish Products')} -
  • ` - - : `
  • - ${__('Become a seller')} -
  • `; - - this.$sidebar.append(` -
      -
    • - ${__('Browse')} -
    • -
    • - ${__('Favorites')} -
    • - ${user_specific_items_html} -
    - `); - - this.make_sidebar_categories(); - } - - make_sidebar_categories() { - frappe.call('erpnext.hub_node.get_categories') - .then(r => { - const categories = r.message.map(d => d.value).sort(); - const sidebar_items = [ - `
  • - ${__('Category')} -
  • `, - `
  • - ${__('All')} -
  • `, - ...(this.registered - ? [`
  • - ${__('Your Products')} -
  • `] - : []), - ...categories.map(category => ` -
  • - ${__(category)} -
  • - `) - ]; - - this.$sidebar.append(` -
      - ${sidebar_items.join('')} -
    - `); - - this.update_sidebar(); - }); - } - - make_body() { - this.$body = this.$parent.find('.layout-main-section'); - } - - update_sidebar() { - const route = frappe.get_route_str(); - const $sidebar_item = this.$sidebar.find(`[data-route="${route}"]`); - - const $siblings = this.$sidebar.find('[data-route]'); - $siblings.removeClass('active').addClass('text-muted'); - - $sidebar_item.addClass('active').removeClass('text-muted'); - } - - refresh() { - const route = frappe.get_route(); - this.subpages = this.subpages || {}; - - for (let page in this.subpages) { - this.subpages[page].hide(); - } - - if (route[1] === 'home' && !this.subpages.home) { - this.subpages.home = new erpnext.hub.Home(this.$body); - } - - if (route[1] === 'favourites' && !this.subpages.favourites) { - this.subpages.favourites = new erpnext.hub.Favourites(this.$body); - } - - if (route[1] === 'category' && route[2] && !this.subpages.category) { - this.subpages.category = new erpnext.hub.Category(this.$body); - } - - if (route[1] === 'item' && route[2] && !this.subpages.item) { - this.subpages.item = new erpnext.hub.Item(this.$body); - } - - if (route[1] === 'register' && !this.subpages.register) { - this.subpages.register = new erpnext.hub.Register(this.$body); - } - - if (route[1] === 'publish' && !this.subpages.publish) { - this.subpages.publish = new erpnext.hub.Publish(this.$body); - } - - - if (!Object.keys(this.subpages).includes(route[1])) { - frappe.show_not_found(); - return; - } - - this.update_sidebar(); - frappe.utils.scroll_to(0); - this.subpages[route[1]].show(); - } -} - -class SubPage { - constructor(parent) { - this.$parent = $(parent); - this.make_wrapper(); - } - - make_wrapper() { - const page_name = frappe.get_route()[1]; - this.$wrapper = $(`
    `).appendTo(this.$parent); - this.hide(); - } - - show() { - this.refresh(); - this.$wrapper.show(); - } - - hide() { - this.$wrapper.hide(); - } -} - -erpnext.hub.Home = class Home extends SubPage { - make_wrapper() { - super.make_wrapper(); - this.make_search_bar(); - } - - refresh() { - this.get_items_and_render(); - } - - get_items_and_render() { - this.$wrapper.find('.hub-card-container').empty(); - this.get_items() - .then(r => { - erpnext.hub.hub_item_cache = r.message; - this.render(r.message); - }); - } - - get_items() { - return frappe.call('erpnext.hub_node.get_list', { - doctype: 'Hub Item', - filters: { - image: ['like', 'http%'] - } - }); - } - - make_search_bar() { - const $search = $(` -
    - -
    ` - ); - this.$wrapper.append($search); - const $search_input = $search.find('input'); - - $search_input.on('keydown', frappe.utils.debounce((e) => { - if (e.which === frappe.ui.keyCode.ENTER) { - this.search_value = $search_input.val(); - this.get_items_and_render(); - } - }, 300)); - } - - render(items) { - const html = get_item_card_container_html(items, __('Recently Published')); - this.$wrapper.append(html) - } -} - -erpnext.hub.Favourites = class Favourites extends SubPage { - refresh() { - this.get_favourites() - .then(r => { - this.render(r.message); - }); - } - - get_favourites() { - return frappe.call('erpnext.hub_node.get_item_favourites'); - } - - render(items) { - this.$wrapper.find('.hub-card-container').empty(); - const html = get_item_card_container_html(items, __('Favourites')); - this.$wrapper.append(html) - } -} - -erpnext.hub.Category = class Category extends SubPage { - refresh() { - this.category = frappe.get_route()[2]; - this.get_items_for_category(this.category) - .then(r => { - this.render(r.message); - }); - } - - get_items_for_category(category) { - this.$wrapper.find('.hub-card-container').empty(); - return frappe.call('erpnext.hub_node.get_list', { - doctype: 'Hub Item', - filters: { - hub_category: category - } - }); - } - - render(items) { - const html = get_item_card_container_html(items, __(this.category)); - this.$wrapper.append(html) - } -} - -erpnext.hub.Item = class Item extends SubPage { - refresh() { - this.hub_item_code = frappe.get_route()[2]; - - this.get_item(this.hub_item_code) - .then(item => { - this.render(item); - }); - } - - get_item(hub_item_code) { - return new Promise(resolve => { - const item = (erpnext.hub.hub_item_cache || []).find(item => item.name === hub_item_code) - - if (item) { - resolve(item); - } else { - frappe.call('erpnext.hub_node.get_list', { - doctype: 'Hub Item', - filters: { - name: hub_item_code - } - }) - .then(r => { - resolve(r.message[0]); - }); - } - }); - } - - render(item) { - const title = item.item_name || item.name; - const company = item.company_name; - - const who = __('Posted By {0}', [company]); - const when = comment_when(item.creation); - - const city = item.seller_city ? item.seller_city + ', ' : ''; - const country = item.country ? item.country : ''; - const where = `${city}${country}`; - - const dot_spacer = ''; - - const description = item.description || ''; - - const rating_html = get_rating_html(item); - const rating_count = item.reviews.length > 0 ? `(${item.reviews.length} reviews)` : ''; - - const html = ` -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -

    ${title}

    -
    -

    ${where}${dot_spacer}${when}

    -

    ${rating_html}${rating_count}

    -
    -
    -
    - ${description ? - `${__('Description')} -

    ${description}

    - ` : __('No description') - } -
    -
    -
    -
    -
    - Seller Information -
    -
    - -
    -
    - ${company} -

    - Contact Seller -

    -
    -
    - -
    - -
    -
    - `; - - this.$wrapper.html(html); - - this.make_review_area(); - this.render_reviews(item); - } - - make_review_area() { - this.comment_area = new frappe.ui.ReviewArea({ - parent: this.$wrapper.find('.timeline-head').empty(), - mentions: [], - on_submit: (val) => { - val.user = frappe.session.user; - val.username = frappe.session.user_fullname; - - frappe.call({ - method: 'erpnext.hub_node.send_review', - args: { - hub_item_code: this.hub_item_code, - review: val - }, - callback: (r) => { - console.log(r); - this.render_reviews(r.message); - this.comment_area.reset(); - }, - freeze: true - }); - } - }); - } - - render_reviews(item) { - this.$wrapper.find('.timeline-items').empty(); - item.reviews.forEach(review => this.render_review(review, item)); - } - - render_review(review, item) { - let username = review.username || review.user || __("Anonymous"); - - let image_html = review.user_image - ? `
    ` - : `
    ${frappe.get_abbr(username)}
    ` - - let edit_html = review.own - ? ` -
    - - ${'data.edit'} - -
    ` - : ''; - - let rating_html = get_rating_html(item); - - const $timeline_items = this.$wrapper.find('.timeline-items'); - - $(this.get_timeline_item(review, image_html, edit_html, rating_html)) - .appendTo($timeline_items); - } - - get_timeline_item(data, image_html, edit_html, rating_html) { - return `
    - -
    -
    -
    ${edit_html}
    - -
    - - ${image_html} - - -
    - - - ${data.username} - - - - - -
    -
    -
    -
    -

    - ${rating_html} -

    -
    ${data.subject}
    -

    - ${data.content} -

    -
    -
    -
    -
    -
    `; - } -} -erpnext.hub.Register = class Register extends SubPage { - make_wrapper() { - super.make_wrapper(); - this.$register_container = $(`
    `) - .appendTo(this.$wrapper); - this.$form_container = $('
    ') - .appendTo(this.$wrapper); - } - - refresh() { - this.$register_container.empty(); - this.$form_container.empty(); - this.render(); - } - - render() { - this.make_field_group(); - } - - make_field_group() { - const fields = [ - { - fieldtype: 'Link', - fieldname: 'company', - label: __('Company'), - options: 'Company', - onchange: () => { - const value = this.field_group.get_value('company'); - - if (value) { - frappe.db.get_doc('Company', value) - .then(company => { - this.field_group.set_values({ - country: company.country, - company_email: company.email, - currency: company.default_currency - }); - }); - } - } - }, - { - fieldname: 'company_email', - label: __('Email'), - fieldtype: 'Data' - }, - { - fieldname: 'country', - label: __('Country'), - fieldtype: 'Read Only' - }, - { - fieldname: 'currency', - label: __('Currency'), - fieldtype: 'Read Only' - }, - { - fieldtype: 'Text', - label: __('About your Company'), - fieldname: 'company_description' - } - ]; - - this.field_group = new frappe.ui.FieldGroup({ - parent: this.$form_container, - fields - }); - - this.field_group.make(); - - this.$form_container.find('.form-column').append(` -
    - -
    - `); - - this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller')) - - this.$form_container.on('click', '.btn-register', () => { - const form_values = this.field_group.get_values(); - frappe.call('erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', form_values) - .then(() => { - // Reload page and things ... but for now - frappe.msgprint('Registered successfully.'); - }); - }); - } -} - -erpnext.hub.Publish = class Publish extends SubPage { - make_wrapper() { - super.make_wrapper(); - const title_html = `${__('Select Products to Publish')}`; - const info = `

    ${__("Status decided by the 'Publish in Hub' field in Item.")}

    `; - const subtitle_html = ` -

    - ${__(`Only products with an image, description and category can be published. - Please update them if an item in your inventory does not appear.`)} -

    `; - const publish_button_html = ``; - - const select_all_button = ``; - const deselect_all_button = ``; - - const search_html = `
    - -
    `; - - const subpage_header = $(` -
    -
    - ${title_html} - ${subtitle_html} -
    - ${publish_button_html} -
    - - ${search_html} - - ${select_all_button} - ${deselect_all_button} - `); - - this.$wrapper.append(subpage_header); - - this.setup_events(); - } - - setup_events() { - this.$wrapper.find('.select-all').on('click', () => { - this.$wrapper.find('.hub-card').addClass('active'); - }); - - this.$wrapper.find('.deselect-all').on('click', () => { - this.$wrapper.find('.hub-card').removeClass('active'); - }); - - this.$wrapper.find('.publish-items').on('click', () => { - this.publish_selected_items() - .then(r => { - frappe.msgprint('check'); - }); - }); - - const $search_input = this.$wrapper.find('.hub-search-container input'); - this.search_value = ''; - - $search_input.on('keydown', frappe.utils.debounce((e) => { - if (e.which === frappe.ui.keyCode.ENTER) { - this.search_value = $search_input.val(); - this.get_items_and_render(); - } - }, 300)); - } - - get_items_and_render() { - this.$wrapper.find('.hub-card-container').empty(); - this.get_valid_items() - .then(r => { - this.render(r.message); - }); - } - - refresh() { - this.get_items_and_render(); - } - - render(items) { - const items_container = $(get_item_card_container_html(items)); - items_container.addClass('static').on('click', '.hub-card', (e) => { - const $target = $(e.currentTarget); - $target.toggleClass('active'); - }); - - this.$wrapper.append(items_container); - } - - get_valid_items() { - return frappe.call( - 'erpnext.hub_node.get_valid_items', - { - search_value: this.search_value - } - ); - } - - publish_selected_items() { - const items_to_publish = []; - const items_to_unpublish = []; - this.$wrapper.find('.hub-card').map(function () { - const active = $(this).hasClass('active'); - - if(active) { - items_to_publish.push($(this).attr("data-id")); - } else { - items_to_unpublish.push($(this).attr("data-id")); - } - }); - - return frappe.call( - 'erpnext.hub_node.publish_selected_items', - { - items_to_publish: items_to_publish, - items_to_unpublish: items_to_unpublish - } - ); - } -} - -function get_item_card_container_html(items, title='') { - const items_html = (items || []).map(item => get_item_card_html(item)).join(''); - - const html = `
    -
    - ${title} -
    - ${items_html} -
    `; - - return html; -} - -function get_item_card_html(item) { - const item_name = item.item_name || item.name; - const title = strip_html(item_name); - const img_url = item.image; - - const company_name = item.company_name; - - const active = item.publish_in_hub; - - const id = item.hub_item_code || item.item_code; - - // Subtitle - let subtitle = [comment_when(item.creation)]; - const rating = get_rating(item); - if (rating > 0) { - subtitle.push(rating + ``) - } - subtitle.push(company_name); - - let dot_spacer = ''; - subtitle = subtitle.join(dot_spacer); - - // Decide item link - const isLocal = item.source_type === "local"; - const route = !isLocal - ? `marketplace/item/${item.hub_item_code}` - : `Form/Item/${item.item_name}`; - - const card_route = isLocal ? '' : `data-route='${route}'`; - - const show_local_item_button = isLocal - ? `
    - -
    ` - : ''; - - const item_html = ` -
    -
    -
    -
    -
    ${title}
    -
    ${subtitle}
    -
    - -
    -
    - -
    - ${show_local_item_button} -
    -
    -
    - `; - - return item_html; -} - -function get_rating(item) { - const review_length = (item.reviews || []).length; - return review_length - ? item.reviews - .map(r => r.rating) - .reduce((a, b) => a + b, 0) / review_length - : 0; -} - -function get_rating_html(item) { - const rating = get_rating(item); - let rating_html = ``; - for (var i = 0; i < 5; i++) { - let star_class = 'fa-star'; - if (i >= rating) star_class = 'fa-star-o'; - rating_html += ``; - } - return rating_html; -} erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { setup_defaults() { From f4884f810885ada0359b7a069a2318590c91604b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 26 Jul 2018 18:37:01 +0530 Subject: [PATCH 016/154] Marketplace - Cache hub calls - call_hub_method using FrappeClient connection - Become a seller UX fixes --- erpnext/hub_node/__init__.py | 13 + .../doctype/hub_settings/hub_settings.py | 20 +- erpnext/public/js/hub/marketplace.js | 844 ++++++++++++++++++ 3 files changed, 867 insertions(+), 10 deletions(-) create mode 100644 erpnext/public/js/hub/marketplace.js diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index c8e40eb8e8..d15785f279 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -14,6 +14,19 @@ def enable_hub(): frappe.db.commit() return hub_settings +@frappe.whitelist() +def call_hub_method(method, params=None): + connection = get_client_connection() + + params = json.loads(params) + params.update({ + 'cmd': 'hub.hub.api.' + method + }) + + response = connection.post_api('hub.hub.api.' + method, params) + response = connection.post_request(params) + return response + @frappe.whitelist() def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=None): connection = get_client_connection() diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 551423c8a2..561f1f0faa 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -86,15 +86,20 @@ class HubSettings(Document): post_url = hub_url + '/api/method/hub.hub.api.register' response = requests.post(post_url, data=data, headers = {'accept': 'application/json'}) - + response.raise_for_status() - + if response.ok: message = response.json().get('message') else: frappe.throw(json.loads(response.text)) - return message.get('password') if message else None + if message.get('email'): + self.create_hub_connector(message) + self.registered = 1 + self.save() + + return message or None def unregister(self): """ Disable the User on hub.erpnext.org""" @@ -149,11 +154,6 @@ def sync(): def register_seller(**kwargs): settings = frappe.get_doc('Hub Settings') settings.update(kwargs) - password = settings.register() + message = settings.register() - print(password) - - # if password: - # self.create_hub_connector(message) - # self.registered = 1 - # self.save() + return message.get('email') diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js new file mode 100644 index 0000000000..2526b95eaf --- /dev/null +++ b/erpnext/public/js/hub/marketplace.js @@ -0,0 +1,844 @@ +frappe.provide('hub'); +frappe.provide('erpnext.hub'); + +erpnext.hub.Marketplace = class Marketplace { + constructor({ parent }) { + this.$parent = $(parent); + this.page = parent.page; + + frappe.db.get_doc('Hub Settings') + .then(doc => { + this.hub_settings = doc; + this.registered = doc.registered; + + this.setup_header(); + this.make_sidebar(); + this.make_body(); + this.setup_events(); + this.refresh(); + }); + } + + setup_header() { + this.page.set_title(__('Marketplace')); + } + + setup_events() { + this.$parent.on('click', '[data-route]', (e) => { + const $target = $(e.currentTarget); + const route = $target.data().route; + frappe.set_route(route); + + e.stopPropagation(); + }); + } + + make_sidebar() { + this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); + + this.make_sidebar_nav_buttons(); + this.make_sidebar_categories(); + } + + make_sidebar_nav_buttons() { + let $nav_group = this.$sidebar.find('[data-nav-buttons]'); + if (!$nav_group.length) { + $nav_group = $('
      ').appendTo(this.$sidebar); + } + $nav_group.empty(); + + const user_specific_items_html = this.registered + ? `
    • + ${__('Your Profile')} +
    • +
    • + ${__('Publish Products')} +
    • ` + + : `
    • + ${__('Become a seller')} +
    • `; + + $nav_group.append(` +
    • + ${__('Browse')} +
    • +
    • + ${__('Favorites')} +
    • + ${user_specific_items_html} + `); + } + + make_sidebar_categories() { + frappe.call('erpnext.hub_node.get_categories') + .then(r => { + const categories = r.message.map(d => d.value).sort(); + const sidebar_items = [ + `
    • + ${__('Category')} +
    • `, + `
    • + ${__('All')} +
    • `, + ...(this.registered + ? [`
    • + ${__('Your Products')} +
    • `] + : []), + ...categories.map(category => ` +
    • + ${__(category)} +
    • + `) + ]; + + this.$sidebar.append(` +
        + ${sidebar_items.join('')} +
      + `); + + this.update_sidebar(); + }); + } + + make_body() { + this.$body = this.$parent.find('.layout-main-section'); + + this.$body.on('seller-registered', () => { + this.registered = 1; + this.make_sidebar_nav_buttons(); + }); + } + + update_sidebar() { + const route = frappe.get_route_str(); + const $sidebar_item = this.$sidebar.find(`[data-route="${route}"]`); + + const $siblings = this.$sidebar.find('[data-route]'); + $siblings.removeClass('active').addClass('text-muted'); + + $sidebar_item.addClass('active').removeClass('text-muted'); + } + + refresh() { + const route = frappe.get_route(); + this.subpages = this.subpages || {}; + + for (let page in this.subpages) { + this.subpages[page].hide(); + } + + if (route[1] === 'home' && !this.subpages.home) { + this.subpages.home = new erpnext.hub.Home(this.$body); + } + + if (route[1] === 'favourites' && !this.subpages.favourites) { + this.subpages.favourites = new erpnext.hub.Favourites(this.$body); + } + + if (route[1] === 'category' && route[2] && !this.subpages.category) { + this.subpages.category = new erpnext.hub.Category(this.$body); + } + + if (route[1] === 'item' && route[2] && !this.subpages.item) { + this.subpages.item = new erpnext.hub.Item(this.$body); + } + + if (route[1] === 'register' && !this.subpages.register) { + // if (this.registered) { + // frappe.set_route('marketplace', 'home'); + // return; + // } + this.subpages.register = new erpnext.hub.Register(this.$body); + } + + if (route[1] === 'publish' && !this.subpages.publish) { + this.subpages.publish = new erpnext.hub.Publish(this.$body); + } + + + if (!Object.keys(this.subpages).includes(route[1])) { + frappe.show_not_found(); + return; + } + + this.update_sidebar(); + frappe.utils.scroll_to(0); + this.subpages[route[1]].show(); + } +} + +class SubPage { + constructor(parent) { + this.$parent = $(parent); + this.make_wrapper(); + } + + make_wrapper() { + const page_name = frappe.get_route()[1]; + this.$wrapper = $(`
      `).appendTo(this.$parent); + this.hide(); + } + + show() { + this.refresh(); + this.$wrapper.show(); + } + + hide() { + this.$wrapper.hide(); + } +} + +erpnext.hub.Home = class Home extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.make_search_bar(); + } + + refresh() { + this.get_items_and_render(); + } + + get_items_and_render() { + this.$wrapper.find('.hub-card-container').empty(); + this.get_items() + .then(items => { + this.render(items); + }); + } + + get_items() { + return hub.call('get_data_for_homepage'); + } + + make_search_bar() { + const $search = $(` +
      + +
      ` + ); + this.$wrapper.append($search); + const $search_input = $search.find('input'); + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + this.search_value = $search_input.val(); + this.get_items_and_render(); + } + }, 300)); + } + + render(items) { + const html = get_item_card_container_html(items, __('Recently Published')); + this.$wrapper.append(html) + } +} + +erpnext.hub.Favourites = class Favourites extends SubPage { + refresh() { + this.get_favourites() + .then(r => { + this.render(r.message); + }); + } + + get_favourites() { + return frappe.call('erpnext.hub_node.get_item_favourites'); + } + + render(items) { + this.$wrapper.find('.hub-card-container').empty(); + const html = get_item_card_container_html(items, __('Favourites')); + this.$wrapper.append(html) + } +} + +erpnext.hub.Category = class Category extends SubPage { + refresh() { + this.category = frappe.get_route()[2]; + this.get_items_for_category(this.category) + .then(r => { + this.render(r.message); + }); + } + + get_items_for_category(category) { + this.$wrapper.find('.hub-card-container').empty(); + return frappe.call('erpnext.hub_node.get_list', { + doctype: 'Hub Item', + filters: { + hub_category: category + } + }); + } + + render(items) { + const html = get_item_card_container_html(items, __(this.category)); + this.$wrapper.append(html) + } +} + +erpnext.hub.Item = class Item extends SubPage { + refresh() { + this.hub_item_code = frappe.get_route()[2]; + + this.get_item(this.hub_item_code) + .then(item => { + this.render(item); + }); + } + + get_item(hub_item_code) { + return new Promise(resolve => { + const item = (erpnext.hub.hub_item_cache || []).find(item => item.name === hub_item_code) + + if (item) { + resolve(item); + } else { + frappe.call('erpnext.hub_node.get_list', { + doctype: 'Hub Item', + filters: { + name: hub_item_code + } + }) + .then(r => { + resolve(r.message[0]); + }); + } + }); + } + + render(item) { + const title = item.item_name || item.name; + const company = item.company_name; + + const who = __('Posted By {0}', [company]); + const when = comment_when(item.creation); + + const city = item.seller_city ? item.seller_city + ', ' : ''; + const country = item.country ? item.country : ''; + const where = `${city}${country}`; + + const dot_spacer = ''; + + const description = item.description || ''; + + const rating_html = get_rating_html(item); + const rating_count = item.reviews.length > 0 ? `(${item.reviews.length} reviews)` : ''; + + const html = ` +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +

      ${title}

      +
      +

      ${where}${dot_spacer}${when}

      +

      ${rating_html}${rating_count}

      +
      +
      +
      + ${description ? + `${__('Description')} +

      ${description}

      + ` : __('No description') + } +
      +
      +
      +
      +
      + Seller Information +
      +
      + +
      +
      + ${company} +

      + Contact Seller +

      +
      +
      + +
      + +
      +
      + `; + + this.$wrapper.html(html); + + this.make_review_area(); + this.render_reviews(item); + } + + make_review_area() { + this.comment_area = new frappe.ui.ReviewArea({ + parent: this.$wrapper.find('.timeline-head').empty(), + mentions: [], + on_submit: (val) => { + val.user = frappe.session.user; + val.username = frappe.session.user_fullname; + + frappe.call({ + method: 'erpnext.hub_node.send_review', + args: { + hub_item_code: this.hub_item_code, + review: val + }, + callback: (r) => { + console.log(r); + this.render_reviews(r.message); + this.comment_area.reset(); + }, + freeze: true + }); + } + }); + } + + render_reviews(item) { + this.$wrapper.find('.timeline-items').empty(); + item.reviews.forEach(review => this.render_review(review, item)); + } + + render_review(review, item) { + let username = review.username || review.user || __("Anonymous"); + + let image_html = review.user_image + ? `
      ` + : `
      ${frappe.get_abbr(username)}
      ` + + let edit_html = review.own + ? ` +
      + + ${'data.edit'} + +
      ` + : ''; + + let rating_html = get_rating_html(item); + + const $timeline_items = this.$wrapper.find('.timeline-items'); + + $(this.get_timeline_item(review, image_html, edit_html, rating_html)) + .appendTo($timeline_items); + } + + get_timeline_item(data, image_html, edit_html, rating_html) { + return `
      + +
      +
      +
      ${edit_html}
      + +
      + + ${image_html} + + +
      + + + ${data.username} + + + + + +
      +
      +
      +
      +

      + ${rating_html} +

      +
      ${data.subject}
      +

      + ${data.content} +

      +
      +
      +
      +
      +
      `; + } +} +erpnext.hub.Register = class Register extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.$register_container = $(`
      `) + .appendTo(this.$wrapper); + this.$form_container = $('
      ') + .appendTo(this.$wrapper); + } + + refresh() { + this.$register_container.empty(); + this.$form_container.empty(); + this.render(); + } + + render() { + this.make_field_group(); + } + + make_field_group() { + const fields = [ + { + fieldtype: 'Link', + fieldname: 'company', + label: __('Company'), + options: 'Company', + onchange: () => { + const value = this.field_group.get_value('company'); + + if (value) { + frappe.db.get_doc('Company', value) + .then(company => { + this.field_group.set_values({ + country: company.country, + company_email: company.email, + currency: company.default_currency + }); + }); + } + } + }, + { + fieldname: 'company_email', + label: __('Email'), + fieldtype: 'Data' + }, + { + fieldname: 'country', + label: __('Country'), + fieldtype: 'Read Only' + }, + { + fieldname: 'currency', + label: __('Currency'), + fieldtype: 'Read Only' + }, + { + fieldtype: 'Text', + label: __('About your Company'), + fieldname: 'company_description' + } + ]; + + this.field_group = new frappe.ui.FieldGroup({ + parent: this.$form_container, + fields + }); + + this.field_group.make(); + + const default_company = frappe.defaults.get_default('company'); + this.field_group.set_value('company', default_company); + + this.$form_container.find('.form-column').append(` +
      + +
      + `); + + this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller')) + + this.$form_container.on('click', '.btn-register', (e) => { + const form_values = this.field_group.get_values(); + + let values_filled = true; + const mandatory_fields = ['company', 'company_email', 'company_description']; + mandatory_fields.forEach(field => { + const value = form_values[field]; + if (!value) { + this.field_group.set_df_property(field, 'reqd', 1); + values_filled = false; + } + }); + if (!values_filled) return; + + frappe.call({ + method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', + args: form_values, + btn: $(e.currentTarget) + }).then(() => { + frappe.set_route('marketplace', 'publish'); + + // custom jquery event + this.$wrapper.trigger('seller-registered'); + }); + }); + } +} + +erpnext.hub.Publish = class Publish extends SubPage { + make_wrapper() { + super.make_wrapper(); + const title_html = `${__('Select Products to Publish')}`; + const info = `

      ${__("Status decided by the 'Publish in Hub' field in Item.")}

      `; + const subtitle_html = ` +

      + ${__(`Only products with an image, description and category can be published. + Please update them if an item in your inventory does not appear.`)} +

      `; + const publish_button_html = ``; + + const select_all_button = ``; + const deselect_all_button = ``; + + const search_html = `
      + +
      `; + + const subpage_header = $(` +
      +
      + ${title_html} + ${subtitle_html} +
      + ${publish_button_html} +
      + + ${search_html} + + ${select_all_button} + ${deselect_all_button} + `); + + this.$wrapper.append(subpage_header); + + this.setup_events(); + } + + setup_events() { + this.$wrapper.find('.select-all').on('click', () => { + this.$wrapper.find('.hub-card').addClass('active'); + }); + + this.$wrapper.find('.deselect-all').on('click', () => { + this.$wrapper.find('.hub-card').removeClass('active'); + }); + + this.$wrapper.find('.publish-items').on('click', () => { + this.publish_selected_items() + .then(r => { + frappe.msgprint('check'); + }); + }); + + const $search_input = this.$wrapper.find('.hub-search-container input'); + this.search_value = ''; + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + this.search_value = $search_input.val(); + this.get_items_and_render(); + } + }, 300)); + } + + get_items_and_render() { + this.$wrapper.find('.hub-card-container').empty(); + this.get_valid_items() + .then(r => { + this.render(r.message); + }); + } + + refresh() { + this.get_items_and_render(); + } + + render(items) { + const items_container = $(get_item_card_container_html(items)); + items_container.addClass('static').on('click', '.hub-card', (e) => { + const $target = $(e.currentTarget); + $target.toggleClass('active'); + }); + + this.$wrapper.append(items_container); + } + + get_valid_items() { + return frappe.call( + 'erpnext.hub_node.get_valid_items', + { + search_value: this.search_value + } + ); + } + + publish_selected_items() { + const items_to_publish = []; + const items_to_unpublish = []; + this.$wrapper.find('.hub-card').map(function () { + const active = $(this).hasClass('active'); + + if(active) { + items_to_publish.push($(this).attr("data-id")); + } else { + items_to_unpublish.push($(this).attr("data-id")); + } + }); + + return frappe.call( + 'erpnext.hub_node.publish_selected_items', + { + items_to_publish: items_to_publish, + items_to_unpublish: items_to_unpublish + } + ); + } +} + +function get_item_card_container_html(items, title='') { + const items_html = (items || []).map(item => get_item_card_html(item)).join(''); + + const html = `
      +
      + ${title} +
      + ${items_html} +
      `; + + return html; +} + +function get_item_card_html(item) { + const item_name = item.item_name || item.name; + const title = strip_html(item_name); + const img_url = item.image; + + const company_name = item.company_name; + + const active = item.publish_in_hub; + + const id = item.hub_item_code || item.item_code; + + // Subtitle + let subtitle = [comment_when(item.creation)]; + const rating = item.average_rating; + if (rating > 0) { + subtitle.push(rating + ``) + } + subtitle.push(company_name); + + let dot_spacer = ''; + subtitle = subtitle.join(dot_spacer); + + // Decide item link + const is_local = item.source_type === "local"; + const route = !is_local + ? `marketplace/item/${item.hub_item_code}` + : `Form/Item/${item.item_name}`; + + const card_route = is_local ? '' : `data-route='${route}'`; + + const show_local_item_button = is_local + ? `
      + +
      ` + : ''; + + const item_html = ` +
      +
      +
      +
      +
      ${title}
      +
      ${subtitle}
      +
      + +
      +
      + +
      + ${show_local_item_button} +
      +
      +
      + `; + + return item_html; +} + +function get_rating_html(item) { + const rating = item.average_rating; + let rating_html = ``; + for (var i = 0; i < 5; i++) { + let star_class = 'fa-star'; + if (i >= rating) star_class = 'fa-star-o'; + rating_html += ``; + } + return rating_html; +} + +erpnext.hub.cache = {}; +hub.call = function call_hub_method(method, args={}) { + return new Promise((resolve, reject) => { + + // cache + const key = method + JSON.stringify(args); + if (erpnext.hub.cache[key]) { + resolve(erpnext.hub.cache[key]); + } + + // cache invalidation after 5 minutes + setTimeout(() => { + delete erpnext.hub.cache[key]; + }, 5 * 60 * 1000); + + frappe.call({ + method: 'erpnext.hub_node.call_hub_method', + args: { + method, + params: args + } + }) + .then(r => { + if (r.message) { + erpnext.hub.cache[key] = r.message; + resolve(r.message) + } + reject(r) + }) + .fail(reject) + }); +} From cae856cbda482c8e2a65d1ed64038e7f5ba543f8 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 26 Jul 2018 18:39:28 +0530 Subject: [PATCH 017/154] Add marketplace back to hub_listing --- erpnext/public/js/hub/hub_listing.js | 845 +++++++++++++++++++++++++++ 1 file changed, 845 insertions(+) diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 368c723e5b..7a82a9153c 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -1,3 +1,848 @@ +frappe.provide('hub'); +frappe.provide('erpnext.hub'); + +erpnext.hub.Marketplace = class Marketplace { + constructor({ parent }) { + this.$parent = $(parent); + this.page = parent.page; + + frappe.db.get_doc('Hub Settings') + .then(doc => { + this.hub_settings = doc; + this.registered = doc.registered; + + this.setup_header(); + this.make_sidebar(); + this.make_body(); + this.setup_events(); + this.refresh(); + }); + } + + setup_header() { + this.page.set_title(__('Marketplace')); + } + + setup_events() { + this.$parent.on('click', '[data-route]', (e) => { + const $target = $(e.currentTarget); + const route = $target.data().route; + frappe.set_route(route); + + e.stopPropagation(); + }); + } + + make_sidebar() { + this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); + + this.make_sidebar_nav_buttons(); + this.make_sidebar_categories(); + } + + make_sidebar_nav_buttons() { + let $nav_group = this.$sidebar.find('[data-nav-buttons]'); + if (!$nav_group.length) { + $nav_group = $('
        ').appendTo(this.$sidebar); + } + $nav_group.empty(); + + const user_specific_items_html = this.registered + ? `
      • + ${__('Your Profile')} +
      • +
      • + ${__('Publish Products')} +
      • ` + + : `
      • + ${__('Become a seller')} +
      • `; + + $nav_group.append(` +
      • + ${__('Browse')} +
      • +
      • + ${__('Favorites')} +
      • + ${user_specific_items_html} + `); + } + + make_sidebar_categories() { + frappe.call('erpnext.hub_node.get_categories') + .then(r => { + const categories = r.message.map(d => d.value).sort(); + const sidebar_items = [ + `
      • + ${__('Category')} +
      • `, + `
      • + ${__('All')} +
      • `, + ...(this.registered + ? [`
      • + ${__('Your Products')} +
      • `] + : []), + ...categories.map(category => ` +
      • + ${__(category)} +
      • + `) + ]; + + this.$sidebar.append(` +
          + ${sidebar_items.join('')} +
        + `); + + this.update_sidebar(); + }); + } + + make_body() { + this.$body = this.$parent.find('.layout-main-section'); + + this.$body.on('seller-registered', () => { + this.registered = 1; + this.make_sidebar_nav_buttons(); + }); + } + + update_sidebar() { + const route = frappe.get_route_str(); + const $sidebar_item = this.$sidebar.find(`[data-route="${route}"]`); + + const $siblings = this.$sidebar.find('[data-route]'); + $siblings.removeClass('active').addClass('text-muted'); + + $sidebar_item.addClass('active').removeClass('text-muted'); + } + + refresh() { + const route = frappe.get_route(); + this.subpages = this.subpages || {}; + + for (let page in this.subpages) { + this.subpages[page].hide(); + } + + if (route[1] === 'home' && !this.subpages.home) { + this.subpages.home = new erpnext.hub.Home(this.$body); + } + + if (route[1] === 'favourites' && !this.subpages.favourites) { + this.subpages.favourites = new erpnext.hub.Favourites(this.$body); + } + + if (route[1] === 'category' && route[2] && !this.subpages.category) { + this.subpages.category = new erpnext.hub.Category(this.$body); + } + + if (route[1] === 'item' && route[2] && !this.subpages.item) { + this.subpages.item = new erpnext.hub.Item(this.$body); + } + + if (route[1] === 'register' && !this.subpages.register) { + // if (this.registered) { + // frappe.set_route('marketplace', 'home'); + // return; + // } + this.subpages.register = new erpnext.hub.Register(this.$body); + } + + if (route[1] === 'publish' && !this.subpages.publish) { + this.subpages.publish = new erpnext.hub.Publish(this.$body); + } + + + if (!Object.keys(this.subpages).includes(route[1])) { + frappe.show_not_found(); + return; + } + + this.update_sidebar(); + frappe.utils.scroll_to(0); + this.subpages[route[1]].show(); + } +} + +class SubPage { + constructor(parent) { + this.$parent = $(parent); + this.make_wrapper(); + } + + make_wrapper() { + const page_name = frappe.get_route()[1]; + this.$wrapper = $(`
        `).appendTo(this.$parent); + this.hide(); + } + + show() { + this.refresh(); + this.$wrapper.show(); + } + + hide() { + this.$wrapper.hide(); + } +} + +erpnext.hub.Home = class Home extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.make_search_bar(); + } + + refresh() { + this.get_items_and_render(); + } + + get_items_and_render() { + this.$wrapper.find('.hub-card-container').empty(); + this.get_items() + .then(items => { + this.render(items); + }); + } + + get_items() { + return hub.call('get_data_for_homepage'); + } + + make_search_bar() { + const $search = $(` +
        + +
        ` + ); + this.$wrapper.append($search); + const $search_input = $search.find('input'); + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + this.search_value = $search_input.val(); + this.get_items_and_render(); + } + }, 300)); + } + + render(items) { + const html = get_item_card_container_html(items, __('Recently Published')); + this.$wrapper.append(html) + } +} + +erpnext.hub.Favourites = class Favourites extends SubPage { + refresh() { + this.get_favourites() + .then(r => { + this.render(r.message); + }); + } + + get_favourites() { + return frappe.call('erpnext.hub_node.get_item_favourites'); + } + + render(items) { + this.$wrapper.find('.hub-card-container').empty(); + const html = get_item_card_container_html(items, __('Favourites')); + this.$wrapper.append(html) + } +} + +erpnext.hub.Category = class Category extends SubPage { + refresh() { + this.category = frappe.get_route()[2]; + this.get_items_for_category(this.category) + .then(r => { + this.render(r.message); + }); + } + + get_items_for_category(category) { + this.$wrapper.find('.hub-card-container').empty(); + return frappe.call('erpnext.hub_node.get_list', { + doctype: 'Hub Item', + filters: { + hub_category: category + } + }); + } + + render(items) { + const html = get_item_card_container_html(items, __(this.category)); + this.$wrapper.append(html) + } +} + +erpnext.hub.Item = class Item extends SubPage { + refresh() { + this.hub_item_code = frappe.get_route()[2]; + + this.get_item(this.hub_item_code) + .then(item => { + this.render(item); + }); + } + + get_item(hub_item_code) { + return new Promise(resolve => { + const item = (erpnext.hub.hub_item_cache || []).find(item => item.name === hub_item_code) + + if (item) { + resolve(item); + } else { + frappe.call('erpnext.hub_node.get_list', { + doctype: 'Hub Item', + filters: { + name: hub_item_code + } + }) + .then(r => { + resolve(r.message[0]); + }); + } + }); + } + + render(item) { + const title = item.item_name || item.name; + const company = item.company_name; + + const who = __('Posted By {0}', [company]); + const when = comment_when(item.creation); + + const city = item.seller_city ? item.seller_city + ', ' : ''; + const country = item.country ? item.country : ''; + const where = `${city}${country}`; + + const dot_spacer = ''; + + const description = item.description || ''; + + const rating_html = get_rating_html(item); + const rating_count = item.reviews.length > 0 ? `(${item.reviews.length} reviews)` : ''; + + const html = ` +
        +
        +
        + +
        +
        +
        +
        +
        + +
        +
        +
        +

        ${title}

        +
        +

        ${where}${dot_spacer}${when}

        +

        ${rating_html}${rating_count}

        +
        +
        +
        + ${description ? + `${__('Description')} +

        ${description}

        + ` : __('No description') + } +
        +
        +
        +
        +
        + Seller Information +
        +
        + +
        +
        + ${company} +

        + Contact Seller +

        +
        +
        + +
        + +
        +
        + `; + + this.$wrapper.html(html); + + this.make_review_area(); + this.render_reviews(item); + } + + make_review_area() { + this.comment_area = new frappe.ui.ReviewArea({ + parent: this.$wrapper.find('.timeline-head').empty(), + mentions: [], + on_submit: (val) => { + val.user = frappe.session.user; + val.username = frappe.session.user_fullname; + + frappe.call({ + method: 'erpnext.hub_node.send_review', + args: { + hub_item_code: this.hub_item_code, + review: val + }, + callback: (r) => { + console.log(r); + this.render_reviews(r.message); + this.comment_area.reset(); + }, + freeze: true + }); + } + }); + } + + render_reviews(item) { + this.$wrapper.find('.timeline-items').empty(); + item.reviews.forEach(review => this.render_review(review, item)); + } + + render_review(review, item) { + let username = review.username || review.user || __("Anonymous"); + + let image_html = review.user_image + ? `
        ` + : `
        ${frappe.get_abbr(username)}
        ` + + let edit_html = review.own + ? ` +
        + + ${'data.edit'} + +
        ` + : ''; + + let rating_html = get_rating_html(item); + + const $timeline_items = this.$wrapper.find('.timeline-items'); + + $(this.get_timeline_item(review, image_html, edit_html, rating_html)) + .appendTo($timeline_items); + } + + get_timeline_item(data, image_html, edit_html, rating_html) { + return `
        + +
        +
        +
        ${edit_html}
        + +
        + + ${image_html} + + +
        + + + ${data.username} + + + + + +
        +
        +
        +
        +

        + ${rating_html} +

        +
        ${data.subject}
        +

        + ${data.content} +

        +
        +
        +
        +
        +
        `; + } +} +erpnext.hub.Register = class Register extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.$register_container = $(`
        `) + .appendTo(this.$wrapper); + this.$form_container = $('
        ') + .appendTo(this.$wrapper); + } + + refresh() { + this.$register_container.empty(); + this.$form_container.empty(); + this.render(); + } + + render() { + this.make_field_group(); + } + + make_field_group() { + const fields = [ + { + fieldtype: 'Link', + fieldname: 'company', + label: __('Company'), + options: 'Company', + onchange: () => { + const value = this.field_group.get_value('company'); + + if (value) { + frappe.db.get_doc('Company', value) + .then(company => { + this.field_group.set_values({ + country: company.country, + company_email: company.email, + currency: company.default_currency + }); + }); + } + } + }, + { + fieldname: 'company_email', + label: __('Email'), + fieldtype: 'Data' + }, + { + fieldname: 'country', + label: __('Country'), + fieldtype: 'Read Only' + }, + { + fieldname: 'currency', + label: __('Currency'), + fieldtype: 'Read Only' + }, + { + fieldtype: 'Text', + label: __('About your Company'), + fieldname: 'company_description' + } + ]; + + this.field_group = new frappe.ui.FieldGroup({ + parent: this.$form_container, + fields + }); + + this.field_group.make(); + + const default_company = frappe.defaults.get_default('company'); + this.field_group.set_value('company', default_company); + + this.$form_container.find('.form-column').append(` +
        + +
        + `); + + this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller')) + + this.$form_container.on('click', '.btn-register', (e) => { + const form_values = this.field_group.get_values(); + + let values_filled = true; + const mandatory_fields = ['company', 'company_email', 'company_description']; + mandatory_fields.forEach(field => { + const value = form_values[field]; + if (!value) { + this.field_group.set_df_property(field, 'reqd', 1); + values_filled = false; + } + }); + if (!values_filled) return; + + frappe.call({ + method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', + args: form_values, + btn: $(e.currentTarget) + }).then(() => { + frappe.set_route('marketplace', 'publish'); + + // custom jquery event + this.$wrapper.trigger('seller-registered'); + }); + }); + } +} + +erpnext.hub.Publish = class Publish extends SubPage { + make_wrapper() { + super.make_wrapper(); + const title_html = `${__('Select Products to Publish')}`; + const info = `

        ${__("Status decided by the 'Publish in Hub' field in Item.")}

        `; + const subtitle_html = ` +

        + ${__(`Only products with an image, description and category can be published. + Please update them if an item in your inventory does not appear.`)} +

        `; + const publish_button_html = ``; + + const select_all_button = ``; + const deselect_all_button = ``; + + const search_html = `
        + +
        `; + + const subpage_header = $(` +
        +
        + ${title_html} + ${subtitle_html} +
        + ${publish_button_html} +
        + + ${search_html} + + ${select_all_button} + ${deselect_all_button} + `); + + this.$wrapper.append(subpage_header); + + this.setup_events(); + } + + setup_events() { + this.$wrapper.find('.select-all').on('click', () => { + this.$wrapper.find('.hub-card').addClass('active'); + }); + + this.$wrapper.find('.deselect-all').on('click', () => { + this.$wrapper.find('.hub-card').removeClass('active'); + }); + + this.$wrapper.find('.publish-items').on('click', () => { + this.publish_selected_items() + .then(r => { + frappe.msgprint('check'); + }); + }); + + const $search_input = this.$wrapper.find('.hub-search-container input'); + this.search_value = ''; + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + this.search_value = $search_input.val(); + this.get_items_and_render(); + } + }, 300)); + } + + get_items_and_render() { + this.$wrapper.find('.hub-card-container').empty(); + this.get_valid_items() + .then(r => { + this.render(r.message); + }); + } + + refresh() { + this.get_items_and_render(); + } + + render(items) { + const items_container = $(get_item_card_container_html(items)); + items_container.addClass('static').on('click', '.hub-card', (e) => { + const $target = $(e.currentTarget); + $target.toggleClass('active'); + }); + + this.$wrapper.append(items_container); + } + + get_valid_items() { + return frappe.call( + 'erpnext.hub_node.get_valid_items', + { + search_value: this.search_value + } + ); + } + + publish_selected_items() { + const items_to_publish = []; + const items_to_unpublish = []; + this.$wrapper.find('.hub-card').map(function () { + const active = $(this).hasClass('active'); + + if(active) { + items_to_publish.push($(this).attr("data-id")); + } else { + items_to_unpublish.push($(this).attr("data-id")); + } + }); + + return frappe.call( + 'erpnext.hub_node.publish_selected_items', + { + items_to_publish: items_to_publish, + items_to_unpublish: items_to_unpublish + } + ); + } +} + +function get_item_card_container_html(items, title='') { + const items_html = (items || []).map(item => get_item_card_html(item)).join(''); + + const html = `
        +
        + ${title} +
        + ${items_html} +
        `; + + return html; +} + +function get_item_card_html(item) { + const item_name = item.item_name || item.name; + const title = strip_html(item_name); + const img_url = item.image; + + const company_name = item.company_name; + + const active = item.publish_in_hub; + + const id = item.hub_item_code || item.item_code; + + // Subtitle + let subtitle = [comment_when(item.creation)]; + const rating = item.average_rating; + if (rating > 0) { + subtitle.push(rating + ``) + } + subtitle.push(company_name); + + let dot_spacer = ''; + subtitle = subtitle.join(dot_spacer); + + // Decide item link + const is_local = item.source_type === "local"; + const route = !is_local + ? `marketplace/item/${item.hub_item_code}` + : `Form/Item/${item.item_name}`; + + const card_route = is_local ? '' : `data-route='${route}'`; + + const show_local_item_button = is_local + ? `
        + +
        ` + : ''; + + const item_html = ` +
        +
        +
        +
        +
        ${title}
        +
        ${subtitle}
        +
        + +
        +
        + +
        + ${show_local_item_button} +
        +
        +
        + `; + + return item_html; +} + +function get_rating_html(item) { + const rating = item.average_rating; + let rating_html = ``; + for (var i = 0; i < 5; i++) { + let star_class = 'fa-star'; + if (i >= rating) star_class = 'fa-star-o'; + rating_html += ``; + } + return rating_html; +} + +erpnext.hub.cache = {}; +hub.call = function call_hub_method(method, args={}) { + return new Promise((resolve, reject) => { + + // cache + const key = method + JSON.stringify(args); + if (erpnext.hub.cache[key]) { + resolve(erpnext.hub.cache[key]); + } + + // cache invalidation after 5 minutes + setTimeout(() => { + delete erpnext.hub.cache[key]; + }, 5 * 60 * 1000); + + frappe.call({ + method: 'erpnext.hub_node.call_hub_method', + args: { + method, + params: args + } + }) + .then(r => { + if (r.message) { + erpnext.hub.cache[key] = r.message; + resolve(r.message) + } + reject(r) + }) + .fail(reject) + }); +} + erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { setup_defaults() { From b5ce1976b8813b608a706bd70c33983785f5a2b8 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 26 Jul 2018 18:46:38 +0530 Subject: [PATCH 018/154] Move marketplace code back to marketplace.js --- erpnext/public/js/hub/hub_listing.js | 907 --------------------------- erpnext/public/js/hub/marketplace.js | 125 +++- 2 files changed, 94 insertions(+), 938 deletions(-) diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js index 07064f4435..368c723e5b 100644 --- a/erpnext/public/js/hub/hub_listing.js +++ b/erpnext/public/js/hub/hub_listing.js @@ -1,910 +1,3 @@ -frappe.provide('hub'); -frappe.provide('erpnext.hub'); - -erpnext.hub.Marketplace = class Marketplace { - constructor({ parent }) { - this.$parent = $(parent); - this.page = parent.page; - - frappe.db.get_doc('Hub Settings') - .then(doc => { - this.hub_settings = doc; - this.registered = doc.registered; - - this.setup_header(); - this.make_sidebar(); - this.make_body(); - this.setup_events(); - this.refresh(); - }); - } - - setup_header() { - this.page.set_title(__('Marketplace')); - } - - setup_events() { - this.$parent.on('click', '[data-route]', (e) => { - const $target = $(e.currentTarget); - const route = $target.data().route; - frappe.set_route(route); - - e.stopPropagation(); - }); - } - - make_sidebar() { - this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); - - this.make_sidebar_nav_buttons(); - this.make_sidebar_categories(); - } - - make_sidebar_nav_buttons() { - let $nav_group = this.$sidebar.find('[data-nav-buttons]'); - if (!$nav_group.length) { - $nav_group = $('
          ').appendTo(this.$sidebar); - } - $nav_group.empty(); - - const user_specific_items_html = this.registered - ? `
        • - ${__('Your Profile')} -
        • -
        • - ${__('Publish Products')} -
        • ` - - : `
        • - ${__('Become a seller')} -
        • `; - - $nav_group.append(` -
        • - ${__('Browse')} -
        • -
        • - ${__('Favorites')} -
        • - ${user_specific_items_html} - `); - } - - make_sidebar_categories() { - frappe.call('erpnext.hub_node.get_categories') - .then(r => { - const categories = r.message.map(d => d.value).sort(); - const sidebar_items = [ - `
        • - ${__('Category')} -
        • `, - `
        • - ${__('All')} -
        • `, - ...(this.registered - ? [`
        • - ${__('Your Products')} -
        • `] - : []), - ...categories.map(category => ` -
        • - ${__(category)} -
        • - `) - ]; - - this.$sidebar.append(` -
            - ${sidebar_items.join('')} -
          - `); - - this.update_sidebar(); - }); - } - - make_body() { - this.$body = this.$parent.find('.layout-main-section'); - - this.$body.on('seller-registered', () => { - this.registered = 1; - this.make_sidebar_nav_buttons(); - }); - } - - update_sidebar() { - const route = frappe.get_route_str(); - const $sidebar_item = this.$sidebar.find(`[data-route="${route}"]`); - - const $siblings = this.$sidebar.find('[data-route]'); - $siblings.removeClass('active').addClass('text-muted'); - - $sidebar_item.addClass('active').removeClass('text-muted'); - } - - refresh() { - const route = frappe.get_route(); - this.subpages = this.subpages || {}; - - for (let page in this.subpages) { - this.subpages[page].hide(); - } - - if (route[1] === 'home' && !this.subpages.home) { - this.subpages.home = new erpnext.hub.Home(this.$body); - } - - if (route[1] === 'favourites' && !this.subpages.favourites) { - this.subpages.favourites = new erpnext.hub.Favourites(this.$body); - } - - if (route[1] === 'category' && route[2] && !this.subpages.category) { - this.subpages.category = new erpnext.hub.Category(this.$body); - } - - if (route[1] === 'item' && route[2] && !this.subpages.item) { - this.subpages.item = new erpnext.hub.Item(this.$body); - } - - if (route[1] === 'register' && !this.subpages.register) { - // if (this.registered) { - // frappe.set_route('marketplace', 'home'); - // return; - // } - this.subpages.register = new erpnext.hub.Register(this.$body); - } - - if (route[1] === 'profile' && !this.subpages.profile) { - this.subpages.profile = new erpnext.hub.Profile(this.$body, {data: this.hub_settings}); - } - - if (route[1] === 'publish' && !this.subpages.publish) { - this.subpages.publish = new erpnext.hub.Publish( - this.$body, - {sync_in_progress: this.hub_settings.sync_in_progress} - ); - } - - if (!Object.keys(this.subpages).includes(route[1])) { - frappe.show_not_found(); - return; - } - - this.update_sidebar(); - frappe.utils.scroll_to(0); - this.subpages[route[1]].show(); - } -} - -class SubPage { - constructor(parent, options) { - this.$parent = $(parent); - this.make_wrapper(options); - } - - make_wrapper() { - const page_name = frappe.get_route()[1]; - this.$wrapper = $(`
          `).appendTo(this.$parent); - this.hide(); - } - - show() { - this.refresh(); - this.$wrapper.show(); - } - - hide() { - this.$wrapper.hide(); - } -} - -erpnext.hub.Home = class Home extends SubPage { - make_wrapper() { - super.make_wrapper(); - this.make_search_bar(); - } - - refresh() { - this.get_items_and_render(); - } - - get_items_and_render() { - this.$wrapper.find('.hub-card-container').empty(); - this.get_items() - .then(items => { - this.render(items); - }); - } - - get_items() { - return hub.call('get_data_for_homepage'); - } - - make_search_bar() { - const $search = $(` -
          - -
          ` - ); - this.$wrapper.append($search); - const $search_input = $search.find('input'); - - $search_input.on('keydown', frappe.utils.debounce((e) => { - if (e.which === frappe.ui.keyCode.ENTER) { - this.search_value = $search_input.val(); - this.get_items_and_render(); - } - }, 300)); - } - - render(items) { - const html = get_item_card_container_html(items, __('Recently Published')); - this.$wrapper.append(html) - } -} - -erpnext.hub.Favourites = class Favourites extends SubPage { - refresh() { - this.get_favourites() - .then(r => { - this.render(r.message); - }); - } - - get_favourites() { - return frappe.call('erpnext.hub_node.get_item_favourites'); - } - - render(items) { - this.$wrapper.find('.hub-card-container').empty(); - const html = get_item_card_container_html(items, __('Favourites')); - this.$wrapper.append(html) - } -} - -erpnext.hub.Category = class Category extends SubPage { - refresh() { - this.category = frappe.get_route()[2]; - this.get_items_for_category(this.category) - .then(r => { - this.render(r.message); - }); - } - - get_items_for_category(category) { - this.$wrapper.find('.hub-card-container').empty(); - return frappe.call('erpnext.hub_node.get_list', { - doctype: 'Hub Item', - filters: { - hub_category: category - } - }); - } - - render(items) { - const html = get_item_card_container_html(items, __(this.category)); - this.$wrapper.append(html) - } -} - -erpnext.hub.Item = class Item extends SubPage { - refresh() { - this.hub_item_code = frappe.get_route()[2]; - - this.get_item(this.hub_item_code) - .then(item => { - this.render(item); - }); - } - - get_item(hub_item_code) { - return new Promise(resolve => { - const item = (erpnext.hub.hub_item_cache || []).find(item => item.name === hub_item_code) - - if (item) { - resolve(item); - } else { - frappe.call('erpnext.hub_node.get_list', { - doctype: 'Hub Item', - filters: { - name: hub_item_code - } - }) - .then(r => { - resolve(r.message[0]); - }); - } - }); - } - - render(item) { - const title = item.item_name || item.name; - const company = item.company_name; - - const who = __('Posted By {0}', [company]); - const when = comment_when(item.creation); - - const city = item.seller_city ? item.seller_city + ', ' : ''; - const country = item.country ? item.country : ''; - const where = `${city}${country}`; - - const dot_spacer = ''; - - const description = item.description || ''; - - const rating_html = get_rating_html(item); - const rating_count = item.reviews.length > 0 ? `(${item.reviews.length} reviews)` : ''; - - const html = ` -
          -
          -
          - -
          -
          -
          -
          -
          - -
          -
          -
          -

          ${title}

          -
          -

          ${where}${dot_spacer}${when}

          -

          ${rating_html}${rating_count}

          -
          -
          -
          - ${description ? - `${__('Description')} -

          ${description}

          - ` : __('No description') - } -
          -
          -
          -
          -
          - Seller Information -
          -
          - -
          -
          - ${company} -

          - Contact Seller -

          -
          -
          - -
          - -
          -
          - `; - - this.$wrapper.html(html); - - this.make_review_area(); - this.render_reviews(item); - } - - make_review_area() { - this.comment_area = new frappe.ui.ReviewArea({ - parent: this.$wrapper.find('.timeline-head').empty(), - mentions: [], - on_submit: (val) => { - val.user = frappe.session.user; - val.username = frappe.session.user_fullname; - - frappe.call({ - method: 'erpnext.hub_node.send_review', - args: { - hub_item_code: this.hub_item_code, - review: val - }, - callback: (r) => { - console.log(r); - this.render_reviews(r.message); - this.comment_area.reset(); - }, - freeze: true - }); - } - }); - } - - render_reviews(item) { - this.$wrapper.find('.timeline-items').empty(); - item.reviews.forEach(review => this.render_review(review, item)); - } - - render_review(review, item) { - let username = review.username || review.user || __("Anonymous"); - - let image_html = review.user_image - ? `
          ` - : `
          ${frappe.get_abbr(username)}
          ` - - let edit_html = review.own - ? ` -
          - - ${'data.edit'} - -
          ` - : ''; - - let rating_html = get_rating_html(item); - - const $timeline_items = this.$wrapper.find('.timeline-items'); - - $(this.get_timeline_item(review, image_html, edit_html, rating_html)) - .appendTo($timeline_items); - } - - get_timeline_item(data, image_html, edit_html, rating_html) { - return `
          - -
          -
          -
          ${edit_html}
          - -
          - - ${image_html} - - -
          - - - ${data.username} - - - - - -
          -
          -
          -
          -

          - ${rating_html} -

          -
          ${data.subject}
          -

          - ${data.content} -

          -
          -
          -
          -
          -
          `; - } -} -erpnext.hub.Register = class Register extends SubPage { - make_wrapper() { - super.make_wrapper(); - this.$register_container = $(`
          `) - .appendTo(this.$wrapper); - this.$form_container = $('
          ') - .appendTo(this.$wrapper); - } - - refresh() { - this.$register_container.empty(); - this.$form_container.empty(); - this.render(); - } - - render() { - this.make_field_group(); - } - - make_field_group() { - const fields = [ - { - fieldtype: 'Link', - fieldname: 'company', - label: __('Company'), - options: 'Company', - onchange: () => { - const value = this.field_group.get_value('company'); - - if (value) { - frappe.db.get_doc('Company', value) - .then(company => { - this.field_group.set_values({ - country: company.country, - company_email: company.email, - currency: company.default_currency - }); - }); - } - } - }, - { - fieldname: 'company_email', - label: __('Email'), - fieldtype: 'Data' - }, - { - fieldname: 'country', - label: __('Country'), - fieldtype: 'Read Only' - }, - { - fieldname: 'currency', - label: __('Currency'), - fieldtype: 'Read Only' - }, - { - fieldtype: 'Text', - label: __('About your Company'), - fieldname: 'company_description' - } - ]; - - this.field_group = new frappe.ui.FieldGroup({ - parent: this.$form_container, - fields - }); - - this.field_group.make(); - - const default_company = frappe.defaults.get_default('company'); - this.field_group.set_value('company', default_company); - - this.$form_container.find('.form-column').append(` -
          - -
          - `); - - this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller')) - - this.$form_container.on('click', '.btn-register', (e) => { - const form_values = this.field_group.get_values(); - - let values_filled = true; - const mandatory_fields = ['company', 'company_email', 'company_description']; - mandatory_fields.forEach(field => { - const value = form_values[field]; - if (!value) { - this.field_group.set_df_property(field, 'reqd', 1); - values_filled = false; - } - }); - if (!values_filled) return; - - frappe.call({ - method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', - args: form_values, - btn: $(e.currentTarget) - }).then(() => { - frappe.set_route('marketplace', 'publish'); - - // custom jquery event - this.$wrapper.trigger('seller-registered'); - }); - }); - } -} - -erpnext.hub.Profile = class Profile extends SubPage { - constructor(parent, profile_data) { - super(parent); - this.profile_data = profile_data; - } - - make_wrapper() { - super.make_wrapper(); - const profile_html = `
          -
          -
          - -
          -
          -
          -
          -
          - -
          -
          -
          -

          ${'title'}

          -
          -

          ${'where'}${'dot_spacer'}${'when'}

          -

          ${'rating_html'}${'rating_count'}

          -
          -
          -
          - ${'description' ? - `${__('Description')} -

          ${'description'}

          - ` : __('No description') - } -
          -
          -
          -
          `; - - this.$wrapper.html(profile_html); - } - - refresh() {} - - render() {} -} -erpnext.hub.Publish = class Publish extends SubPage { - make_wrapper(options) { - super.make_wrapper(); - this.sync_in_progress = options.sync_in_progress; - - this.load_publish_page(); - } - - load_publish_page() { - const title_html = `${__('Select Products to Publish')}`; - const info = `

          ${__("Status decided by the 'Publish in Hub' field in Item.")}

          `; - const subtitle_html = ` -

          - ${__(`Only products with an image, description and category can be published. - Please update them if an item in your inventory does not appear.`)} -

          `; - const publish_button_html = ``; - - const select_all_button = ``; - const deselect_all_button = ``; - - const search_html = `
          - -
          `; - - const subpage_header = $(` -
          -
          - ${title_html} - ${subtitle_html} -
          - ${publish_button_html} -
          - - ${search_html} - `); - - this.$wrapper.append(subpage_header); - - this.setup_events(); - } - - setup_events() { - this.$wrapper.find('.publish-items').on('click', () => { - this.load_publishing_state(); - this.publish_selected_items() - .then(r => { - frappe.msgprint('check'); - }); - }); - - const $search_input = this.$wrapper.find('.hub-search-container input'); - this.search_value = ''; - - $search_input.on('keydown', frappe.utils.debounce((e) => { - if (e.which === frappe.ui.keyCode.ENTER) { - this.search_value = $search_input.val(); - this.get_items_and_render(); - } - }, 300)); - } - - get_items_and_render() { - if(this.sync_in_progress) { - this.load_publishing_state(); - return; - } - - this.$wrapper.find('.hub-card-container').empty(); - this.get_valid_items() - .then(r => { - this.render(r.message); - }); - } - - refresh() { - this.get_items_and_render(); - } - - render(items) { - const items_container = $(get_item_card_container_html(items)); - items_container.addClass('static').on('click', '.hub-card', (e) => { - const $target = $(e.currentTarget); - $target.toggleClass('active'); - - // Get total items - const total_items = this.$wrapper.find('.hub-card.active').length; - const more_than_one = total_items > 1; - this.$wrapper.find('.publish-items') - .html(__('Publish ' + total_items + ' item' + (more_than_one ? 's' : ''))); - }); - - this.$wrapper.append(items_container); - } - - get_valid_items() { - return frappe.call( - 'erpnext.hub_node.get_valid_items', - { - search_value: this.search_value - } - ); - } - - load_publishing_state() { - this.$wrapper.html(get_empty_state( - 'Publishing items ... You will be notified once published.' - )); - } - - publish_selected_items() { - const items_to_publish = []; - this.$wrapper.find('.hub-card.active').map(function () { - items_to_publish.push($(this).attr("data-id")); - }); - - return frappe.call( - 'erpnext.hub_node.publish_selected_items', - { - items_to_publish: items_to_publish - } - ); - } -} - -function get_empty_state(message) { - return `
          -

          ${message}

          -
          ` -} - -function get_item_card_container_html(items, title='') { - const items_html = (items || []).map(item => get_item_card_html(item)).join(''); - - const html = `
          -
          - ${title} -
          - ${items_html} -
          `; - - return html; -} - -function get_item_card_html(item) { - const item_name = item.item_name || item.name; - const title = strip_html(item_name); - const img_url = item.image; - - const company_name = item.company_name; - - const active = item.publish_in_hub; - - const id = item.hub_item_code || item.item_code; - - // Subtitle - let subtitle = [comment_when(item.creation)]; - const rating = item.average_rating; - if (rating > 0) { - subtitle.push(rating + ``) - } - subtitle.push(company_name); - - let dot_spacer = ''; - subtitle = subtitle.join(dot_spacer); - - // Decide item link - const is_local = item.source_type === "local"; - const route = !is_local - ? `marketplace/item/${item.hub_item_code}` - : `Form/Item/${item.item_name}`; - - const card_route = is_local ? '' : `data-route='${route}'`; - - const show_local_item_button = is_local - ? `
          - -
          ` - : ''; - - const item_html = ` -
          -
          -
          -
          -
          ${title}
          -
          ${subtitle}
          -
          - -
          -
          - -
          - ${show_local_item_button} -
          -
          -
          - `; - - return item_html; -} - -function get_rating_html(item) { - const rating = item.average_rating; - let rating_html = ``; - for (var i = 0; i < 5; i++) { - let star_class = 'fa-star'; - if (i >= rating) star_class = 'fa-star-o'; - rating_html += ``; - } - return rating_html; -} - -erpnext.hub.cache = {}; -hub.call = function call_hub_method(method, args={}) { - return new Promise((resolve, reject) => { - - // cache - const key = method + JSON.stringify(args); - if (erpnext.hub.cache[key]) { - resolve(erpnext.hub.cache[key]); - } - - // cache invalidation after 5 minutes - setTimeout(() => { - delete erpnext.hub.cache[key]; - }, 5 * 60 * 1000); - - frappe.call({ - method: 'erpnext.hub_node.call_hub_method', - args: { - method, - params: args - } - }) - .then(r => { - if (r.message) { - erpnext.hub.cache[key] = r.message; - resolve(r.message) - } - reject(r) - }) - .fail(reject) - }); -} - erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { setup_defaults() { diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 2526b95eaf..07e9858bfe 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -147,17 +147,23 @@ erpnext.hub.Marketplace = class Marketplace { } if (route[1] === 'register' && !this.subpages.register) { - // if (this.registered) { - // frappe.set_route('marketplace', 'home'); - // return; - // } + if (this.registered) { + frappe.set_route('marketplace', 'home'); + return; + } this.subpages.register = new erpnext.hub.Register(this.$body); } - if (route[1] === 'publish' && !this.subpages.publish) { - this.subpages.publish = new erpnext.hub.Publish(this.$body); + if (route[1] === 'profile' && !this.subpages.profile) { + this.subpages.profile = new erpnext.hub.Profile(this.$body, {data: this.hub_settings}); } + if (route[1] === 'publish' && !this.subpages.publish) { + this.subpages.publish = new erpnext.hub.Publish( + this.$body, + {sync_in_progress: this.hub_settings.sync_in_progress} + ); + } if (!Object.keys(this.subpages).includes(route[1])) { frappe.show_not_found(); @@ -171,9 +177,9 @@ erpnext.hub.Marketplace = class Marketplace { } class SubPage { - constructor(parent) { + constructor(parent, options) { this.$parent = $(parent); - this.make_wrapper(); + this.make_wrapper(options); } make_wrapper() { @@ -603,9 +609,61 @@ erpnext.hub.Register = class Register extends SubPage { } } -erpnext.hub.Publish = class Publish extends SubPage { +erpnext.hub.Profile = class Profile extends SubPage { + constructor(parent, profile_data) { + super(parent); + this.profile_data = profile_data; + } + make_wrapper() { super.make_wrapper(); + const profile_html = `
          +
          +
          + +
          +
          +
          +
          +
          + +
          +
          +
          +

          ${'title'}

          +
          +

          ${'where'}${'dot_spacer'}${'when'}

          +

          ${'rating_html'}${'rating_count'}

          +
          +
          +
          + ${'description' ? + `${__('Description')} +

          ${'description'}

          + ` : __('No description') + } +
          +
          +
          +
          `; + + this.$wrapper.html(profile_html); + } + + refresh() {} + + render() {} +} + +erpnext.hub.Publish = class Publish extends SubPage { + make_wrapper(options) { + super.make_wrapper(); + this.sync_in_progress = options.sync_in_progress; + + this.load_publish_page(); + } + + load_publish_page() { const title_html = `${__('Select Products to Publish')}`; const info = `

          ${__("Status decided by the 'Publish in Hub' field in Item.")}

          `; const subtitle_html = ` @@ -635,9 +693,6 @@ erpnext.hub.Publish = class Publish extends SubPage {
          ${search_html} - - ${select_all_button} - ${deselect_all_button} `); this.$wrapper.append(subpage_header); @@ -646,15 +701,8 @@ erpnext.hub.Publish = class Publish extends SubPage { } setup_events() { - this.$wrapper.find('.select-all').on('click', () => { - this.$wrapper.find('.hub-card').addClass('active'); - }); - - this.$wrapper.find('.deselect-all').on('click', () => { - this.$wrapper.find('.hub-card').removeClass('active'); - }); - this.$wrapper.find('.publish-items').on('click', () => { + this.load_publishing_state(); this.publish_selected_items() .then(r => { frappe.msgprint('check'); @@ -673,6 +721,11 @@ erpnext.hub.Publish = class Publish extends SubPage { } get_items_and_render() { + if(this.sync_in_progress) { + this.load_publishing_state(); + return; + } + this.$wrapper.find('.hub-card-container').empty(); this.get_valid_items() .then(r => { @@ -689,6 +742,12 @@ erpnext.hub.Publish = class Publish extends SubPage { items_container.addClass('static').on('click', '.hub-card', (e) => { const $target = $(e.currentTarget); $target.toggleClass('active'); + + // Get total items + const total_items = this.$wrapper.find('.hub-card.active').length; + const more_than_one = total_items > 1; + this.$wrapper.find('.publish-items') + .html(__('Publish ' + total_items + ' item' + (more_than_one ? 's' : ''))); }); this.$wrapper.append(items_container); @@ -703,29 +762,33 @@ erpnext.hub.Publish = class Publish extends SubPage { ); } + load_publishing_state() { + this.$wrapper.html(get_empty_state( + 'Publishing items ... You will be notified once published.' + )); + } + publish_selected_items() { const items_to_publish = []; - const items_to_unpublish = []; - this.$wrapper.find('.hub-card').map(function () { - const active = $(this).hasClass('active'); - - if(active) { - items_to_publish.push($(this).attr("data-id")); - } else { - items_to_unpublish.push($(this).attr("data-id")); - } + this.$wrapper.find('.hub-card.active').map(function () { + items_to_publish.push($(this).attr("data-id")); }); return frappe.call( 'erpnext.hub_node.publish_selected_items', { - items_to_publish: items_to_publish, - items_to_unpublish: items_to_unpublish + items_to_publish: items_to_publish } ); } } +function get_empty_state(message) { + return `
          +

          ${message}

          +
          ` +} + function get_item_card_container_html(items, title='') { const items_html = (items || []).map(item => get_item_card_html(item)).join(''); From 9d1b7d493a2d18919ffbd2f1ebbac239c4d3b7ad Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 26 Jul 2018 18:54:58 +0530 Subject: [PATCH 019/154] [patch] Delete old hub page --- erpnext/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 20def27ced..c5d8e0f9f6 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -559,3 +559,4 @@ erpnext.patches.v11_0.update_allow_transfer_for_manufacture erpnext.patches.v11_0.rename_healthcare_doctype_and_fields erpnext.patches.v11_0.add_item_group_defaults erpnext.patches.v10_0.update_address_template_for_india +execute:frappe.delete_doc("Page", "hub") From 24d5f172559e4256cc81c14aa53a4b8e3b2defd4 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 26 Jul 2018 19:22:58 +0530 Subject: [PATCH 020/154] NotFound page for marketplace --- erpnext/public/js/hub/marketplace.js | 22 +++++++++++++++++----- erpnext/public/less/hub.less | 3 +-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 07e9858bfe..78cf8468e7 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -166,8 +166,10 @@ erpnext.hub.Marketplace = class Marketplace { } if (!Object.keys(this.subpages).includes(route[1])) { - frappe.show_not_found(); - return; + if (!this.subpages.not_found) { + this.subpages.not_found = new erpnext.hub.NotFound(this.$body); + } + route[1] = 'not_found'; } this.update_sidebar(); @@ -783,10 +785,20 @@ erpnext.hub.Publish = class Publish extends SubPage { } } -function get_empty_state(message) { - return `
          +erpnext.hub.NotFound = class NotFound extends SubPage { + refresh() { + this.$wrapper.html(get_empty_state( + __('Sorry! I could not find what you were looking for.'), + `` + )); + } +} + +function get_empty_state(message, action) { + return `

          ${message}

          -
          ` + ${action ? `

          ${action}

          `: ''} +
          `; } function get_item_card_container_html(items, title='') { diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index bd503fe1e3..fd1a752c3b 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -157,8 +157,7 @@ body[data-route^="marketplace/"] { } .empty-state { - justify-content: center; - padding-top: 159px; + height: 500px; } .form-container { From 44f7d2e62b6046b9967b8b3c59122b0370c58c19 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 27 Jul 2018 15:33:00 +0530 Subject: [PATCH 021/154] SearchPage --- erpnext/public/js/hub/marketplace.js | 89 +++++++++++++++++++++------- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 78cf8468e7..a230c2ca08 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -105,7 +105,6 @@ erpnext.hub.Marketplace = class Marketplace { make_body() { this.$body = this.$parent.find('.layout-main-section'); - this.$body.on('seller-registered', () => { this.registered = 1; this.make_sidebar_nav_buttons(); @@ -138,6 +137,10 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages.favourites = new erpnext.hub.Favourites(this.$body); } + if (route[1] === 'search' && route[2] && !this.subpages.search) { + this.subpages.search = new erpnext.hub.SearchPage(this.$body); + } + if (route[1] === 'category' && route[2] && !this.subpages.category) { this.subpages.category = new erpnext.hub.Category(this.$body); } @@ -182,6 +185,16 @@ class SubPage { constructor(parent, options) { this.$parent = $(parent); this.make_wrapper(options); + + // handle broken images after every render + if (this.render) { + this._render = this.render.bind(this); + + this.render = (...args) => { + this._render(...args); + frappe.dom.handle_broken_images(this.$wrapper); + } + } } make_wrapper() { @@ -203,7 +216,10 @@ class SubPage { erpnext.hub.Home = class Home extends SubPage { make_wrapper() { super.make_wrapper(); - this.make_search_bar(); + + make_search_bar(this.$wrapper, keyword => { + frappe.set_route('marketplace', 'search', keyword); + }); } refresh() { @@ -222,26 +238,9 @@ erpnext.hub.Home = class Home extends SubPage { return hub.call('get_data_for_homepage'); } - make_search_bar() { - const $search = $(` -
          - -
          ` - ); - this.$wrapper.append($search); - const $search_input = $search.find('input'); - - $search_input.on('keydown', frappe.utils.debounce((e) => { - if (e.which === frappe.ui.keyCode.ENTER) { - this.search_value = $search_input.val(); - this.get_items_and_render(); - } - }, 300)); - } - render(items) { const html = get_item_card_container_html(items, __('Recently Published')); - this.$wrapper.append(html) + this.$wrapper.append(html); } } @@ -289,6 +288,35 @@ erpnext.hub.Category = class Category extends SubPage { } } +erpnext.hub.SearchPage = class SearchPage extends SubPage { + make_wrapper() { + super.make_wrapper(); + + make_search_bar(this.$wrapper, keyword => { + frappe.set_route('marketplace', 'search', keyword); + }); + } + + refresh() { + this.keyword = frappe.get_route()[2]; + this.$wrapper.find('input').val(this.keyword); + if (!this.keyword) return; + + this.get_items_by_keyword(this.keyword) + .then(items => this.render(items)); + } + + get_items_by_keyword(keyword) { + return hub.call('get_items_by_keyword', { keyword }); + } + + render(items) { + this.$wrapper.find('.hub-card-container').remove(); + const html = get_item_card_container_html(items, __('Search results for "{0}"', [this.keyword])); + this.$wrapper.append(html); + } +} + erpnext.hub.Item = class Item extends SubPage { refresh() { this.hub_item_code = frappe.get_route()[2]; @@ -863,7 +891,7 @@ function get_item_card_html(item) {
          - +
          ${show_local_item_button}
          @@ -885,6 +913,25 @@ function get_rating_html(item) { return rating_html; } +function make_search_bar($wrapper, on_search) { + const $search = $(` +
          + +
          ` + ); + $wrapper.append($search); + const $search_input = $search.find('input'); + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + const search_value = $search_input.val(); + on_search(search_value); + } + }, 300)); +} + +// caching + erpnext.hub.cache = {}; hub.call = function call_hub_method(method, args={}) { return new Promise((resolve, reject) => { From f4fbf5215ba32300a45097a3c9b6d9adcfd40f9f Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Fri, 27 Jul 2018 21:07:56 +0530 Subject: [PATCH 022/154] [hub][search] empty search and route fallback --- erpnext/public/js/hub/marketplace.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index a230c2ca08..7b2907a9e8 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -137,7 +137,7 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages.favourites = new erpnext.hub.Favourites(this.$body); } - if (route[1] === 'search' && route[2] && !this.subpages.search) { + if (route[1] === 'search' && !this.subpages.search) { this.subpages.search = new erpnext.hub.SearchPage(this.$body); } @@ -298,9 +298,8 @@ erpnext.hub.SearchPage = class SearchPage extends SubPage { } refresh() { - this.keyword = frappe.get_route()[2]; + this.keyword = frappe.get_route()[2] || ''; this.$wrapper.find('input').val(this.keyword); - if (!this.keyword) return; this.get_items_by_keyword(this.keyword) .then(items => this.render(items)); @@ -312,7 +311,8 @@ erpnext.hub.SearchPage = class SearchPage extends SubPage { render(items) { this.$wrapper.find('.hub-card-container').remove(); - const html = get_item_card_container_html(items, __('Search results for "{0}"', [this.keyword])); + const title = this.keyword ? __('Search results for "{0}"', [this.keyword]) : ''; + const html = get_item_card_container_html(items, title); this.$wrapper.append(html); } } @@ -831,11 +831,14 @@ function get_empty_state(message, action) { function get_item_card_container_html(items, title='') { const items_html = (items || []).map(item => get_item_card_html(item)).join(''); + const title_html = title + ? `
          + ${title} +
          ` + : ''; const html = `
          -
          - ${title} -
          + ${title_html} ${items_html}
          `; From b7a63ab81e246579ce7ba9979b78e942b482136c Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 27 Jul 2018 21:51:26 +0530 Subject: [PATCH 023/154] Homepage - show random items from each hub seller - show items from country - move add_review method on hub --- erpnext/hub_node/__init__.py | 22 ----- erpnext/public/js/hub/marketplace.js | 126 +++++++++++++++------------ 2 files changed, 71 insertions(+), 77 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 6dc654b39e..3a23daaaa2 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -23,7 +23,6 @@ def call_hub_method(method, params=None): 'cmd': 'hub.hub.api.' + method }) - response = connection.post_api('hub.hub.api.' + method, params) response = connection.post_request(params) return response @@ -156,27 +155,6 @@ def update_category(hub_item_code, category): return response -@frappe.whitelist() -def send_review(hub_item_code, review): - review = json.loads(review) - hub_connection = get_hub_connection() - - item_doc = hub_connection.connection.get_doc('Hub Item', hub_item_code) - existing_reviews = item_doc.get('reviews') - - reviews = [review] - review.setdefault('idx', 0) - for r in existing_reviews: - if r.get('user') != review.get('user'): - reviews.append(r) - - response = hub_connection.update('Hub Item', dict( - doctype='Hub Item', - reviews = reviews - ), hub_item_code) - - return response - @frappe.whitelist() def get_details(hub_sync_id=None, doctype='Hub Item'): if not hub_sync_id: diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index a230c2ca08..b08d79b2df 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -228,19 +228,24 @@ erpnext.hub.Home = class Home extends SubPage { get_items_and_render() { this.$wrapper.find('.hub-card-container').empty(); - this.get_items() - .then(items => { - this.render(items); + this.get_data() + .then(data => { + this.render(data); }); } - get_items() { - return hub.call('get_data_for_homepage'); + get_data() { + return hub.call('get_data_for_homepage', { country: frappe.defaults.get_user_default('country') }); } - render(items) { - const html = get_item_card_container_html(items, __('Recently Published')); + render(data) { + let html = get_item_card_container_html(data.random_items, __('Explore')); this.$wrapper.append(html); + + if (data.items_by_country.length) { + html = get_item_card_container_html(data.items_by_country, __('Near you')); + this.$wrapper.append(html); + } } } @@ -323,38 +328,23 @@ erpnext.hub.Item = class Item extends SubPage { this.get_item(this.hub_item_code) .then(item => { + this.item = item; this.render(item); }); } get_item(hub_item_code) { - return new Promise(resolve => { - const item = (erpnext.hub.hub_item_cache || []).find(item => item.name === hub_item_code) - - if (item) { - resolve(item); - } else { - frappe.call('erpnext.hub_node.get_list', { - doctype: 'Hub Item', - filters: { - name: hub_item_code - } - }) - .then(r => { - resolve(r.message[0]); - }); - } - }); + return hub.call('get_item_details', { hub_item_code }); } render(item) { const title = item.item_name || item.name; - const company = item.company_name; + const seller = item.company; - const who = __('Posted By {0}', [company]); + const who = __('Posted By {0}', [seller]); const when = comment_when(item.creation); - const city = item.seller_city ? item.seller_city + ', ' : ''; + const city = item.city ? item.city + ', ' : ''; const country = item.country ? item.country : ''; const where = `${city}${country}`; @@ -362,8 +352,8 @@ erpnext.hub.Item = class Item extends SubPage { const description = item.description || ''; - const rating_html = get_rating_html(item); - const rating_count = item.reviews.length > 0 ? `(${item.reviews.length} reviews)` : ''; + const rating_html = get_rating_html(item.average_rating); + const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet'); const html = `
          @@ -382,7 +372,7 @@ erpnext.hub.Item = class Item extends SubPage {

          ${title}

          ${where}${dot_spacer}${when}

          -

          ${rating_html}${rating_count}

          +

          ${rating_html} (${rating_count})


          @@ -402,7 +392,7 @@ erpnext.hub.Item = class Item extends SubPage {
          - ${company} + ${seller}

          Contact Seller

          @@ -428,40 +418,60 @@ erpnext.hub.Item = class Item extends SubPage { this.$wrapper.html(html); this.make_review_area(); - this.render_reviews(item); + + this.get_reviews() + .then(reviews => { + this.reviews = reviews; + this.render_reviews(reviews); + }); } make_review_area() { this.comment_area = new frappe.ui.ReviewArea({ parent: this.$wrapper.find('.timeline-head').empty(), mentions: [], - on_submit: (val) => { - val.user = frappe.session.user; - val.username = frappe.session.user_fullname; + on_submit: (values) => { + values.user = frappe.session.user; + values.username = frappe.session.user_fullname; - frappe.call({ - method: 'erpnext.hub_node.send_review', - args: { - hub_item_code: this.hub_item_code, - review: val - }, - callback: (r) => { - console.log(r); - this.render_reviews(r.message); - this.comment_area.reset(); - }, - freeze: true + hub.call('add_item_review', { + hub_item_code: this.hub_item_code, + review: JSON.stringify(values) + }) + .then(review => { + this.reviews = this.reviews || []; + this.reviews.push(review); + this.render_reviews(this.reviews); + + this.comment_area.reset(); }); } }); } - render_reviews(item) { - this.$wrapper.find('.timeline-items').empty(); - item.reviews.forEach(review => this.render_review(review, item)); + get_reviews() { + return hub.call('get_item_reviews', { hub_item_code: this.hub_item_code }).catch(() => {}); } - render_review(review, item) { + render_reviews(reviews=[]) { + this.$wrapper.find('.timeline-items').empty(); + + reviews.sort((a, b) => { + if (a.modified > b.modified) { + return -1; + } + + if (a.modified < b.modified) { + return 1; + } + + return 0; + }); + + reviews.forEach(review => this.render_review(review)); + } + + render_review(review) { let username = review.username || review.user || __("Anonymous"); let image_html = review.user_image @@ -481,7 +491,7 @@ erpnext.hub.Item = class Item extends SubPage {
          ` : ''; - let rating_html = get_rating_html(item); + let rating_html = get_rating_html(review.rating); const $timeline_items = this.$wrapper.find('.timeline-items'); @@ -847,7 +857,7 @@ function get_item_card_html(item) { const title = strip_html(item_name); const img_url = item.image; - const company_name = item.company_name; + const company_name = item.company; const active = item.publish_in_hub; @@ -902,8 +912,7 @@ function get_item_card_html(item) { return item_html; } -function get_rating_html(item) { - const rating = item.average_rating; +function get_rating_html(rating) { let rating_html = ``; for (var i = 0; i < 5; i++) { let star_class = 'fa-star'; @@ -956,6 +965,13 @@ hub.call = function call_hub_method(method, args={}) { }) .then(r => { if (r.message) { + if (r.message.error) { + frappe.throw({ + title: __('Marketplace Error'), + message: r.message.error + }); + } + erpnext.hub.cache[key] = r.message; resolve(r.message) } From 6e7fb71f1faca3de0890f680f4415b90725f73b6 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Fri, 27 Jul 2018 22:02:47 +0530 Subject: [PATCH 024/154] [hub][patch] reset publish_in_hub for all items --- .../item_to_hub_item/item_to_hub_item.json | 72 +++++++++---------- .../hub_sync/hub_sync.json | 26 +++---- erpnext/patches.txt | 1 + .../reset_publish_in_hub_for_all_items.py | 5 ++ 4 files changed, 53 insertions(+), 51 deletions(-) create mode 100644 erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json index 7423f2e8a6..55bda4843d 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json @@ -1,55 +1,55 @@ { - "condition": "{\"publish_in_hub\": 1}", - "creation": "2017-09-07 13:27:52.726350", - "docstatus": 0, - "doctype": "Data Migration Mapping", + "condition": "{\"publish_in_hub\": 1}", + "creation": "2017-09-07 13:27:52.726350", + "docstatus": 0, + "doctype": "Data Migration Mapping", "fields": [ { - "is_child_table": 0, - "local_fieldname": "item_code", + "is_child_table": 0, + "local_fieldname": "item_code", "remote_fieldname": "item_code" - }, + }, { - "is_child_table": 0, - "local_fieldname": "item_name", + "is_child_table": 0, + "local_fieldname": "item_name", "remote_fieldname": "item_name" - }, + }, { - "is_child_table": 0, - "local_fieldname": "eval:frappe.db.get_default(\"company\")", + "is_child_table": 0, + "local_fieldname": "eval:frappe.db.get_default(\"company\")", "remote_fieldname": "company_name" - }, + }, { - "is_child_table": 0, - "local_fieldname": "image", + "is_child_table": 0, + "local_fieldname": "image", "remote_fieldname": "image" - }, + }, { - "is_child_table": 0, - "local_fieldname": "item_group", + "is_child_table": 0, + "local_fieldname": "item_group", "remote_fieldname": "item_group" - }, + }, { - "is_child_table": 0, - "local_fieldname": "eval:frappe.session.user", + "is_child_table": 0, + "local_fieldname": "eval:frappe.session.user", "remote_fieldname": "seller" - }, + }, { - "is_child_table": 0, - "local_fieldname": "eval:frappe.db.get_default(\"country\")", + "is_child_table": 0, + "local_fieldname": "eval:frappe.db.get_default(\"country\")", "remote_fieldname": "country" } - ], - "idx": 1, - "local_doctype": "Item", - "mapping_name": "Item to Hub Item", - "mapping_type": "Push", - "migration_id_field": "hub_sync_id", - "modified": "2018-02-14 15:57:05.595712", - "modified_by": "achilles@erpnext.com", - "name": "Item to Hub Item", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Hub Item", + ], + "idx": 1, + "local_doctype": "Item", + "mapping_name": "Item to Hub Item", + "mapping_type": "Push", + "migration_id_field": "hub_sync_id", + "modified": "2018-07-27 21:52:52.383842", + "modified_by": "cave@aperture.com", + "name": "Item to Hub Item", + "owner": "Administrator", + "page_length": 10, + "remote_objectname": "Hub Item", "remote_primary_key": "item_code" } \ No newline at end of file diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json index d66ac24b1c..c07623ad33 100644 --- a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json +++ b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json @@ -1,22 +1,18 @@ { - "creation": "2017-09-07 11:39:38.445902", - "docstatus": 0, - "doctype": "Data Migration Plan", - "idx": 1, + "creation": "2017-09-07 11:39:38.445902", + "docstatus": 0, + "doctype": "Data Migration Plan", + "idx": 1, "mappings": [ { - "enabled": 1, + "enabled": 1, "mapping": "Item to Hub Item" - }, - { - "enabled": 1, - "mapping": "Hub Message to Lead" } - ], - "modified": "2018-02-14 15:57:05.519715", - "modified_by": "achilles@erpnext.com", - "module": "Hub Node", - "name": "Hub Sync", - "owner": "Administrator", + ], + "modified": "2018-07-27 21:52:52.324025", + "modified_by": "cave@aperture.com", + "module": "Hub Node", + "name": "Hub Sync", + "owner": "Administrator", "plan_name": "Hub Sync" } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c5d8e0f9f6..154f440740 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -560,3 +560,4 @@ erpnext.patches.v11_0.rename_healthcare_doctype_and_fields erpnext.patches.v11_0.add_item_group_defaults erpnext.patches.v10_0.update_address_template_for_india execute:frappe.delete_doc("Page", "hub") +erpnext.patches.v11_0.reset_publish_in_hub_for_all_items diff --git a/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py b/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py new file mode 100644 index 0000000000..fac772ccdd --- /dev/null +++ b/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py @@ -0,0 +1,5 @@ +import frappe + +def execute(): + frappe.reload_doc('stock', 'doctype', 'item') + frappe.db.sql("""update `tabItem` set publish_in_hub = 0""") From 83d60ffa1d1aacc1081146efab163305f13745d8 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Sat, 28 Jul 2018 00:07:25 +0530 Subject: [PATCH 025/154] [hub] update hub item mapping --- .../item_to_hub_item/item_to_hub_item.json | 74 ++++++++----------- 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json index 55bda4843d..befc87f967 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json @@ -1,55 +1,45 @@ { - "condition": "{\"publish_in_hub\": 1}", - "creation": "2017-09-07 13:27:52.726350", - "docstatus": 0, - "doctype": "Data Migration Mapping", + "condition": "{\"publish_in_hub\": 1}", + "creation": "2017-09-07 13:27:52.726350", + "docstatus": 0, + "doctype": "Data Migration Mapping", "fields": [ { - "is_child_table": 0, - "local_fieldname": "item_code", + "is_child_table": 0, + "local_fieldname": "item_code", "remote_fieldname": "item_code" - }, + }, { - "is_child_table": 0, - "local_fieldname": "item_name", + "is_child_table": 0, + "local_fieldname": "item_name", "remote_fieldname": "item_name" - }, + }, { - "is_child_table": 0, - "local_fieldname": "eval:frappe.db.get_default(\"company\")", - "remote_fieldname": "company_name" - }, + "is_child_table": 0, + "local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')", + "remote_fieldname": "hub_seller" + }, { - "is_child_table": 0, - "local_fieldname": "image", + "is_child_table": 0, + "local_fieldname": "image", "remote_fieldname": "image" - }, + }, { - "is_child_table": 0, - "local_fieldname": "item_group", + "is_child_table": 0, + "local_fieldname": "item_group", "remote_fieldname": "item_group" - }, - { - "is_child_table": 0, - "local_fieldname": "eval:frappe.session.user", - "remote_fieldname": "seller" - }, - { - "is_child_table": 0, - "local_fieldname": "eval:frappe.db.get_default(\"country\")", - "remote_fieldname": "country" } - ], - "idx": 1, - "local_doctype": "Item", - "mapping_name": "Item to Hub Item", - "mapping_type": "Push", - "migration_id_field": "hub_sync_id", - "modified": "2018-07-27 21:52:52.383842", - "modified_by": "cave@aperture.com", - "name": "Item to Hub Item", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Hub Item", + ], + "idx": 1, + "local_doctype": "Item", + "mapping_name": "Item to Hub Item", + "mapping_type": "Push", + "migration_id_field": "hub_sync_id", + "modified": "2018-07-27 21:52:52.383842", + "modified_by": "cave@aperture.com", + "name": "Item to Hub Item", + "owner": "Administrator", + "page_length": 10, + "remote_objectname": "Hub Item", "remote_primary_key": "item_code" -} \ No newline at end of file +} From dd8045759fc82031dc70bcd303a4c6957ad65e92 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Sat, 28 Jul 2018 01:54:06 +0530 Subject: [PATCH 026/154] [hub] Published Products Page --- .../doctype/hub_settings/hub_settings.json | 34 +++++++++- .../doctype/hub_settings/hub_settings.py | 65 +++++-------------- erpnext/public/js/hub/marketplace.js | 62 +++++++++++------- 3 files changed, 89 insertions(+), 72 deletions(-) diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.json b/erpnext/hub_node/doctype/hub_settings/hub_settings.json index 61ef48ca54..a5338e9b3e 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json @@ -173,6 +173,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "site_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Site Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -643,7 +675,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-07-26 15:56:36.104768", + "modified": "2018-07-28 00:48:21.210413", "modified_by": "cave@aperture.com", "module": "Hub Node", "name": "Hub Settings", diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 621229cb33..0b7dc48b15 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -15,24 +15,13 @@ from six import string_types # hub_url = "http://erpnext.hub:8001" hub_url = "http://hub.market:8000" -class OAuth2Session(): - def __init__(self, headers): - self.headers = headers - def get(self, url, params, headers, verify): - res = requests.get(url, params=params, headers=self.headers, verify=verify) - return res - def post(self, url, data, verify): - res = requests.post(url, data=data, headers=self.headers, verify=verify) - return res - def put(self, url, data, verify): - res = requests.put(url, data=data, headers=self.headers, verify=verify) - return res - class HubSetupError(frappe.ValidationError): pass class HubSettings(Document): def validate(self): + protocol = 'http://' + self.site_name = protocol + frappe.local.site + ':' + str(frappe.conf.webserver_port) if self.publish_pricing and not self.selling_price_list: frappe.throw(_("Please select a Price List to publish pricing")) @@ -53,35 +42,13 @@ class HubSettings(Document): doc.run() # self.sync_in_progress = 0 - def pre_reg(self): - site_name = frappe.local.site + ':' + str(frappe.conf.webserver_port) - protocol = 'http://' - route = '/token' - data = { - 'site_name': site_name, - 'protocol': protocol, - 'route': route - } - - redirect_url = protocol + site_name + route - post_url = hub_url + '/api/method/hub.hub.api.pre_reg' - - response = requests.post(post_url, data=data) - response.raise_for_status() - message = response.json().get('message') - - if message and message.get('client_id'): - print("======CLIENT_ID======") - print(message.get('client_id')) - - return { - 'client_id': message.get('client_id'), - 'redirect_uri': redirect_url - } - - def register(self): """ Create a User on hub.erpnext.org and return username/password """ + + # TODO: site_name for cloud sites + protocol = 'http://' + self.site_name = protocol + frappe.local.site + ':' + str(frappe.conf.webserver_port) + data = { 'profile': self.as_json() } @@ -103,18 +70,18 @@ class HubSettings(Document): return message or None - def unregister(self): - """ Disable the User on hub.erpnext.org""" + # def unregister(self): + # """ Disable the User on hub.erpnext.org""" - hub_connector = frappe.get_doc( - 'Data Migration Connector', 'Hub Connector') + # hub_connector = frappe.get_doc( + # 'Data Migration Connector', 'Hub Connector') - connection = hub_connector.get_connection() - response_doc = connection.update('User', frappe._dict({'enabled': 0}), hub_connector.username) + # connection = hub_connector.get_connection() + # response_doc = connection.update('User', frappe._dict({'enabled': 0}), hub_connector.username) - if response_doc['enabled'] == 0: - self.enabled = 0 - self.save() + # if response_doc['enabled'] == 0: + # self.enabled = 0 + # self.save() def create_hub_connector(self, message): if frappe.db.exists('Data Migration Connector', 'Hub Connector'): diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index cb879eb720..7363afb912 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -8,7 +8,7 @@ erpnext.hub.Marketplace = class Marketplace { frappe.db.get_doc('Hub Settings') .then(doc => { - this.hub_settings = doc; + hub.settings = doc; this.registered = doc.registered; this.setup_header(); @@ -158,14 +158,15 @@ erpnext.hub.Marketplace = class Marketplace { } if (route[1] === 'profile' && !this.subpages.profile) { - this.subpages.profile = new erpnext.hub.Profile(this.$body, {data: this.hub_settings}); + this.subpages.profile = new erpnext.hub.Profile(this.$body); } if (route[1] === 'publish' && !this.subpages.publish) { - this.subpages.publish = new erpnext.hub.Publish( - this.$body, - {sync_in_progress: this.hub_settings.sync_in_progress} - ); + this.subpages.publish = new erpnext.hub.Publish(this.$body); + } + + if (route[1] === 'my-products' && !this.subpages['my-products']) { + this.subpages['my-products'] = new erpnext.hub.PublishedProducts(this.$body); } if (!Object.keys(this.subpages).includes(route[1])) { @@ -650,13 +651,12 @@ erpnext.hub.Register = class Register extends SubPage { } erpnext.hub.Profile = class Profile extends SubPage { - constructor(parent, profile_data) { - super(parent); - this.profile_data = profile_data; - } - make_wrapper() { super.make_wrapper(); + + // Shorthand for profile data; + const p = hub.settings; + const profile_html = `
          @@ -696,21 +696,16 @@ erpnext.hub.Profile = class Profile extends SubPage { } erpnext.hub.Publish = class Publish extends SubPage { - make_wrapper(options) { - super.make_wrapper(); - this.sync_in_progress = options.sync_in_progress; - - this.load_publish_page(); - } - load_publish_page() { const title_html = `${__('Select Products to Publish')}`; + const info = `

          ${__("Status decided by the 'Publish in Hub' field in Item.")}

          `; - const subtitle_html = ` -

          + + const subtitle_html = `

          ${__(`Only products with an image, description and category can be published. Please update them if an item in your inventory does not appear.`)}

          `; + const publish_button_html = ` + +
          `; + } + const html = `
          @@ -409,9 +421,10 @@ erpnext.hub.Item = class Item extends SubPage { ${description ? `${__('Description')}

          ${description}

          - ` : __('No description') + ` : `

          ${__('No description')}

          ` }

          + ${edit_buttons_html}
          @@ -447,6 +460,10 @@ erpnext.hub.Item = class Item extends SubPage { this.$wrapper.html(html); + if(this.own_item) { + this.bind_edit_buttons(); + } + this.make_review_area(); this.get_reviews() @@ -456,6 +473,31 @@ erpnext.hub.Item = class Item extends SubPage { }); } + bind_edit_buttons() { + this.edit_dialog = new frappe.ui.Dialog({ + title: "Edit Your Product", + fields: [] + }); + + this.$wrapper.find('.edit-item').on('click', this.on_edit.bind(this)); + this.$wrapper.find('.unpublish').on('click', this.on_unpublish.bind(this)); + } + + on_edit() { + this.edit_dialog.show(); + } + + on_unpublish() { + if(!this.unpublish_dialog) { + this.unpublish_dialog = new frappe.ui.Dialog({ + title: "Edit Your Product", + fields: [] + }); + } + + this.unpublish_dialog.show(); + } + make_review_area() { this.comment_area = new frappe.ui.ReviewArea({ parent: this.$wrapper.find('.timeline-head').empty(), From abcc8ab8e5cafc21edbc4a233838c47bb7a676fb Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 28 Jul 2018 13:57:16 +0530 Subject: [PATCH 029/154] Push review area to bottom --- erpnext/public/less/hub.less | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index fd1a752c3b..5886893fbc 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -171,6 +171,14 @@ body[data-route^="marketplace/"] { padding-bottom: 0; border-bottom: none; } + + .hub-item-container { + overflow: hidden; + } + + .hub-item-review-container { + margin-top: calc(30vh); + } } body[data-route^="Hub/"] { From 296848cb69b221a79e8d3d9aafe6df9c195a4a96 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 28 Jul 2018 16:14:22 +0530 Subject: [PATCH 030/154] Publish Page - Code cleanup - Separate local item card and hub item card - Use make_search_bar --- erpnext/hub_node/__init__.py | 11 +- erpnext/public/js/hub/marketplace.js | 175 ++++++++++++++++----------- erpnext/public/less/hub.less | 39 +++--- 3 files changed, 132 insertions(+), 93 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 3a23daaaa2..5ed2dade2f 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -67,17 +67,16 @@ def get_valid_items(search_value=''): @frappe.whitelist() def publish_selected_items(items_to_publish): - for item_code in json.loads(items_to_publish): - frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - # frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1) - # time.sleep(10) - # frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0) + items_to_publish = json.loads(items_to_publish) + + for item_code in items_to_publish: + frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) hub_settings = frappe.get_doc('Hub Settings') hub_settings.sync() - return + return len(items_to_publish) @frappe.whitelist() def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 7363afb912..97fed62406 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -28,8 +28,6 @@ erpnext.hub.Marketplace = class Marketplace { const $target = $(e.currentTarget); const route = $target.data().route; frappe.set_route(route); - - e.stopPropagation(); }); } @@ -218,8 +216,11 @@ erpnext.hub.Home = class Home extends SubPage { make_wrapper() { super.make_wrapper(); - make_search_bar(this.$wrapper, keyword => { - frappe.set_route('marketplace', 'search', keyword); + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + frappe.set_route('marketplace', 'search', keyword); + } }); } @@ -298,8 +299,11 @@ erpnext.hub.SearchPage = class SearchPage extends SubPage { make_wrapper() { super.make_wrapper(); - make_search_bar(this.$wrapper, keyword => { - frappe.set_route('marketplace', 'search', keyword); + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + frappe.set_route('marketplace', 'search', keyword); + } }); } @@ -696,6 +700,11 @@ erpnext.hub.Profile = class Profile extends SubPage { } erpnext.hub.Publish = class Publish extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.load_publish_page(); + } + load_publish_page() { const title_html = `${__('Select Products to Publish')}`; @@ -708,15 +717,11 @@ erpnext.hub.Publish = class Publish extends SubPage { const publish_button_html = ``; - const select_all_button = ``; - const deselect_all_button = ``; - - const search_html = `
          - -
          `; + const select_all_button = ``; + const deselect_all_button = ``; const subpage_header = $(`
          @@ -726,12 +731,19 @@ erpnext.hub.Publish = class Publish extends SubPage {
          ${publish_button_html}
          - - ${search_html} `); this.$wrapper.append(subpage_header); + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + this.search_value = keyword; + this.get_items_and_render(); + }, + placeholder: __('Search Items') + }); + this.setup_events(); } @@ -740,27 +752,32 @@ erpnext.hub.Publish = class Publish extends SubPage { this.load_publishing_state(); this.publish_selected_items() .then(r => { - frappe.msgprint('check'); + console.log(`${r.message} items will be published`); }); }); - const $search_input = this.$wrapper.find('.hub-search-container input'); - this.search_value = ''; + this.$wrapper.on('click', '.hub-card', (e) => { + const $target = $(e.currentTarget); + $target.toggleClass('active'); - $search_input.on('keydown', frappe.utils.debounce((e) => { - if (e.which === frappe.ui.keyCode.ENTER) { - this.search_value = $search_input.val(); - this.get_items_and_render(); + // Get total items + const total_items = this.$wrapper.find('.hub-card.active').length; + + let button_label; + if (total_items > 0) { + const more_than_one = total_items > 1; + button_label = __('Publish {0} item{1}', [total_items, more_than_one ? 's' : '']); + } else { + button_label = __('Publish'); } - }, 300)); + + this.$wrapper.find('.publish-items') + .text(button_label) + .prop('disabled', total_items === 0); + }); } get_items_and_render() { - if(hub.settings.sync_in_progress) { - this.load_publishing_state(); - return; - } - this.$wrapper.find('.hub-card-container').empty(); this.get_valid_items() .then(r => { @@ -769,22 +786,15 @@ erpnext.hub.Publish = class Publish extends SubPage { } refresh() { - this.get_items_and_render(); + if (hub.settings.sync_in_progress) { + this.load_publishing_state(); + } else { + this.get_items_and_render(); + } } render(items) { - const items_container = $(get_item_card_container_html(items)); - items_container.addClass('static').on('click', '.hub-card', (e) => { - const $target = $(e.currentTarget); - $target.toggleClass('active'); - - // Get total items - const total_items = this.$wrapper.find('.hub-card.active').length; - const more_than_one = total_items > 1; - this.$wrapper.find('.publish-items') - .html(__('Publish ' + total_items + ' item' + (more_than_one ? 's' : ''))); - }); - + const items_container = $(get_item_card_container_html(items, '', get_local_item_card_html)); this.$wrapper.append(items_container); } @@ -855,8 +865,8 @@ function get_empty_state(message, action) {
          `; } -function get_item_card_container_html(items, title='') { - const items_html = (items || []).map(item => get_item_card_html(item)).join(''); +function get_item_card_container_html(items, title='', get_item_html = get_item_card_html) { + const items_html = (items || []).map(item => get_item_html(item)).join(''); const title_html = title ? `
          ${title} @@ -875,11 +885,44 @@ function get_item_card_html(item) { const item_name = item.item_name || item.name; const title = strip_html(item_name); const img_url = item.image; - const company_name = item.company; - const active = item.publish_in_hub; + // Subtitle + let subtitle = [comment_when(item.creation)]; + const rating = item.average_rating; + if (rating > 0) { + subtitle.push(rating + ``) + } + subtitle.push(company_name); + let dot_spacer = ''; + subtitle = subtitle.join(dot_spacer); + + const item_html = ` +
          +
          +
          +
          ${title}
          +
          ${subtitle}
          +
          +
          + +
          +
          +
          +
          + `; + + return item_html; +} + +function get_local_item_card_html(item) { + const item_name = item.item_name || item.name; + const title = strip_html(item_name); + const img_url = item.image; + const company_name = item.company; + + const is_active = item.publish_in_hub; const id = item.hub_item_code || item.item_code; // Subtitle @@ -893,36 +936,27 @@ function get_item_card_html(item) { let dot_spacer = ''; subtitle = subtitle.join(dot_spacer); - // Decide item link - const is_local = item.source_type === "local"; - const route = !is_local - ? `marketplace/item/${item.hub_item_code}` - : `Form/Item/${item.item_name}`; - - const card_route = is_local ? '' : `data-route='${route}'`; - - const show_local_item_button = is_local - ? `
          - -
          ` - : ''; + const edit_item_button = `
          + +
          `; const item_html = `
          -
          +
          -
          -
          ${title}
          -
          ${subtitle}
          -
          +
          ${title}
          +
          ${subtitle}
          -
          - ${show_local_item_button} +
          +
          + ${edit_item_button} +
          +
          @@ -931,6 +965,7 @@ function get_item_card_html(item) { return item_html; } + function get_rating_html(rating) { let rating_html = ``; for (var i = 0; i < 5; i++) { @@ -941,13 +976,13 @@ function get_rating_html(rating) { return rating_html; } -function make_search_bar($wrapper, on_search) { +function make_search_bar({wrapper, on_search, placeholder = __('Search for anything')}) { const $search = $(`
          - +
          ` ); - $wrapper.append($search); + wrapper.append($search); const $search_input = $search.find('input'); $search_input.on('keydown', frappe.utils.debounce((e) => { diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 5886893fbc..177ec42728 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -43,10 +43,12 @@ body[data-route^="marketplace/"] { overflow: hidden; cursor: pointer; - &:hover .overlay { + &:hover .hub-card-overlay { display: block; } + } + .hub-card.is-local { &.active { .hub-card-header { background-color: #f4ffe5; @@ -56,42 +58,45 @@ body[data-route^="marketplace/"] { display: inline; } } - } - - .hub-card-header { - padding: 12px 15px; - height: 60px; - border-bottom: 1px solid @border-color; - - display: flex; - justify-content: space-between; .octicon-check { display: none; + position: absolute; font-size: 20px; + right: 15px; + top: 50%; + transform: translateY(-50%); } } + .hub-card-header { + position: relative; + padding: 12px 15px; + height: 60px; + border-bottom: 1px solid @border-color; + } + .hub-card-body { position: relative; height: 200px; } - .overlay { + .hub-card-overlay { display: none; position: absolute; - } - - .hub-card-overlay { top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.1); } - .button-overlay { - top: 155px; - left: 15px; + .hub-card-overlay-body { + position: relative; + height: 100%; + } + + .hub-card-overlay-button { + position: absolute; } .hub-card-image { From 6f05ea124eccba5bd9429b0003efbb792dc43f81 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Sun, 29 Jul 2018 14:40:52 +0530 Subject: [PATCH 031/154] [hub] Track Hub Sync on the Server - Create a Hub Seller activity on start of Hub Sync - Store its name as remote_id in the Run - Update that activity remotely once the Run here is complete, status --- erpnext/demo/data/drug_list.json | 63 ------------------- erpnext/hub_node/__init__.py | 49 ++++++++++++--- .../item_to_hub_item/item_to_hub_item.json | 2 +- .../hub_sync/hub_sync.json | 27 ++++---- .../doctype/hub_settings/hub_settings.py | 24 ++++--- erpnext/public/js/hub/marketplace.js | 18 ++++-- 6 files changed, 83 insertions(+), 100 deletions(-) diff --git a/erpnext/demo/data/drug_list.json b/erpnext/demo/data/drug_list.json index f34ca572d2..9b101cb1c8 100644 --- a/erpnext/demo/data/drug_list.json +++ b/erpnext/demo/data/drug_list.json @@ -48,7 +48,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -133,7 +132,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -218,7 +216,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -303,7 +300,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -388,7 +384,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -473,7 +468,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -558,7 +552,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -643,7 +636,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -728,7 +720,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -813,7 +804,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -898,7 +888,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -983,7 +972,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1068,7 +1056,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1153,7 +1140,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1238,7 +1224,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1323,7 +1308,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1408,7 +1392,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1493,7 +1476,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1578,7 +1560,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1663,7 +1644,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1748,7 +1728,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1833,7 +1812,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -1918,7 +1896,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2003,7 +1980,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2088,7 +2064,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2173,7 +2148,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2258,7 +2232,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2343,7 +2316,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2428,7 +2400,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2513,7 +2484,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2598,7 +2568,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2683,7 +2652,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2768,7 +2736,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2853,7 +2820,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -2938,7 +2904,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3023,7 +2988,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3108,7 +3072,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3193,7 +3156,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3278,7 +3240,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3363,7 +3324,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3448,7 +3408,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3533,7 +3492,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3618,7 +3576,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3703,7 +3660,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3788,7 +3744,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3873,7 +3828,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -3958,7 +3912,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4043,7 +3996,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4128,7 +4080,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4213,7 +4164,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4298,7 +4248,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4383,7 +4332,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4468,7 +4416,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4553,7 +4500,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4638,7 +4584,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4723,7 +4668,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4808,7 +4752,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4893,7 +4836,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -4978,7 +4920,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -5063,7 +5004,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -5148,7 +5088,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -5233,7 +5172,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, @@ -5318,7 +5256,6 @@ "naming_series": null, "net_weight": 0.0, "opening_stock": 0.0, - "publish_in_hub": 1, "quality_parameters": [], "reorder_levels": [], "route": null, diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 3a23daaaa2..471f039ba2 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -2,7 +2,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, requests, json, time +import frappe, requests, json from frappe.utils import now, nowdate, cint from frappe.utils.nestedset import get_root_of from frappe.contacts.doctype.contact.contact import get_default_contact @@ -18,7 +18,9 @@ def enable_hub(): def call_hub_method(method, params=None): connection = get_client_connection() - params = json.loads(params) + if type(params) == unicode: + params = json.loads(params) + params.update({ 'cmd': 'hub.hub.api.' + method }) @@ -67,17 +69,46 @@ def get_valid_items(search_value=''): @frappe.whitelist() def publish_selected_items(items_to_publish): - for item_code in json.loads(items_to_publish): + items_to_publish = json.loads(items_to_publish) + if not len(items_to_publish): + return + + for item_code in items_to_publish: frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - # frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1) - # time.sleep(10) - # frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0) - hub_settings = frappe.get_doc('Hub Settings') - hub_settings.sync() + remote_id = item_sync_preprocess() + hub_settings.sync(remote_id) - return + return remote_id + +def item_sync_preprocess(): + # Call Hub to make a new activity + # and return an activity ID + # that will be used as the remote ID for the Migration Run + + response = call_hub_method('init_new_activity_for_seller', { + 'hub_seller': frappe.db.get_value("Hub Settings", "Hub Settings", "company_email"), + 'activity_type': 'Items Publish' + }) + + if response: + # frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1) + return response + else: + return '' + +def item_sync_postprocess(obj): + response = call_hub_method('update_activity_for_seller', { + 'hub_seller': frappe.db.get_value("Hub Settings", "Hub Settings", "company_email"), + 'name': obj["remote_id"], + 'status': obj["status"] + }) + + if response: + frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0) + else: + frappe.throw("Unable to update remote activity") @frappe.whitelist() def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json index befc87f967..edcf91a370 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json @@ -35,7 +35,7 @@ "mapping_name": "Item to Hub Item", "mapping_type": "Push", "migration_id_field": "hub_sync_id", - "modified": "2018-07-27 21:52:52.383842", + "modified": "2018-07-28 22:25:35.289335", "modified_by": "cave@aperture.com", "name": "Item to Hub Item", "owner": "Administrator", diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json index c07623ad33..7490e432ac 100644 --- a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json +++ b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json @@ -1,18 +1,19 @@ { - "creation": "2017-09-07 11:39:38.445902", - "docstatus": 0, - "doctype": "Data Migration Plan", - "idx": 1, + "creation": "2017-09-07 11:39:38.445902", + "docstatus": 0, + "doctype": "Data Migration Plan", + "idx": 1, "mappings": [ { - "enabled": 1, + "enabled": 1, "mapping": "Item to Hub Item" } - ], - "modified": "2018-07-27 21:52:52.324025", - "modified_by": "cave@aperture.com", - "module": "Hub Node", - "name": "Hub Sync", - "owner": "Administrator", - "plan_name": "Hub Sync" -} \ No newline at end of file + ], + "modified": "2018-07-28 22:25:35.216172", + "modified_by": "cave@aperture.com", + "module": "Hub Node", + "name": "Hub Sync", + "owner": "Administrator", + "plan_name": "Hub Sync", + "postprocess_method": "erpnext.hub_node.item_sync_postprocess" +} diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 0b7dc48b15..66dd560388 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -2,7 +2,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, requests, json +import frappe, requests, json, time from frappe.model.document import Document from frappe.utils import add_years, now, get_datetime, get_datetime_str @@ -28,19 +28,23 @@ class HubSettings(Document): def get_hub_url(self): return hub_url - def sync(self): + def sync(self, remote_id): """Create and execute Data Migration Run for Hub Sync plan""" frappe.has_permission('Hub Settings', throw=True) - doc = frappe.get_doc({ - 'doctype': 'Data Migration Run', - 'data_migration_plan': 'Hub Sync', - 'data_migration_connector': 'Hub Connector', - }).insert() + if remote_id: + doc = frappe.get_doc({ + 'doctype': 'Data Migration Run', + 'data_migration_plan': 'Hub Sync', + 'data_migration_connector': 'Hub Connector', + 'remote_id': remote_id + }).insert() - # self.sync_in_progress = 1 - doc.run() - # self.sync_in_progress = 0 + self.sync_in_progress = 1 + # time.sleep(10) + doc.run() + else: + frappe.throw("No remote ID specified") def register(self): """ Create a User on hub.erpnext.org and return username/password """ diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index f024e774fd..5261c3f554 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -767,7 +767,16 @@ erpnext.hub.Profile = class Profile extends SubPage { } erpnext.hub.Publish = class Publish extends SubPage { - load_publish_page() { + make_wrapper() { + super.make_wrapper(); + if(!hub.settings.sync_in_progress) { + this.make_publish_header(); + } else { + this.show_publishing_state(); + } + } + + make_publish_header() { const title_html = `${__('Select Products to Publish')}`; const info = `

          ${__("Status decided by the 'Publish in Hub' field in Item.")}

          `; @@ -808,7 +817,6 @@ erpnext.hub.Publish = class Publish extends SubPage { setup_events() { this.$wrapper.find('.publish-items').on('click', () => { - this.load_publishing_state(); this.publish_selected_items() .then(r => { frappe.msgprint('check'); @@ -828,7 +836,7 @@ erpnext.hub.Publish = class Publish extends SubPage { get_items_and_render() { if(hub.settings.sync_in_progress) { - this.load_publishing_state(); + this.show_publishing_state(); return; } @@ -868,7 +876,7 @@ erpnext.hub.Publish = class Publish extends SubPage { ); } - load_publishing_state() { + show_publishing_state() { this.$wrapper.html(get_empty_state( 'Publishing items ... You will be notified once published.' )); @@ -880,6 +888,8 @@ erpnext.hub.Publish = class Publish extends SubPage { items_to_publish.push($(this).attr("data-id")); }); + this.show_publishing_state(); + return frappe.call( 'erpnext.hub_node.publish_selected_items', { From d27b27f88a99b1e2c9f5b1d4616c166eaec3dba7 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Sun, 29 Jul 2018 16:52:16 +0530 Subject: [PATCH 032/154] Hub Seller Profile and Timeline - profile details fetched from server - timeline updates on publishing items --- erpnext/hub_node/__init__.py | 13 ++--- erpnext/public/js/hub/marketplace.js | 76 +++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 471f039ba2..6ac0aba626 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -93,22 +93,23 @@ def item_sync_preprocess(): }) if response: - # frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1) + frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1) return response else: return '' def item_sync_postprocess(obj): response = call_hub_method('update_activity_for_seller', { - 'hub_seller': frappe.db.get_value("Hub Settings", "Hub Settings", "company_email"), - 'name': obj["remote_id"], - 'status': obj["status"] + 'hub_seller': frappe.db.get_value('Hub Settings', 'Hub Settings', 'company_email'), + 'name': obj['remote_id'], + 'status': obj['status'], + 'stats': obj['stats'] }) if response: - frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0) + frappe.db.set_value('Hub Settings', 'Hub Settings', 'sync_in_progress', 0) else: - frappe.throw("Unable to update remote activity") + frappe.throw('Unable to update remote activity') @frappe.whitelist() def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 5261c3f554..22149562cf 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -724,9 +724,36 @@ erpnext.hub.Register = class Register extends SubPage { erpnext.hub.Profile = class Profile extends SubPage { make_wrapper() { super.make_wrapper(); + } - // Shorthand for profile data; - const p = hub.settings; + refresh() { + this.get_hub_seller_profile(this.keyword) + .then(profile => this.render(profile)); + } + + get_hub_seller_profile() { + return hub.call('get_hub_seller_profile', { hub_seller: hub.settings.company_email }); + } + + render(profile) { + const p = profile; + const content_by_log_type = this.get_content_by_log_type(); + + let activity_logs = (p.hub_seller_activity || []).sort((a, b) => { + return new Date(b.creation) - new Date(a.creation); + }); + + const timeline_items_html = activity_logs + .map(log => { + const stats = JSON.parse(log.stats); + const no_of_items = stats && stats.push_update || ''; + + const content = content_by_log_type[log.type]; + const message = content.get_message(no_of_items); + const icon = content.icon; + return this.get_timeline_log_item(log.pretty_date, message, icon); + }) + .join(''); const profile_html = `
          @@ -737,33 +764,58 @@ erpnext.hub.Profile = class Profile extends SubPage {
          - +
          -

          ${'title'}

          +

          ${p.company}

          -

          ${'where'}${'dot_spacer'}${'when'}

          -

          ${'rating_html'}${'rating_count'}

          +

          ${p.country}

          +

          ${p.site_name}


          - ${'description' ? - `${__('Description')} -

          ${'description'}

          - ` : __('No description') + ${'description' + ? `

          ${p.company_description}

          ` + : `

          __('No description')

          + +
          +
          + ${timeline_items_html} +
          +
          +
          `; this.$wrapper.html(profile_html); } - refresh() {} + get_timeline_log_item(pretty_date, message, icon) { + return `
          +
          + + ${pretty_date} ${message} +
          +
          `; + } - render() {} + get_content_by_log_type() { + return { + "Created": { + icon: 'octicon-heart', + get_message: () => 'Joined Marketplace' + }, + "Items Publish": { + icon: 'octicon-bookmark', + get_message: (no_of_items) => + `Published ${no_of_items} product${no_of_items > 1 ? 's' : ''} to Marketplace` + } + } + } } erpnext.hub.Publish = class Publish extends SubPage { From efbd2ee3f57d83f04f87b26a571b50a9759c520d Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 30 Jul 2018 09:17:14 +0530 Subject: [PATCH 033/154] [hub] Realtime Sync Progress - Show progress from the migration plan - Show products being synced - Disallow publishing in sync state --- .../doctype/hub_settings/hub_settings.json | 6 +- erpnext/public/js/hub/marketplace.js | 192 ++++++++++++------ erpnext/public/less/hub.less | 21 +- 3 files changed, 158 insertions(+), 61 deletions(-) diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.json b/erpnext/hub_node/doctype/hub_settings/hub_settings.json index a5338e9b3e..10bb959dff 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json @@ -373,7 +373,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enabled", + "depends_on": "", "fieldname": "publish_section", "fieldtype": "Section Break", "hidden": 0, @@ -675,8 +675,8 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-07-28 00:48:21.210413", - "modified_by": "cave@aperture.com", + "modified": "2018-07-30 08:01:09.024358", + "modified_by": "Administrator", "module": "Hub Node", "name": "Hub Settings", "name_case": "", diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 4c3e9ef7c9..28309f3caf 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -825,17 +825,44 @@ erpnext.hub.Profile = class Profile extends SubPage { erpnext.hub.Publish = class Publish extends SubPage { make_wrapper() { super.make_wrapper(); + this.items_to_publish = []; + this.unpublished_items = []; + this.fetched_items = []; + } + + refresh() { if(!hub.settings.sync_in_progress) { - this.make_publish_header(); + this.make_publish_ready_state(); } else { - this.show_publishing_state(); + this.make_publish_in_progress_state(); } } - make_publish_header() { - const title_html = `${__('Select Products to Publish')}`; + make_publish_ready_state() { + this.$wrapper.empty(); + this.$wrapper.append(this.get_publishing_header()); - const info = `

          ${__("Status decided by the 'Publish in Hub' field in Item.")}

          `; + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + this.search_value = keyword; + this.get_items_and_render(); + }, + placeholder: __('Search Items') + }); + + this.setup_publishing_events(); + + if(hub.settings.last_sync) { + this.show_message(`Last sync was ${hub.settings.last_sync}. + See your Published Products.`); + } + + this.get_items_and_render(); + } + + get_publishing_header() { + const title_html = `${__('Select Products to Publish')}`; const subtitle_html = `

          ${__(`Only products with an image, description and category can be published. @@ -847,10 +874,7 @@ erpnext.hub.Publish = class Publish extends SubPage { `; - const select_all_button = ``; - const deselect_all_button = ``; - - const subpage_header = $(` + return $(`

          ${title_html} @@ -859,27 +883,12 @@ erpnext.hub.Publish = class Publish extends SubPage { ${publish_button_html}
          `); - - this.$wrapper.append(subpage_header); - - make_search_bar({ - wrapper: this.$wrapper, - on_search: keyword => { - this.search_value = keyword; - this.get_items_and_render(); - }, - placeholder: __('Search Items') - }); - - this.setup_events(); } - setup_events() { + setup_publishing_events() { this.$wrapper.find('.publish-items').on('click', () => { this.publish_selected_items() - .then(r => { - console.log(`${r.message} items will be published`); - }); + .then(this.refresh.bind(this)) }); this.$wrapper.on('click', '.hub-card', (e) => { @@ -903,33 +912,95 @@ erpnext.hub.Publish = class Publish extends SubPage { }); } - get_items_and_render() { - if(hub.settings.sync_in_progress) { - this.show_publishing_state(); - return; - } + show_message(message) { + const $message = $(`
          +

          + + ${message} + + +

          +
          `); - this.$wrapper.find('.hub-card-container').empty(); - this.get_valid_items() - .then(r => { - this.render(r.message); - }); + $message.find('.octicon-x').on('click', () => { + $message.remove(); + }); + + this.$wrapper.prepend($message); } - refresh() { - if (hub.settings.sync_in_progress) { - this.load_publishing_state(); + make_publish_in_progress_state() { + this.$wrapper.empty(); + + this.$wrapper.append(this.show_publish_progress()); + + const subtitle_html = `

          + ${__(`Only products with an image, description and category can be published. + Please update them if an item in your inventory does not appear.`)} +

          `; + + this.$wrapper.append(subtitle_html); + + // Show search list with only desctiption, and don't set any events + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + this.search_value = keyword; + this.get_items_and_render(); + }, + placeholder: __('Search Items') + }); + + this.get_items_and_render(); + } + + show_publish_progress() { + const items_to_publish = this.items_to_publish.length + ? this.items_to_publish + : JSON.parse(hub.settings.custom_data); + + const $publish_progress = $(`
          +

          ${__(`Syncing ${items_to_publish.length} Products`)}

          +
          +
          +
          + +
          `); + + const items_to_publish_container = $(get_item_card_container_html( + items_to_publish, '', get_local_item_card_html)); + + items_to_publish_container.find('.hub-card').addClass('active'); + + $publish_progress.append(items_to_publish_container); + + return $publish_progress; + } + + get_items_and_render(wrapper = this.$wrapper) { + wrapper.find('.results').remove(); + const items = this.get_valid_items(); + + if(!items.then) { + this.render(items, wrapper); } else { - this.get_items_and_render(); + items.then(r => { + this.fetched_items = r.message; + this.render(r.message, wrapper); + }); } } - render(items) { + render(items, wrapper) { const items_container = $(get_item_card_container_html(items, '', get_local_item_card_html)); - this.$wrapper.append(items_container); + items_container.addClass('results'); + wrapper.append(items_container); } get_valid_items() { + if(this.unpublished_items.length) { + return this.unpublished_items; + } return frappe.call( 'erpnext.hub_node.get_valid_items', { @@ -938,26 +1009,33 @@ erpnext.hub.Publish = class Publish extends SubPage { ); } - show_publishing_state() { - this.$wrapper.html(get_empty_state( - 'Publishing items ... You will be notified once published.' - )); - } - publish_selected_items() { - const items_to_publish = []; + const item_codes_to_publish = []; this.$wrapper.find('.hub-card.active').map(function () { - items_to_publish.push($(this).attr("data-id")); + item_codes_to_publish.push($(this).attr("data-id")); }); - this.show_publishing_state(); + this.unpublished_items = this.fetched_items.filter(item => { + return !item_codes_to_publish.includes(item.item_code); + }); - return frappe.call( - 'erpnext.hub_node.publish_selected_items', - { - items_to_publish: items_to_publish - } - ); + const items_to_publish = this.fetched_items.filter(item => { + return item_codes_to_publish.includes(item.item_code); + }); + this.items_to_publish = items_to_publish; + + return frappe.db.set_value("Hub Settings", "Hub Settings", { + custom_data: JSON.stringify(items_to_publish), + // sync_in_progress: 1 + }).then(() => { + hub.settings.sync_in_progress = 1; + }) + // .then(frappe.call( + // 'erpnext.hub_node.publish_selected_items', + // { + // items_to_publish: item_codes_to_publish + // } + // )); } } diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index a4321569fb..c610aad25f 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -31,14 +31,33 @@ body[data-route^="marketplace/"] { border-color: #59b81c; } + .progress-bar { + background-color: #89da28; + } + .subpage-title.flex { align-items: flex-start; justify-content: space-between; } + .subpage-message { + p { + padding: 10px 15px; + margin-top: 0px; + margin-bottom: 15px; + background-color: #f9fbf7; + justify-content: space-between; + } + + .octicon-x { + cursor: pointer; + } + } + .hub-card { - border: 1px solid @border-color; margin-bottom: 25px; + position: relative; + border: 1px solid @border-color; border-radius: 4px; overflow: hidden; cursor: pointer; From 46df72d55dfecc54b6e93e545e6e2f2b0b5d7b9a Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 30 Jul 2018 13:24:01 +0530 Subject: [PATCH 034/154] [hub] test progress trigger --- erpnext/hub_node/__init__.py | 17 ++++++---- .../doctype/hub_settings/hub_settings.json | 6 ++-- .../doctype/hub_settings/hub_settings.py | 27 ++++++++++++--- erpnext/public/js/hub/marketplace.js | 34 ++++++++++++++----- 4 files changed, 62 insertions(+), 22 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 6ac0aba626..61676ad7f7 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -73,14 +73,18 @@ def publish_selected_items(items_to_publish): if not len(items_to_publish): return - for item_code in items_to_publish: - frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) + # TODO: sync + # for item_code in items_to_publish: + # frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) + + # hub_settings = frappe.get_doc('Hub Settings') + # remote_id = item_sync_preprocess() + # hub_settings.sync(remote_id) + + # return remote_id hub_settings = frappe.get_doc('Hub Settings') - remote_id = item_sync_preprocess() - hub_settings.sync(remote_id) - - return remote_id + hub_settings.sync('TEST') def item_sync_preprocess(): # Call Hub to make a new activity @@ -108,6 +112,7 @@ def item_sync_postprocess(obj): if response: frappe.db.set_value('Hub Settings', 'Hub Settings', 'sync_in_progress', 0) + frappe.db.set_value('Hub Settings', 'Hub Settings', 'last_sync_datetime', frappe.utils.now()) else: frappe.throw('Unable to update remote activity') diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.json b/erpnext/hub_node/doctype/hub_settings/hub_settings.json index 10bb959dff..a0d8188d44 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json @@ -538,7 +538,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "publish", + "depends_on": "", "fieldname": "last_sync_datetime", "fieldtype": "Datetime", "hidden": 0, @@ -555,7 +555,7 @@ "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 1, + "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, @@ -675,7 +675,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-07-30 08:01:09.024358", + "modified": "2018-07-30 10:43:28.818498", "modified_by": "Administrator", "module": "Hub Node", "name": "Hub Settings", diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 66dd560388..727ad627c7 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -37,12 +37,31 @@ class HubSettings(Document): 'doctype': 'Data Migration Run', 'data_migration_plan': 'Hub Sync', 'data_migration_connector': 'Hub Connector', - 'remote_id': remote_id + 'remote_id': remote_id, + 'trigger_name': 'items-sync' }).insert() - self.sync_in_progress = 1 - # time.sleep(10) - doc.run() + time.sleep(2) + frappe.publish_realtime('items-sync', {"progress_percent": 20}) + print("=======================") + + time.sleep(2) + frappe.publish_realtime('items-sync', {"progress_percent": 40}) + print("=======================") + + time.sleep(1) + frappe.publish_realtime('items-sync', {"progress_percent": 80}) + print("=======================") + + time.sleep(2) + frappe.publish_realtime('items-sync', {"progress_percent": 100}) + print("=======================") + + frappe.db.set_value('Hub Settings', 'Hub Settings', 'last_sync_datetime', frappe.utils.now()) + + # TODO: sync + # self.sync_in_progress = 1 + # doc.run() else: frappe.throw("No remote ID specified") diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 28309f3caf..703da1f8ee 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -828,6 +828,21 @@ erpnext.hub.Publish = class Publish extends SubPage { this.items_to_publish = []; this.unpublished_items = []; this.fetched_items = []; + + frappe.realtime.on("items-sync", (data) => { + this.$wrapper.find('.progress-bar').css('width', data.progress_percent+'%'); + + if(data.progress_percent === 100) { + setTimeout(() => { + hub.settings.sync_in_progress = 0; + frappe.db.get_doc('Hub Settings') + .then(doc => { + hub.settings = doc; + this.refresh(); + }); + }, 500); + } + }); } refresh() { @@ -853,8 +868,8 @@ erpnext.hub.Publish = class Publish extends SubPage { this.setup_publishing_events(); - if(hub.settings.last_sync) { - this.show_message(`Last sync was ${hub.settings.last_sync}. + if(hub.settings.last_sync_datetime) { + this.show_message(`Last sync was ${hub.settings.last_sync_datetime}. See your Published Products.`); } @@ -962,7 +977,7 @@ erpnext.hub.Publish = class Publish extends SubPage { const $publish_progress = $(`

          ${__(`Syncing ${items_to_publish.length} Products`)}

          -
          +
          `); @@ -1026,16 +1041,17 @@ erpnext.hub.Publish = class Publish extends SubPage { return frappe.db.set_value("Hub Settings", "Hub Settings", { custom_data: JSON.stringify(items_to_publish), + // TODO: sync // sync_in_progress: 1 }).then(() => { hub.settings.sync_in_progress = 1; }) - // .then(frappe.call( - // 'erpnext.hub_node.publish_selected_items', - // { - // items_to_publish: item_codes_to_publish - // } - // )); + .then(frappe.call( + 'erpnext.hub_node.publish_selected_items', + { + items_to_publish: item_codes_to_publish + } + )); } } From 425356c21c4ddfdbb9a3bd1a3118ae3648ba7475 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 31 Jul 2018 10:45:53 +0530 Subject: [PATCH 035/154] [hub] remove test code --- erpnext/hub_node/__init__.py | 16 +++++------- .../doctype/hub_settings/hub_settings.py | 23 ++-------------- erpnext/public/js/hub/marketplace.js | 26 +++++++++++++------ 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 61676ad7f7..f3a00984af 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -73,18 +73,14 @@ def publish_selected_items(items_to_publish): if not len(items_to_publish): return - # TODO: sync - # for item_code in items_to_publish: - # frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - - # hub_settings = frappe.get_doc('Hub Settings') - # remote_id = item_sync_preprocess() - # hub_settings.sync(remote_id) - - # return remote_id + for item_code in items_to_publish: + frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) hub_settings = frappe.get_doc('Hub Settings') - hub_settings.sync('TEST') + remote_id = item_sync_preprocess() + hub_settings.sync(remote_id) + + return remote_id def item_sync_preprocess(): # Call Hub to make a new activity diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 727ad627c7..81264dd90e 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -41,27 +41,8 @@ class HubSettings(Document): 'trigger_name': 'items-sync' }).insert() - time.sleep(2) - frappe.publish_realtime('items-sync', {"progress_percent": 20}) - print("=======================") - - time.sleep(2) - frappe.publish_realtime('items-sync', {"progress_percent": 40}) - print("=======================") - - time.sleep(1) - frappe.publish_realtime('items-sync', {"progress_percent": 80}) - print("=======================") - - time.sleep(2) - frappe.publish_realtime('items-sync', {"progress_percent": 100}) - print("=======================") - - frappe.db.set_value('Hub Settings', 'Hub Settings', 'last_sync_datetime', frappe.utils.now()) - - # TODO: sync - # self.sync_in_progress = 1 - # doc.run() + self.sync_in_progress = 1 + doc.run() else: frappe.throw("No remote ID specified") diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 703da1f8ee..543a0b3dbb 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -832,7 +832,7 @@ erpnext.hub.Publish = class Publish extends SubPage { frappe.realtime.on("items-sync", (data) => { this.$wrapper.find('.progress-bar').css('width', data.progress_percent+'%'); - if(data.progress_percent === 100) { + if(data.progress_percent === 100 || data.progress_percent === '100') { setTimeout(() => { hub.settings.sync_in_progress = 0; frappe.db.get_doc('Hub Settings') @@ -1039,13 +1039,7 @@ erpnext.hub.Publish = class Publish extends SubPage { }); this.items_to_publish = items_to_publish; - return frappe.db.set_value("Hub Settings", "Hub Settings", { - custom_data: JSON.stringify(items_to_publish), - // TODO: sync - // sync_in_progress: 1 - }).then(() => { - hub.settings.sync_in_progress = 1; - }) + return this.set_sync(items_to_publish) .then(frappe.call( 'erpnext.hub_node.publish_selected_items', { @@ -1053,6 +1047,22 @@ erpnext.hub.Publish = class Publish extends SubPage { } )); } + + set_sync(items_to_publish) { + hub.settings.sync_in_progress = 1; + return frappe.db.set_value("Hub Settings", "Hub Settings", { + custom_data: JSON.stringify(items_to_publish), + sync_in_progress: 1 + }) + } + + reset_sync() { + hub.settings.sync_in_progress = 0; + return frappe.db.set_value("Hub Settings", "Hub Settings", { + custom_data: '', + sync_in_progress: 0 + }) + } } erpnext.hub.PublishedProducts = class PublishedProducts extends SubPage { From 2ae80fd000646bf31ad89c69770a1a6fcee13bdc Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 31 Jul 2018 12:02:20 +0530 Subject: [PATCH 036/154] wip --- erpnext/public/js/hub/marketplace.js | 45 ++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 28309f3caf..2c2a29d525 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -332,6 +332,11 @@ erpnext.hub.SearchPage = class SearchPage extends SubPage { } erpnext.hub.Item = class Item extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.setup_events(); + } + refresh() { this.show_skeleton(); this.hub_item_code = frappe.get_route()[2]; @@ -370,6 +375,29 @@ erpnext.hub.Item = class Item extends SubPage { this.$wrapper.html(skeleton); } + setup_events() { + this.$wrapper.on('click', '.btn-contact-seller', () => { + const d = new frappe.ui.Dialog({ + title: __('Send a message'), + fields: [ + { + fieldname: 'to', + fieldtype: 'Read Only', + label: __('To'), + default: this.item.company + }, + { + fieldtype: 'Text', + fieldname: 'message', + label: __('Message') + } + ] + }); + + d.show(); + }); + } + get_item(hub_item_code) { return hub.call('get_item_details', { hub_item_code }); } @@ -414,7 +442,7 @@ erpnext.hub.Item = class Item extends SubPage {
          -
          +

          ${title}

          ${where}${dot_spacer}${when}

          @@ -430,6 +458,11 @@ erpnext.hub.Item = class Item extends SubPage {
          ${edit_buttons_html}
          +
          + +
          @@ -438,11 +471,11 @@ erpnext.hub.Item = class Item extends SubPage {
          -
          - ${seller} -

          - Contact Seller -

          +
          + +
          From c1438c616921db379ac7c8257fdd73a99da3c5b4 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 31 Jul 2018 17:51:45 +0530 Subject: [PATCH 037/154] refactor(hub connection): read only connection - Seller activity in Activity Log - Move hub_url to site_config --- erpnext/hub_node/__init__.py | 112 ++++++------------ .../doctype/hub_settings/hub_settings.py | 33 ++---- 2 files changed, 46 insertions(+), 99 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index f3a00984af..88570e56cc 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe, requests, json from frappe.utils import now, nowdate, cint from frappe.utils.nestedset import get_root_of +from frappe.frappeclient import FrappeClient from frappe.contacts.doctype.contact.contact import get_default_contact @frappe.whitelist() @@ -16,7 +17,7 @@ def enable_hub(): @frappe.whitelist() def call_hub_method(method, params=None): - connection = get_client_connection() + connection = get_hub_connection() if type(params) == unicode: params = json.loads(params) @@ -28,22 +29,6 @@ def call_hub_method(method, params=None): response = connection.post_request(params) return response -@frappe.whitelist() -def get_list(doctype, start=0, limit=20, fields=["*"], filters="{}", order_by=None): - connection = get_client_connection() - filters = json.loads(filters) - - response = connection.get_list(doctype, - limit_start=start, limit_page_length=limit, - filters=filters, fields=['name']) - - # Bad, need child tables in response - listing = [] - for obj in response: - doc = connection.get_doc(doctype, obj['name']) - listing.append(doc) - - return listing #### LOCAL ITEMS @frappe.whitelist() @@ -76,34 +61,44 @@ def publish_selected_items(items_to_publish): for item_code in items_to_publish: frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - hub_settings = frappe.get_doc('Hub Settings') - remote_id = item_sync_preprocess() - hub_settings.sync(remote_id) - - return remote_id + try: + hub_settings = frappe.get_doc('Hub Settings') + item_sync_preprocess() + hub_settings.sync() + except Exception as e: + frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0) + frappe.throw(e) def item_sync_preprocess(): # Call Hub to make a new activity # and return an activity ID # that will be used as the remote ID for the Migration Run - response = call_hub_method('init_new_activity_for_seller', { - 'hub_seller': frappe.db.get_value("Hub Settings", "Hub Settings", "company_email"), - 'activity_type': 'Items Publish' + hub_seller = frappe.db.get_value("Hub Settings", "Hub Settings", "company_email") + + response = call_hub_method('add_hub_seller_activity', { + 'hub_seller': hub_seller, + 'activity_details': json.dumps({ + 'subject': 'Publishing items', + 'status': 'Success' + }) }) if response: frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1) return response else: - return '' + frappe.throw('Unable to update remote activity') -def item_sync_postprocess(obj): - response = call_hub_method('update_activity_for_seller', { - 'hub_seller': frappe.db.get_value('Hub Settings', 'Hub Settings', 'company_email'), - 'name': obj['remote_id'], - 'status': obj['status'], - 'stats': obj['stats'] +def item_sync_postprocess(sync_details): + hub_seller = frappe.db.get_value("Hub Settings", "Hub Settings", "company_email") + + response = call_hub_method('add_hub_seller_activity', { + 'hub_seller': hub_seller, + 'activity_details': json.dumps({ + 'subject': 'Publishing items:' + sync_details['status'], + 'content': json.dumps(sync_details['stats']) + }) }) if response: @@ -145,31 +140,6 @@ def update_wishlist_item(item_name, remove=0): hub_settings.custom_data = item_names_str hub_settings.save() -@frappe.whitelist() -def get_meta(doctype): - connection = get_client_connection() - meta = connection.get_doc('DocType', doctype) - categories = connection.get_list('Hub Category', - limit_start=0, limit_page_length=300, - filters={}, fields=['name']) - - categories = [d.get('name') for d in categories] - return { - 'meta': meta, - 'companies': connection.get_list('Hub Company', - limit_start=0, limit_page_length=300, - filters={}, fields=['name']), - 'categories': categories - } - -@frappe.whitelist() -def get_categories(parent='All Categories'): - # get categories info with parent category and stuff - connection = get_client_connection() - categories = connection.get_list('Hub Category', filters={'parent_hub_category': parent}) - - response = [{'value': c.get('name'), 'expandable': c.get('is_group')} for c in categories] - return response @frappe.whitelist() def update_category(hub_item_code, category): @@ -188,28 +158,14 @@ def update_category(hub_item_code, category): return response -@frappe.whitelist() -def get_details(hub_sync_id=None, doctype='Hub Item'): - if not hub_sync_id: - return - connection = get_client_connection() - details = connection.get_doc(doctype, hub_sync_id) - reviews = details.get('reviews') - if reviews and len(reviews): - for r in reviews: - r.setdefault('pretty_date', frappe.utils.pretty_date(r.get('modified'))) - details.setdefault('reviews', reviews) - return details - -def get_client_connection(): - # frappeclient connection - hub_connection = get_hub_connection() - return hub_connection.connection - def get_hub_connection(): - hub_connector = frappe.get_doc( - 'Data Migration Connector', 'Hub Connector') - hub_connection = hub_connector.get_connection() + if frappe.db.exists('Data Migration Connector', 'Hub Connector'): + hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector') + hub_connection = hub_connector.get_connection() + return hub_connection.connection + + # read-only connection + hub_connection = FrappeClient(frappe.conf.hub_url) return hub_connection def make_opportunity(buyer_name, email_id): diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 81264dd90e..bfb332022a 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -10,11 +10,6 @@ from frappe import _ from erpnext.utilities.product import get_price, get_qty_in_stock from six import string_types -# hub_url = "https://hubmarket.org" -# hub_url = "http://159.89.175.122" -# hub_url = "http://erpnext.hub:8001" -hub_url = "http://hub.market:8000" - class HubSetupError(frappe.ValidationError): pass class HubSettings(Document): @@ -26,25 +21,21 @@ class HubSettings(Document): frappe.throw(_("Please select a Price List to publish pricing")) def get_hub_url(self): - return hub_url + return frappe.conf.hub_url - def sync(self, remote_id): + def sync(self): """Create and execute Data Migration Run for Hub Sync plan""" frappe.has_permission('Hub Settings', throw=True) - if remote_id: - doc = frappe.get_doc({ - 'doctype': 'Data Migration Run', - 'data_migration_plan': 'Hub Sync', - 'data_migration_connector': 'Hub Connector', - 'remote_id': remote_id, - 'trigger_name': 'items-sync' - }).insert() + doc = frappe.get_doc({ + 'doctype': 'Data Migration Run', + 'data_migration_plan': 'Hub Sync', + 'data_migration_connector': 'Hub Connector', + 'trigger_name': 'items-sync' + }).insert() - self.sync_in_progress = 1 - doc.run() - else: - frappe.throw("No remote ID specified") + self.sync_in_progress = 1 + doc.run() def register(self): """ Create a User on hub.erpnext.org and return username/password """ @@ -56,7 +47,7 @@ class HubSettings(Document): data = { 'profile': self.as_json() } - post_url = hub_url + '/api/method/hub.hub.api.register' + post_url = self.get_hub_url() + '/api/method/hub.hub.api.register' response = requests.post(post_url, data=data, headers = {'accept': 'application/json'}) @@ -99,7 +90,7 @@ class HubSettings(Document): 'doctype': 'Data Migration Connector', 'connector_type': 'Frappe', 'connector_name': 'Hub Connector', - 'hostname': hub_url, + 'hostname': self.get_hub_url(), 'username': message['email'], 'password': message['password'] }).insert() From 1ff25279bfe4f2b4842efbf9fa180599130595f1 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 31 Jul 2018 17:59:03 +0530 Subject: [PATCH 038/154] Remove old hub styles --- erpnext/public/less/hub.less | 166 ----------------------------------- 1 file changed, 166 deletions(-) diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index c610aad25f..3809c231a8 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -218,169 +218,3 @@ body[data-route^="marketplace/"] { margin-top: calc(30vh); } } - -body[data-route^="Hub/"] { - .hub-icon { - width: 40px; - height: 40px; - } - - .img-wrapper { - border: 1px solid #d1d8dd; - border-radius: 3px; - padding: 12px; - overflow: hidden; - text-align: center; - white-space: nowrap; - - .helper { - height: 100%; - display: inline-block; - vertical-align: middle; - } - } - - .tree { - margin: 10px 0px; - padding: 0px; - height: 100%; - position: relative; - } - - .tree.with-skeleton.opened::before { - left: 9px; - top: 14px; - height: calc(~"100% - 32px"); - } - - .list-header-icon { - width: 72px; - border-radius: 4px; - flex-shrink: 0; - margin: 10px; - padding: 1px; - border: 1px solid @border-color; - height: 72px; - display: flex; - align-items: center; - justify-content: center; - - img { - border-radius: 4px; - } - } - - .star-icon.fa-star { - color: @indicator-orange; - } - - .octicon-heart.liked { - color: @indicator-red; - } - - .margin-vertical-10 { - margin: 10px 0px; - } - - .margin-vertical-15 { - margin: 15px 0px; - } - - .frappe-list .result { - min-height: 100px; - } - - .frappe-control[data-fieldtype="Attach Image"] { - width: 140px; - height: 180px; - margin-top: 20px; - } - - .frappe-control[data-fieldtype="Attach Image"] .form-group { - display: none; - } - - .frappe-control[data-fieldtype="Attach Image"] .clearfix { - display: none; - } - - .missing-image { - display: block; - position: relative; - border-radius: 4px; - border: 1px solid #d1d8dd; - border-radius: 6px; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - } - .missing-image .octicon { - position: relative; - top: 50%; - transform: translate(0px, -50%); - -webkit-transform: translate(0px, -50%); - } - .attach-image-display { - display: block; - position: relative; - border-radius: 4px; - } - .img-container { - height: 100%; - width: 100%; - padding: 2px; - display: flex; - align-items: center; - justify-content: center; - position: relative; - border: 1px solid #d1d8dd; - border-radius: 6px; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - } - .img-overlay { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - width: 100%; - height: 100%; - color: #777777; - background-color: rgba(255, 255, 255, 0.7); - opacity: 0; - } - .img-overlay:hover { - opacity: 1; - cursor: pointer; - } -} - -.image-view-container { - .image-view-body { - &:hover .like-button { - opacity: 0.7; - } - } - - .like-button { - bottom: 10px !important; - left: 10px !important; - width: 36px; - height: 36px; - opacity: 0; - font-size: 16px; - color: @text-color; - position: absolute; - - // show zoom button on mobile devices - @media (max-width: @screen-xs) { - opacity: 0.5 - } - } - - .image-view-body:hover .like-button { - opacity: 0.7; - } -} - -.rating-area .star-icon { - cursor: pointer; - font-size: 15px; -} From 300c07c3d9ff1b1290eda5113267b5250fb001af Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 31 Jul 2018 18:02:13 +0530 Subject: [PATCH 039/154] Use hub.call for categories and favourites --- erpnext/public/js/hub/marketplace.js | 33 +++++++--------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 83c13a078d..86ab60b5c0 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -69,9 +69,9 @@ erpnext.hub.Marketplace = class Marketplace { } make_sidebar_categories() { - frappe.call('erpnext.hub_node.get_categories') - .then(r => { - const categories = r.message.map(d => d.value).sort(); + hub.call('erpnext.hub_node.get_categories') + .then(categories => { + const categories = r.message.map(d => d.name); const sidebar_items = [ `
        • ${__('Category')} @@ -258,13 +258,13 @@ erpnext.hub.Home = class Home extends SubPage { erpnext.hub.Favourites = class Favourites extends SubPage { refresh() { this.get_favourites() - .then(r => { - this.render(r.message); + .then(items => { + this.render(items); }); } get_favourites() { - return frappe.call('erpnext.hub_node.get_item_favourites'); + return hub.call('get_item_favourites'); } render(items) { @@ -1072,29 +1072,12 @@ erpnext.hub.Publish = class Publish extends SubPage { }); this.items_to_publish = items_to_publish; - return this.set_sync(items_to_publish) - .then(frappe.call( + return frappe.call( 'erpnext.hub_node.publish_selected_items', { items_to_publish: item_codes_to_publish } - )); - } - - set_sync(items_to_publish) { - hub.settings.sync_in_progress = 1; - return frappe.db.set_value("Hub Settings", "Hub Settings", { - custom_data: JSON.stringify(items_to_publish), - sync_in_progress: 1 - }) - } - - reset_sync() { - hub.settings.sync_in_progress = 0; - return frappe.db.set_value("Hub Settings", "Hub Settings", { - custom_data: '', - sync_in_progress: 0 - }) + ) } } From 057839298c581f63ce2891e4f249e75ba530421b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 31 Jul 2018 19:06:11 +0530 Subject: [PATCH 040/154] fix get_category call --- erpnext/public/js/hub/marketplace.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 86ab60b5c0..3d4611ad8c 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -69,9 +69,10 @@ erpnext.hub.Marketplace = class Marketplace { } make_sidebar_categories() { - hub.call('erpnext.hub_node.get_categories') + hub.call('get_categories') .then(categories => { - const categories = r.message.map(d => d.name); + categories = categories.map(d => d.name); + const sidebar_items = [ `
        • ${__('Category')} @@ -902,7 +903,7 @@ erpnext.hub.Publish = class Publish extends SubPage { this.setup_publishing_events(); if(hub.settings.last_sync_datetime) { - this.show_message(`Last sync was ${hub.settings.last_sync_datetime}. + this.show_message(`Last sync was ${comment_when(hub.settings.last_sync_datetime)}. See your Published Products.`); } From a12076e0fd8dfd0e4d35802684bbd837ed2246b0 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 31 Jul 2018 23:06:00 +0530 Subject: [PATCH 041/154] Convert image to base64 before sending Item to sync --- .../item_to_hub_item/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py index e69de29bb2..9445e3a8dc 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py @@ -0,0 +1,19 @@ +import io, base64, urllib, os + +def pre_process(doc): + + file_path = doc.image + file_name = os.path.basename(file_path) + + if file_path.startswith('http'): + url = file_path + file_path = os.path.join('/tmp', file_name) + urllib.urlretrieve(url, file_path) + + with io.open(file_path, 'rb') as f: + doc.image = base64.b64encode(f.read()) + + doc.image_file_name = file_name + + return doc + From c0a22a689e1330d9f5733dfbd4e4d84c6ca4f6ca Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 1 Aug 2018 13:35:19 +0530 Subject: [PATCH 042/154] Item Menu dropdown --- erpnext/public/js/hub/marketplace.js | 12 +++++++++--- erpnext/public/less/hub.less | 6 +++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 3d4611ad8c..31cf5daf48 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -460,9 +460,15 @@ erpnext.hub.Item = class Item extends SubPage { ${edit_buttons_html}
        • - +
          diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 3809c231a8..c7277f28d9 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -106,7 +106,7 @@ body[data-route^="marketplace/"] { top: 0; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.1); + background-color: rgba(0, 0, 0, 0.05); } .hub-card-overlay-body { @@ -217,4 +217,8 @@ body[data-route^="marketplace/"] { .hub-item-review-container { margin-top: calc(30vh); } + + .hub-item-dropdown { + margin-top: 20px; + } } From 709a4a3f4e2fc059691d89306946c8df350d0daa Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 1 Aug 2018 14:09:07 +0530 Subject: [PATCH 043/154] Break marketplace.js into multiple files --- erpnext/public/build.json | 3 + erpnext/public/js/hub/helpers.js | 143 +++ erpnext/public/js/hub/hub_call.js | 44 + erpnext/public/js/hub/hub_factory.js | 86 +- erpnext/public/js/hub/hub_form.js | 493 ------- erpnext/public/js/hub/hub_listing.js | 802 ------------ erpnext/public/js/hub/marketplace.js | 1132 +---------------- erpnext/public/js/hub/pages/base_page.js | 35 + erpnext/public/js/hub/pages/category.js | 27 + erpnext/public/js/hub/pages/favourites.js | 21 + erpnext/public/js/hub/pages/home.js | 41 + erpnext/public/js/hub/pages/item.js | 327 +++++ erpnext/public/js/hub/pages/not_found.js | 10 + erpnext/public/js/hub/pages/profile.js | 98 ++ erpnext/public/js/hub/pages/publish.js | 228 ++++ .../public/js/hub/pages/published_products.js | 23 + erpnext/public/js/hub/pages/register.js | 110 ++ erpnext/public/js/hub/pages/search.js | 34 + 18 files changed, 1162 insertions(+), 2495 deletions(-) create mode 100644 erpnext/public/js/hub/helpers.js create mode 100644 erpnext/public/js/hub/hub_call.js delete mode 100644 erpnext/public/js/hub/hub_form.js delete mode 100644 erpnext/public/js/hub/hub_listing.js create mode 100644 erpnext/public/js/hub/pages/base_page.js create mode 100644 erpnext/public/js/hub/pages/category.js create mode 100644 erpnext/public/js/hub/pages/favourites.js create mode 100644 erpnext/public/js/hub/pages/home.js create mode 100644 erpnext/public/js/hub/pages/item.js create mode 100644 erpnext/public/js/hub/pages/not_found.js create mode 100644 erpnext/public/js/hub/pages/profile.js create mode 100644 erpnext/public/js/hub/pages/publish.js create mode 100644 erpnext/public/js/hub/pages/published_products.js create mode 100644 erpnext/public/js/hub/pages/register.js create mode 100644 erpnext/public/js/hub/pages/search.js diff --git a/erpnext/public/build.json b/erpnext/public/build.json index ed4ebababe..7bcf99bd2f 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -7,6 +7,9 @@ "public/js/website_utils.js", "public/js/shopping_cart.js" ], + "js/marketplace.min.js": [ + "public/js/hub/marketplace.js" + ], "js/erpnext.min.js": [ "public/js/conf.js", "public/js/utils.js", diff --git a/erpnext/public/js/hub/helpers.js b/erpnext/public/js/hub/helpers.js new file mode 100644 index 0000000000..22e35c3d89 --- /dev/null +++ b/erpnext/public/js/hub/helpers.js @@ -0,0 +1,143 @@ +function get_empty_state(message, action) { + return `
          +

          ${message}

          + ${action ? `

          ${action}

          `: ''} +
          `; +} + +function get_item_card_container_html(items, title='', get_item_html = get_item_card_html) { + const items_html = (items || []).map(item => get_item_html(item)).join(''); + const title_html = title + ? `
          + ${title} +
          ` + : ''; + + const html = `
          + ${title_html} + ${items_html} +
          `; + + return html; +} + +function get_item_card_html(item) { + const item_name = item.item_name || item.name; + const title = strip_html(item_name); + const img_url = item.image; + const company_name = item.company; + + // Subtitle + let subtitle = [comment_when(item.creation)]; + const rating = item.average_rating; + if (rating > 0) { + subtitle.push(rating + ``) + } + subtitle.push(company_name); + + let dot_spacer = ''; + subtitle = subtitle.join(dot_spacer); + + const item_html = ` +
          +
          +
          +
          ${title}
          +
          ${subtitle}
          +
          +
          + +
          +
          +
          +
          + `; + + return item_html; +} + +function get_local_item_card_html(item) { + const item_name = item.item_name || item.name; + const title = strip_html(item_name); + const img_url = item.image; + const company_name = item.company; + + const is_active = item.publish_in_hub; + const id = item.hub_item_code || item.item_code; + + // Subtitle + let subtitle = [comment_when(item.creation)]; + const rating = item.average_rating; + if (rating > 0) { + subtitle.push(rating + ``) + } + subtitle.push(company_name); + + let dot_spacer = ''; + subtitle = subtitle.join(dot_spacer); + + const edit_item_button = `
          + +
          `; + + const item_html = ` +
          +
          +
          +
          ${title}
          +
          ${subtitle}
          + +
          +
          + +
          +
          + ${edit_item_button} +
          +
          +
          +
          +
          + `; + + return item_html; +} + + +function get_rating_html(rating) { + let rating_html = ``; + for (var i = 0; i < 5; i++) { + let star_class = 'fa-star'; + if (i >= rating) star_class = 'fa-star-o'; + rating_html += ``; + } + return rating_html; +} + +function make_search_bar({wrapper, on_search, placeholder = __('Search for anything')}) { + const $search = $(` +
          + +
          ` + ); + wrapper.append($search); + const $search_input = $search.find('input'); + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + const search_value = $search_input.val(); + on_search(search_value); + } + }, 300)); +} + +export { + get_empty_state, + get_item_card_container_html, + get_item_card_html, + get_local_item_card_html, + get_rating_html, + make_search_bar, +} \ No newline at end of file diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js new file mode 100644 index 0000000000..e0fead3e50 --- /dev/null +++ b/erpnext/public/js/hub/hub_call.js @@ -0,0 +1,44 @@ +frappe.provide('hub'); +frappe.provide('erpnext.hub'); + +erpnext.hub.cache = {}; +hub.call = function call_hub_method(method, args={}) { + return new Promise((resolve, reject) => { + + // cache + const key = method + JSON.stringify(args); + if (erpnext.hub.cache[key]) { + resolve(erpnext.hub.cache[key]); + } + + // cache invalidation after 5 minutes + const timeout = 5 * 60 * 1000; + + setTimeout(() => { + delete erpnext.hub.cache[key]; + }, timeout); + + frappe.call({ + method: 'erpnext.hub_node.call_hub_method', + args: { + method, + params: args + } + }) + .then(r => { + if (r.message) { + if (r.message.error) { + frappe.throw({ + title: __('Marketplace Error'), + message: r.message.error + }); + } + + erpnext.hub.cache[key] = r.message; + resolve(r.message) + } + reject(r) + }) + .fail(reject) + }); +} diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js index c94edf4e4d..b073720430 100644 --- a/erpnext/public/js/hub/hub_factory.js +++ b/erpnext/public/js/hub/hub_factory.js @@ -1,9 +1,7 @@ -frappe.provide('erpnext.hub.pages'); +frappe.provide('erpnext.hub'); frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views.Factory { show() { - const page_name = frappe.get_route_str(); - if (frappe.pages.marketplace) { frappe.container.change_to('marketplace'); erpnext.hub.marketplace.refresh(); @@ -14,7 +12,7 @@ frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views. make(page_name) { const assets = [ - '/assets/erpnext/js/hub/marketplace.js' + '/assets/js/marketplace.min.js' ]; frappe.require(assets, () => { @@ -24,83 +22,3 @@ frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views. }); } } - -frappe.views.HubFactory = class HubFactory extends frappe.views.Factory { - - make(route) { - const page_name = frappe.get_route_str(); - const page = route[1]; - - const assets = { - 'List': [ - '/assets/erpnext/js/hub/hub_listing.js', - ], - 'Form': [ - '/assets/erpnext/js/hub/hub_form.js' - ] - }; - frappe.model.with_doc('Hub Settings', 'Hub Settings', () => { - this.hub_settings = frappe.get_doc('Hub Settings'); - - if (!erpnext.hub.pages[page_name]) { - if(!frappe.is_online()) { - this.render_offline_card(); - return; - } - if (!route[2]) { - frappe.require(assets['List'], () => { - if(page === 'Favourites') { - erpnext.hub.pages[page_name] = new erpnext.hub['Favourites']({ - parent: this.make_page(true, page_name), - hub_settings: this.hub_settings - }); - } else { - erpnext.hub.pages[page_name] = new erpnext.hub[page+'Listing']({ - parent: this.make_page(true, page_name), - hub_settings: this.hub_settings - }); - } - }); - } else if (!route[3]){ - frappe.require(assets['Form'], () => { - erpnext.hub.pages[page_name] = new erpnext.hub[page+'Page']({ - unique_id: route[2], - doctype: route[2], - parent: this.make_page(true, page_name), - hub_settings: this.hub_settings - }); - }); - } else { - frappe.require(assets['List'], () => { - frappe.route_options = {}; - frappe.route_options["company_name"] = route[2] - erpnext.hub.pages[page_name] = new erpnext.hub['ItemListing']({ - parent: this.make_page(true, page_name), - hub_settings: this.hub_settings - }); - }); - } - window.hub_page = erpnext.hub.pages[page_name]; - } else { - frappe.container.change_to(page_name); - window.hub_page = erpnext.hub.pages[page_name]; - } - }); - } - - render_offline_card() { - let html = `
          -
          - ${'Failed to connect'} -
          -

          ${ __("Please check your network connection.") }

          - -
          `; - - let page = $('#body_div'); - page.append(html); - - return; - } -} diff --git a/erpnext/public/js/hub/hub_form.js b/erpnext/public/js/hub/hub_form.js deleted file mode 100644 index 9287e6d54f..0000000000 --- a/erpnext/public/js/hub/hub_form.js +++ /dev/null @@ -1,493 +0,0 @@ -frappe.provide('erpnext.hub'); - -erpnext.hub.HubDetailsPage = class HubDetailsPage extends frappe.views.BaseList { - setup_defaults() { - super.setup_defaults(); - this.method = 'erpnext.hub_node.get_details'; - const route = frappe.get_route(); - // this.page_name = route[2]; - } - - setup_fields() { - return this.get_meta() - .then(r => { - this.meta = r.message.meta || this.meta; - this.categories = r.message.categories || []; - this.bootstrap_data(r.message); - - this.getFormFields(); - }); - } - - bootstrap_data() { } - - get_meta() { - return new Promise(resolve => - frappe.call('erpnext.hub_node.get_meta', {doctype: 'Hub ' + this.doctype}, resolve)); - } - - - set_breadcrumbs() { - frappe.breadcrumbs.add({ - label: __('Hub'), - route: '#Hub/' + this.doctype, - type: 'Custom' - }); - } - - setup_side_bar() { - this.sidebar = new frappe.ui.Sidebar({ - wrapper: this.$page.find('.layout-side-section'), - css_class: 'hub-form-sidebar' - }); - } - - setup_filter_area() { } - - setup_sort_selector() { } - - // let category = this.quick_view.get_values().hub_category; - // return new Promise((resolve, reject) => { - // frappe.call({ - // method: 'erpnext.hub_node.update_category', - // args: { - // hub_item_code: values.hub_item_code, - // category: category, - // }, - // callback: (r) => { - // resolve(); - // }, - // freeze: true - // }).fail(reject); - // }); - - get_timeline() { - return `
          -
          -
          -
          - -
          -
          -
          `; - } - - get_footer() { - return ``; - } - - get_args() { - return { - hub_sync_id: this.unique_id, - doctype: 'Hub ' + this.doctype - }; - } - - prepare_data(r) { - this.data = r.message; - } - - update_data(r) { - this.data = r.message; - } - - render() { - const image_html = this.data[this.image_field_name] ? - ` - ` : - `
          ${frappe.get_abbr(this.page_title)}
          `; - - this.sidebar.remove_item('image'); - this.sidebar.add_item({ - name: 'image', - label: image_html - }); - - if(!this.form) { - let fields = this.formFields; - this.form = new frappe.ui.FieldGroup({ - parent: this.$result, - fields - }); - this.form.make(); - } - - if(this.data.hub_category) { - this.form.fields_dict.set_category.hide(); - } - - this.form.set_values(this.data); - this.$result.show(); - - this.$timelineList && this.$timelineList.empty(); - if(this.data.reviews && this.data.reviews.length) { - this.data.reviews.map(review => { - this.addReviewToTimeline(review); - }) - } - - this.postRender() - } - - postRender() {} - - attachFooter() { - let footerHtml = ``; - - let parent = $('
          ').appendTo(this.page.main.parent()); - this.$footer = $(footerHtml).appendTo(parent); - } - - attachTimeline() { - let timelineHtml = `
          -
          -
          -
          - -
          -
          -
          `; - - let parent = this.$footer.find(".form-comments"); - this.$timeline = $(timelineHtml).appendTo(parent); - - this.$timelineList = this.$timeline.find(".timeline-items"); - } - - attachReviewArea() { - this.comment_area = new frappe.ui.ReviewArea({ - parent: this.$footer.find('.timeline-head'), - mentions: [], - on_submit: (val) => { - val.user = frappe.session.user; - val.username = frappe.session.user_fullname; - frappe.call({ - method: 'erpnext.hub_node.send_review', - args: { - hub_item_code: this.data.hub_item_code, - review: val - }, - callback: (r) => { - this.refresh(); - this.comment_area.reset(); - }, - freeze: true - }); - } - }); - } - - addReviewToTimeline(data) { - let username = data.username || data.user || __("Anonymous") - let imageHtml = data.user_image - ? `
          ` - : `
          ${frappe.get_abbr(username)}
          ` - - let editHtml = data.own - ? ` -
          - - ${'data.edit'} - -
          ` - : ''; - - let ratingHtml = ''; - - for(var i = 0; i < 5; i++) { - let starIcon = 'fa-star-o' - if(i < data.rating) { - starIcon = 'fa-star'; - } - ratingHtml += ``; - } - - $(this.getTimelineItem(data, imageHtml, editHtml, ratingHtml)) - .appendTo(this.$timelineList); - } - - getTimelineItem(data, imageHtml, editHtml, ratingHtml) { - return `
          - - -
          -
          -
          ${editHtml}
          - -
          - - ${imageHtml} - - -
          - - - ${data.username} - - - - - - - - - ${''} - -
          -
          -
          -
          -

          - ${data.subject} -

          - -
          - -

          - ${ratingHtml} -

          - -
          -

          - ${data.content} -

          -
          -
          -
          -
          -
          `; - } - - prepareFormFields(fields, fieldnames) { - return fields - .filter(field => fieldnames.includes(field.fieldname)) - .map(field => { - let { - label, - fieldname, - fieldtype, - } = field; - let read_only = 1; - return { - label, - fieldname, - fieldtype, - read_only, - }; - }); - } -}; - -erpnext.hub.ItemPage = class ItemPage extends erpnext.hub.HubDetailsPage { - constructor(opts) { - super(opts); - - this.show(); - } - - setup_defaults() { - super.setup_defaults(); - this.doctype = 'Item'; - this.image_field_name = 'image'; - } - - setup_page_head() { - super.setup_page_head(); - this.set_primary_action(); - } - - setup_side_bar() { - super.setup_side_bar(); - this.attachFooter(); - this.attachTimeline(); - this.attachReviewArea(); - } - - set_primary_action() { - let item = this.data; - this.page.set_primary_action(__('Request a Quote'), () => { - this.show_rfq_modal() - .then(values => { - item.item_code = values.item_code; - delete values.item_code; - - const supplier = values; - return [item, supplier]; - }) - .then(([item, supplier]) => { - return this.make_rfq(item, supplier, this.page.btn_primary); - }) - .then(r => { - console.log(r); - if (r.message && r.message.rfq) { - this.page.btn_primary.addClass('disabled').html(` ${__('Quote Requested')}`); - } else { - throw r; - } - }) - .catch((e) => { - console.log(e); //eslint-disable-line - }); - }, 'octicon octicon-plus'); - } - - prepare_data(r) { - super.prepare_data(r); - this.page.set_title(this.data["item_name"]); - } - - make_rfq(item, supplier, btn) { - console.log(supplier); - return new Promise((resolve, reject) => { - frappe.call({ - method: 'erpnext.hub_node.make_rfq_and_send_opportunity', - args: { item, supplier }, - callback: resolve, - btn, - }).fail(reject); - }); - } - - postRender() { - this.categoryDialog = new frappe.ui.Dialog({ - title: __('Suggest Category'), - fields: [ - { - label: __('Category'), - fieldname: 'category', - fieldtype: 'Autocomplete', - options: this.categories, - reqd: 1 - } - ], - primary_action_label: __("Send"), - primary_action: () => { - let values = this.categoryDialog.get_values(); - frappe.call({ - method: 'erpnext.hub_node.update_category', - args: { - hub_item_code: this.data.hub_item_code, - category: values.category - }, - callback: () => { - this.categoryDialog.hide(); - this.refresh(); - }, - freeze: true - }).fail(() => {}); - } - }); - } - - getFormFields() { - let colOneFieldnames = ['item_name', 'item_code', 'description']; - let colTwoFieldnames = ['seller', 'company_name', 'country']; - let colOneFields = this.prepareFormFields(this.meta.fields, colOneFieldnames); - let colTwoFields = this.prepareFormFields(this.meta.fields, colTwoFieldnames); - - let miscFields = [ - { - label: __('Category'), - fieldname: 'hub_category', - fieldtype: 'Data', - read_only: 1 - }, - - { - label: __('Suggest Category?'), - fieldname: 'set_category', - fieldtype: 'Button', - click: () => { - this.categoryDialog.show(); - } - }, - - { - fieldname: 'cb1', - fieldtype: 'Column Break' - } - ]; - this.formFields = colOneFields.concat(miscFields, colTwoFields); - } - - show_rfq_modal() { - let item = this.data; - return new Promise(res => { - let fields = [ - { label: __('Item Code'), fieldtype: 'Data', fieldname: 'item_code', default: item.item_code }, - { fieldtype: 'Column Break' }, - { label: __('Item Group'), fieldtype: 'Link', fieldname: 'item_group', default: item.item_group }, - { label: __('Supplier Details'), fieldtype: 'Section Break' }, - { label: __('Supplier Name'), fieldtype: 'Data', fieldname: 'supplier_name', default: item.company_name }, - { label: __('Supplier Email'), fieldtype: 'Data', fieldname: 'supplier_email', default: item.seller }, - { fieldtype: 'Column Break' }, - { label: __('Supplier Group'), fieldname: 'supplier_group', - fieldtype: 'Link', options: 'Supplier Group' } - ]; - fields = fields.map(f => { f.reqd = 1; return f; }); - - const d = new frappe.ui.Dialog({ - title: __('Request for Quotation'), - fields: fields, - primary_action_label: __('Send'), - primary_action: (values) => { - res(values); - d.hide(); - } - }); - - d.show(); - }); - } -} - -erpnext.hub.CompanyPage = class CompanyPage extends erpnext.hub.HubDetailsPage { - constructor(opts) { - super(opts); - this.show(); - } - - setup_defaults() { - super.setup_defaults(); - this.doctype = 'Company'; - this.image_field_name = 'company_logo'; - } - - prepare_data(r) { - super.prepare_data(r); - this.page.set_title(this.data["company_name"]); - } - - getFormFields() { - let fieldnames = ['company_name', 'description', 'route', 'country', 'seller', 'site_name'];; - this.formFields = this.prepareFormFields(this.meta.fields, fieldnames); - } -} diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js deleted file mode 100644 index 368c723e5b..0000000000 --- a/erpnext/public/js/hub/hub_listing.js +++ /dev/null @@ -1,802 +0,0 @@ - -erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList { - setup_defaults() { - super.setup_defaults(); - this.page_title = __(''); - this.method = 'erpnext.hub_node.get_list'; - - this.cache = {}; - - const route = frappe.get_route(); - this.page_name = route[1]; - - this.menu_items = this.menu_items.concat(this.get_menu_items()); - - this.imageFieldName = 'image'; - - this.show_filters = 0; - } - - set_title() { - const title = this.page_title; - let iconHtml = ``; - let titleHtml = `${title}`; - this.page.set_title(titleHtml, '', false, title); - } - - setup_fields() { - return this.get_meta() - .then(r => { - this.meta = r.message.meta || this.meta; - frappe.model.sync(this.meta); - this.bootstrap_data(r.message); - - this.prepareFormFields(); - }); - } - - setup_filter_area() { } - - get_meta() { - return new Promise(resolve => - frappe.call('erpnext.hub_node.get_meta', { doctype: this.doctype }, resolve)); - } - - set_breadcrumbs() { } - - prepareFormFields() { } - - bootstrap_data() { } - - get_menu_items() { - const items = [ - { - label: __('Hub Settings'), - action: () => frappe.set_route('Form', 'Hub Settings'), - standard: true - }, - { - label: __('Favourites'), - action: () => frappe.set_route('Hub', 'Favourites'), - standard: true - } - ]; - - return items; - } - - setup_side_bar() { - this.sidebar = new frappe.ui.Sidebar({ - wrapper: this.page.wrapper.find('.layout-side-section'), - css_class: 'hub-sidebar' - }); - } - - setup_sort_selector() { - // this.sort_selector = new frappe.ui.SortSelector({ - // parent: this.filter_area.$filter_list_wrapper, - // doctype: this.doctype, - // args: this.order_by, - // onchange: () => this.refresh(true) - // }); - } - - setup_view() { - if (frappe.route_options) { - const filters = []; - for (let field in frappe.route_options) { - var value = frappe.route_options[field]; - this.page.fields_dict[field].set_value(value); - } - } - - const $hub_search = $(` -
          - -
          ` - ); - this.$frappe_list.prepend($hub_search); - const $search_input = $hub_search.find('input'); - - $search_input.on('keydown', frappe.utils.debounce((e) => { - if (e.which === frappe.ui.keyCode.ENTER) { - this.search_value = $search_input.val(); - this.refresh(); - } - }, 300)); - } - - get_args() { - return { - doctype: this.doctype, - start: this.start, - limit: this.page_length, - order_by: this.order_by, - // fields: this.fields, - filters: this.get_filters_for_args() - }; - } - - update_data(r) { - const data = r.message; - - if (this.start === 0) { - this.data = data; - } else { - this.data = this.data.concat(data); - } - - this.data_dict = {}; - } - - freeze(toggle) { } - - render() { - this.data_dict = {}; - this.render_image_view(); - - this.setup_quick_view(); - this.setup_like(); - } - - render_offline_card() { - let html = `
          -
          - - {{ _("Payment Cancelled") }} -
          -

          ${ __("Your payment is cancelled.")}

          - -
          `; - - let page = this.page.wrapper.find('.layout-side-section') - page.append(html); - - return; - } - - render_image_view() { - var html = this.data.map(this.item_html.bind(this)).join(""); - - if (this.start === 0) { - // ${this.getHeaderHtml()} - this.$result.html(` -
          -
          - Recently Published -
          - ${html} -
          - `); - } - - if (this.data.length) { - this.doc = this.data[0]; - } - - this.data.map(this.loadImage.bind(this)); - - this.data_dict = {}; - this.data.map(d => { - this.data_dict[d.hub_item_code] = d; - }); - } - - getHeaderHtml(title, image, content) { - // let company_html = - return ` -
          -
          -
          - ${title} -
          -
          -
          - ${title} -
          - - ${content} - -
          -
          -
          - `; - } - - renderHeader() { - return ``; - } - - get_image_html(encoded_name, src, alt_text) { - return `${alt_text}`; - } - - get_image_placeholder(title) { - return `${frappe.get_abbr(title)}`; - } - - loadImage(item) { - item._name = encodeURI(item.name); - const encoded_name = item._name; - const title = strip_html(item[this.meta.title_field || 'name']); - - let placeholder = this.get_image_placeholder(title); - let $container = this.$result.find(`.image-field[data-name="${encoded_name}"]`); - - if (!item[this.imageFieldName]) { - $container.prepend(placeholder); - $container.addClass('no-image'); - } - - frappe.load_image(item[this.imageFieldName], - (imageObj) => { - $container.prepend(imageObj) - }, - () => { - $container.prepend(placeholder); - $container.addClass('no-image'); - }, - (imageObj) => { - imageObj.title = encoded_name; - imageObj.alt = title; - } - ) - } - - setup_quick_view() { - if (this.quick_view) return; - - this.quick_view = new frappe.ui.Dialog({ - title: 'Quick View', - fields: this.formFields - }); - this.quick_view.set_primary_action(__('Request a Quote'), () => { - this.show_rfq_modal() - .then(values => { - item.item_code = values.item_code; - delete values.item_code; - - const supplier = values; - return [item, supplier]; - }) - .then(([item, supplier]) => { - return this.make_rfq(item, supplier, this.page.btn_primary); - }) - .then(r => { - console.log(r); - if (r.message && r.message.rfq) { - this.page.btn_primary.addClass('disabled').html(` ${__('Quote Requested')}`); - } else { - throw r; - } - }) - .catch((e) => { - console.log(e); //eslint-disable-line - }); - }, 'octicon octicon-plus'); - - this.$result.on('click', '.btn.zoom-view', (e) => { - e.preventDefault(); - e.stopPropagation(); - var name = $(e.target).attr('data-name'); - name = decodeURIComponent(name); - - this.quick_view.set_title(name); - let values = this.data_dict[name]; - this.quick_view.set_values(values); - - let fields = []; - - this.quick_view.show(); - - return false; - }); - } - - setup_like() { - if (this.setup_like_done) return; - this.setup_like_done = 1; - this.$result.on('click', '.btn.like-button', (e) => { - if ($(e.target).hasClass('changing')) return; - $(e.target).addClass('changing'); - - e.preventDefault(); - e.stopPropagation(); - - var name = $(e.target).attr('data-name'); - name = decodeURIComponent(name); - let values = this.data_dict[name]; - - let heart = $(e.target); - if (heart.hasClass('like-button')) { - heart = $(e.target).find('.octicon'); - } - - let remove = 1; - - if (heart.hasClass('liked')) { - // unlike - heart.removeClass('liked'); - } else { - // like - remove = 0; - heart.addClass('liked'); - } - - frappe.call({ - method: 'erpnext.hub_node.update_wishlist_item', - args: { - item_name: values.hub_item_code, - remove: remove - }, - callback: (r) => { - let message = __("Added to Favourites"); - if (remove) { - message = __("Removed from Favourites"); - } - frappe.show_alert(message); - }, - freeze: true - }); - - $(e.target).removeClass('changing'); - return false; - }); - } -} - -erpnext.hub.ItemListing = class ItemListing extends erpnext.hub.HubListing { - constructor(opts) { - super(opts); - this.show(); - } - - setup_defaults() { - super.setup_defaults(); - this.doctype = 'Hub Item'; - this.page_title = __('Marketplace'); - this.fields = ['name', 'hub_item_code', 'image', 'item_name', 'item_code', 'company_name', 'description', 'country']; - this.filters = []; - } - - render() { - this.data_dict = {}; - this.render_image_view(); - - this.setup_quick_view(); - this.setup_like(); - } - - bootstrap_data(response) { - // let companies = response.companies.map(d => d.name); - // this.custom_filter_configs = [ - // { - // fieldtype: 'Autocomplete', - // label: __('Select Company'), - // condition: 'like', - // fieldname: 'company_name', - // options: companies - // }, - // { - // fieldtype: 'Link', - // label: __('Select Country'), - // options: 'Country', - // condition: 'like', - // fieldname: 'country' - // } - // ]; - } - - prepareFormFields() { - let fieldnames = ['item_name', 'description', 'company_name', 'country']; - this.formFields = this.meta.fields - .filter(field => fieldnames.includes(field.fieldname)) - .map(field => { - let { - label, - fieldname, - fieldtype, - } = field; - let read_only = 1; - return { - label, - fieldname, - fieldtype, - read_only, - }; - }); - - this.formFields.unshift({ - label: 'image', - fieldname: 'image', - fieldtype: 'Attach Image' - }); - } - - setup_side_bar() { - super.setup_side_bar(); - - this.setup_new_sidebar(); - - return; - - let $pitch = $(`
          -
          Sell on HubMarket
          -

          Over 2000 products listed. Register your company to start selling.

          -
          `); - - this.sidebar.$sidebar.append($pitch); - - this.category_tree = new frappe.ui.Tree({ - parent: this.sidebar.$sidebar, - label: 'All Categories', - expandable: true, - - args: { parent: this.current_category }, - method: 'erpnext.hub_node.get_categories', - on_click: (node) => { - this.update_category(node.label); - } - }); - - this.sidebar.add_item({ - label: __('Companies'), - on_click: () => frappe.set_route('Hub', 'Company') - }, undefined, true); - - this.sidebar.add_item({ - label: this.hub_settings.company, - on_click: () => frappe.set_route('Form', 'Company', this.hub_settings.company) - }, __("Account")); - - this.sidebar.add_item({ - label: __("Favourites"), - on_click: () => frappe.set_route('Hub', 'Favourites') - }, __("Account")); - - this.sidebar.add_item({ - label: __("Settings"), - on_click: () => frappe.set_route('Form', 'Hub Settings') - }, __("Account")); - } - - setup_new_sidebar() { - this.sidebar.$sidebar.append(` -
            -
          • - Browse -
          • -
          • - Favorites -
          • -
          • - Become a seller -
          • -
          - `); - - frappe.call('erpnext.hub_node.get_categories') - .then(r => { - const categories = r.message.map(d => d.value).sort(); - const sidebar_items = [ - `
        • - ${__('Category')} -
        • `, - `
        • - All -
        • `, - ...categories.map(category => ` -
        • - ${category} -
        • - `) - ]; - - this.sidebar.$sidebar.append(` -
            - ${sidebar_items.join('')} -
          - `); - }); - } - - update_category(label) { - this.current_category = (label == 'All Categories') ? undefined : label; - this.refresh(); - } - - get_filters_for_args() { - const filter = {}; - - if (this.search_value) { - filter.item_name = ['like', `%${this.search_value}%`]; - } - - filter.image = ['like', 'http%']; - return filter; - - // if(!this.filter_area) return; - // let filters = {}; - // this.filter_area.get().forEach(f => { - // let field = f[1] !== 'name' ? f[1] : 'item_name'; - // filters[field] = [f[2], f[3]]; - // }); - // if(this.current_category) { - // filters['hub_category'] = this.current_category; - // } - // return filters; - } - - update_data(r) { - super.update_data(r); - - this.data_dict = {}; - this.data.map(d => { - this.data_dict[d.hub_item_code] = d; - }); - } - - item_html(item, index) { - item._name = encodeURI(item.name); - const encoded_name = item._name; - const title = strip_html(item[this.meta.title_field || 'name']); - - const img_url = item[this.imageFieldName]; - const no_image = !img_url; - const _class = no_image ? 'no-image' : ''; - const route = `#Hub/Item/${item.hub_item_code}`; - const company_name = item['company_name']; - - const reviewLength = (item.reviews || []).length; - const ratingAverage = reviewLength - ? item.reviews - .map(r => r.rating) - .reduce((a, b) => a + b, 0) / reviewLength - : -1; - - let ratingHtml = ``; - - for (var i = 0; i < 5; i++) { - let starClass = 'fa-star'; - if (i >= ratingAverage) starClass = 'fa-star-o'; - ratingHtml += ``; - } - let dot_spacer = ''; - let subtitle = ''; - subtitle += comment_when(item.creation); - subtitle += dot_spacer; - - if (ratingAverage > 0) { - subtitle += ratingAverage + ``; - subtitle += dot_spacer; - } - subtitle += company_name; - - let item_html = ` -
          -
          -
          -
          - - ${title} - -
          -
          - ${ratingHtml} - (${reviewLength}) -
          - -
          - -
          -
          - `; - - item_html = ` -
          -
          -
          -
          ${title}
          -
          ${subtitle}
          -
          -
          - -
          -
          -
          - `; - - return item_html; - } - -}; - -erpnext.hub.Favourites2 = class Favourites extends erpnext.hub.ItemListing { - constructor(opts) { - super(opts); - this.show(); - } - - setup_defaults() { - super.setup_defaults(); - this.doctype = 'Hub Item'; - this.page_title = __('Favourites'); - this.fields = ['name', 'hub_item_code', 'image', 'item_name', 'item_code', 'company_name', 'description', 'country']; - this.filters = []; - this.method = 'erpnext.hub_node.get_item_favourites'; - } - - setup_filter_area() { } - - setup_sort_selector() { } - - // setupHe - - getHeaderHtml() { - return ''; - } - - get_args() { - return { - start: this.start, - limit: this.page_length, - order_by: this.order_by, - fields: this.fields - }; - } - - bootstrap_data(response) { } - - prepareFormFields() { } - - setup_side_bar() { - this.sidebar = new frappe.ui.Sidebar({ - wrapper: this.page.wrapper.find('.layout-side-section'), - css_class: 'hub-sidebar' - }); - - this.sidebar.add_item({ - label: __('Back to Products'), - on_click: () => frappe.set_route('Hub', 'Item') - }); - } - - update_category(label) { - this.current_category = (label == 'All Categories') ? undefined : label; - this.refresh(); - } - - get_filters_for_args() { - if (!this.filter_area) return; - let filters = {}; - this.filter_area.get().forEach(f => { - let field = f[1] !== 'name' ? f[1] : 'item_name'; - filters[field] = [f[2], f[3]]; - }); - if (this.current_category) { - filters['hub_category'] = this.current_category; - } - return filters; - } - - update_data(r) { - super.update_data(r); - - this.data_dict = {}; - this.data.map(d => { - this.data_dict[d.hub_item_code] = d; - }); - } -}; - -erpnext.hub.CompanyListing = class CompanyListing extends erpnext.hub.HubListing { - constructor(opts) { - super(opts); - this.show(); - } - - render() { - this.data_dict = {}; - this.render_image_view(); - } - - setup_defaults() { - super.setup_defaults(); - this.doctype = 'Hub Company'; - this.page_title = __('Companies'); - this.fields = ['company_logo', 'name', 'site_name', 'seller_city', 'seller_description', 'seller', 'country', 'company_name']; - this.filters = []; - this.custom_filter_configs = [ - { - fieldtype: 'Link', - label: 'Country', - options: 'Country', - condition: 'like', - fieldname: 'country' - } - ]; - this.imageFieldName = 'company_logo'; - } - - setup_side_bar() { - this.sidebar = new frappe.ui.Sidebar({ - wrapper: this.page.wrapper.find('.layout-side-section'), - css_class: 'hub-sidebar' - }); - - this.sidebar.add_item({ - label: __('Back to Products'), - on_click: () => frappe.set_route('Hub', 'Item') - }); - } - - get_filters_for_args() { - let filters = {}; - // this.filter_area.get().forEach(f => { - // let field = f[1] !== 'name' ? f[1] : 'company_name'; - // filters[field] = [f[2], f[3]]; - // }); - return filters; - } - - item_html(company) { - company._name = encodeURI(company.company_name); - const encoded_name = company._name; - const title = strip_html(company.company_name); - const _class = !company[this.imageFieldName] ? 'no-image' : ''; - const company_name = company['company_name']; - const route = `#Hub/Company/${company_name}`; - - let image_html = company.company_logo ? - `` : - `
          ${frappe.get_abbr(company.company_name)}
          `; - - let item_html = ` -
          -
          -
          - - ${title} - -
          -
          - - -
          - `; - - return item_html; - } - -}; diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 31cf5daf48..ab0d4f7e71 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -1,3 +1,19 @@ +// pages +import './pages/home'; +import './pages/favourites'; +import './pages/search'; +import './pages/category'; +import './pages/item'; +import './pages/register'; +import './pages/profile'; +import './pages/publish'; +import './pages/published_products'; +import './pages/not_found'; + +// helpers +import './helpers'; +import './hub_call'; + frappe.provide('hub'); frappe.provide('erpnext.hub'); @@ -180,1119 +196,3 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages[route[1]].show(); } } - -class SubPage { - constructor(parent, options) { - this.$parent = $(parent); - this.make_wrapper(options); - - // handle broken images after every render - if (this.render) { - this._render = this.render.bind(this); - - this.render = (...args) => { - this._render(...args); - frappe.dom.handle_broken_images(this.$wrapper); - } - } - } - - make_wrapper() { - const page_name = frappe.get_route()[1]; - this.$wrapper = $(`
          `).appendTo(this.$parent); - this.hide(); - } - - empty() { - this.$wrapper.empty(); - } - - show() { - this.refresh(); - this.$wrapper.show(); - } - - hide() { - this.$wrapper.hide(); - } -} - -erpnext.hub.Home = class Home extends SubPage { - make_wrapper() { - super.make_wrapper(); - - make_search_bar({ - wrapper: this.$wrapper, - on_search: keyword => { - frappe.set_route('marketplace', 'search', keyword); - } - }); - } - - refresh() { - this.get_items_and_render(); - } - - get_items_and_render() { - this.$wrapper.find('.hub-card-container').empty(); - this.get_data() - .then(data => { - this.render(data); - }); - } - - get_data() { - return hub.call('get_data_for_homepage', { country: frappe.defaults.get_user_default('country') }); - } - - render(data) { - let html = get_item_card_container_html(data.random_items, __('Explore')); - this.$wrapper.append(html); - - if (data.items_by_country.length) { - html = get_item_card_container_html(data.items_by_country, __('Near you')); - this.$wrapper.append(html); - } - } -} - -erpnext.hub.Favourites = class Favourites extends SubPage { - refresh() { - this.get_favourites() - .then(items => { - this.render(items); - }); - } - - get_favourites() { - return hub.call('get_item_favourites'); - } - - render(items) { - this.$wrapper.find('.hub-card-container').empty(); - const html = get_item_card_container_html(items, __('Favourites')); - this.$wrapper.append(html) - } -} - -erpnext.hub.Category = class Category extends SubPage { - refresh() { - this.category = frappe.get_route()[2]; - this.get_items_for_category(this.category) - .then(r => { - this.render(r.message); - }); - } - - get_items_for_category(category) { - this.$wrapper.find('.hub-card-container').empty(); - return frappe.call('erpnext.hub_node.get_list', { - doctype: 'Hub Item', - filters: { - hub_category: category - } - }); - } - - render(items) { - const html = get_item_card_container_html(items, __(this.category)); - this.$wrapper.append(html) - } -} - -erpnext.hub.SearchPage = class SearchPage extends SubPage { - make_wrapper() { - super.make_wrapper(); - - make_search_bar({ - wrapper: this.$wrapper, - on_search: keyword => { - frappe.set_route('marketplace', 'search', keyword); - } - }); - } - - refresh() { - this.keyword = frappe.get_route()[2] || ''; - this.$wrapper.find('input').val(this.keyword); - - this.get_items_by_keyword(this.keyword) - .then(items => this.render(items)); - } - - get_items_by_keyword(keyword) { - return hub.call('get_items_by_keyword', { keyword }); - } - - render(items) { - this.$wrapper.find('.hub-card-container').remove(); - const title = this.keyword ? __('Search results for "{0}"', [this.keyword]) : ''; - const html = get_item_card_container_html(items, title); - this.$wrapper.append(html); - } -} - -erpnext.hub.Item = class Item extends SubPage { - make_wrapper() { - super.make_wrapper(); - this.setup_events(); - } - - refresh() { - this.show_skeleton(); - this.hub_item_code = frappe.get_route()[2]; - - this.own_item = false; - - this.get_item(this.hub_item_code) - .then(item => { - this.own_item = item.hub_seller === hub.settings.company_email; - this.item = item; - this.render(item); - }); - } - - show_skeleton() { - const skeleton = `
          -
          -
          -
          -
          -
          -

          Name

          -
          -

          Details

          -

          Ratings

          -
          -
          -
          -

          Desc

          -

          Desc

          -
          -
          -
          -
          `; - - this.$wrapper.html(skeleton); - } - - setup_events() { - this.$wrapper.on('click', '.btn-contact-seller', () => { - const d = new frappe.ui.Dialog({ - title: __('Send a message'), - fields: [ - { - fieldname: 'to', - fieldtype: 'Read Only', - label: __('To'), - default: this.item.company - }, - { - fieldtype: 'Text', - fieldname: 'message', - label: __('Message') - } - ] - }); - - d.show(); - }); - } - - get_item(hub_item_code) { - return hub.call('get_item_details', { hub_item_code }); - } - - render(item) { - const title = item.item_name || item.name; - const seller = item.company; - - const who = __('Posted By {0}', [seller]); - const when = comment_when(item.creation); - - const city = item.city ? item.city + ', ' : ''; - const country = item.country ? item.country : ''; - const where = `${city}${country}`; - - const dot_spacer = ''; - - const description = item.description || ''; - - const rating_html = get_rating_html(item.average_rating); - const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet'); - - let edit_buttons_html = ''; - - if(this.own_item) { - edit_buttons_html = `
          - - -
          `; - } - - const html = ` -
          -
          -
          - -
          -
          -
          -
          -
          - -
          -
          -
          -

          ${title}

          -
          -

          ${where}${dot_spacer}${when}

          -

          ${rating_html} (${rating_count})

          -
          -
          -
          - ${description ? - `${__('Description')} -

          ${description}

          - ` : `

          ${__('No description')}

          ` - } -

          - ${edit_buttons_html} -
          -
          - -
          -
          -
          -
          - Seller Information -
          -
          - -
          -
          - - -
          -
          - -
          - -
          -
          - `; - - this.$wrapper.html(html); - - if(this.own_item) { - this.bind_edit_buttons(); - } - - this.make_review_area(); - - this.get_reviews() - .then(reviews => { - this.reviews = reviews; - this.render_reviews(reviews); - }); - } - - bind_edit_buttons() { - this.edit_dialog = new frappe.ui.Dialog({ - title: "Edit Your Product", - fields: [] - }); - - this.$wrapper.find('.edit-item').on('click', this.on_edit.bind(this)); - this.$wrapper.find('.unpublish').on('click', this.on_unpublish.bind(this)); - } - - on_edit() { - this.edit_dialog.show(); - } - - on_unpublish() { - if(!this.unpublish_dialog) { - this.unpublish_dialog = new frappe.ui.Dialog({ - title: "Edit Your Product", - fields: [] - }); - } - - this.unpublish_dialog.show(); - } - - make_review_area() { - this.comment_area = new frappe.ui.ReviewArea({ - parent: this.$wrapper.find('.timeline-head').empty(), - mentions: [], - on_submit: (values) => { - values.user = frappe.session.user; - values.username = frappe.session.user_fullname; - - hub.call('add_item_review', { - hub_item_code: this.hub_item_code, - review: JSON.stringify(values) - }) - .then(review => { - this.reviews = this.reviews || []; - this.reviews.push(review); - this.render_reviews(this.reviews); - - this.comment_area.reset(); - }); - } - }); - } - - get_reviews() { - return hub.call('get_item_reviews', { hub_item_code: this.hub_item_code }).catch(() => {}); - } - - render_reviews(reviews=[]) { - this.$wrapper.find('.timeline-items').empty(); - - reviews.sort((a, b) => { - if (a.modified > b.modified) { - return -1; - } - - if (a.modified < b.modified) { - return 1; - } - - return 0; - }); - - reviews.forEach(review => this.render_review(review)); - } - - render_review(review) { - let username = review.username || review.user || __("Anonymous"); - - let image_html = review.user_image - ? `
          ` - : `
          ${frappe.get_abbr(username)}
          ` - - let edit_html = review.own - ? ` -
          - - ${'data.edit'} - -
          ` - : ''; - - let rating_html = get_rating_html(review.rating); - - const $timeline_items = this.$wrapper.find('.timeline-items'); - - $(this.get_timeline_item(review, image_html, edit_html, rating_html)) - .appendTo($timeline_items); - } - - get_timeline_item(data, image_html, edit_html, rating_html) { - return `
          - -
          -
          -
          ${edit_html}
          - -
          - - ${image_html} - - -
          - - - ${data.username} - - - - - -
          -
          -
          -
          -

          - ${rating_html} -

          -
          ${data.subject}
          -

          - ${data.content} -

          -
          -
          -
          -
          -
          `; - } -} -erpnext.hub.Register = class Register extends SubPage { - make_wrapper() { - super.make_wrapper(); - this.$register_container = $(`
          `) - .appendTo(this.$wrapper); - this.$form_container = $('
          ') - .appendTo(this.$wrapper); - } - - refresh() { - this.$register_container.empty(); - this.$form_container.empty(); - this.render(); - } - - render() { - this.make_field_group(); - } - - make_field_group() { - const fields = [ - { - fieldtype: 'Link', - fieldname: 'company', - label: __('Company'), - options: 'Company', - onchange: () => { - const value = this.field_group.get_value('company'); - - if (value) { - frappe.db.get_doc('Company', value) - .then(company => { - this.field_group.set_values({ - country: company.country, - company_email: company.email, - currency: company.default_currency - }); - }); - } - } - }, - { - fieldname: 'company_email', - label: __('Email'), - fieldtype: 'Data' - }, - { - fieldname: 'country', - label: __('Country'), - fieldtype: 'Read Only' - }, - { - fieldname: 'currency', - label: __('Currency'), - fieldtype: 'Read Only' - }, - { - fieldtype: 'Text', - label: __('About your Company'), - fieldname: 'company_description' - } - ]; - - this.field_group = new frappe.ui.FieldGroup({ - parent: this.$form_container, - fields - }); - - this.field_group.make(); - - const default_company = frappe.defaults.get_default('company'); - this.field_group.set_value('company', default_company); - - this.$form_container.find('.form-column').append(` -
          - -
          - `); - - this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller')) - - this.$form_container.on('click', '.btn-register', (e) => { - const form_values = this.field_group.get_values(); - - let values_filled = true; - const mandatory_fields = ['company', 'company_email', 'company_description']; - mandatory_fields.forEach(field => { - const value = form_values[field]; - if (!value) { - this.field_group.set_df_property(field, 'reqd', 1); - values_filled = false; - } - }); - if (!values_filled) return; - - frappe.call({ - method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', - args: form_values, - btn: $(e.currentTarget) - }).then(() => { - frappe.set_route('marketplace', 'publish'); - - // custom jquery event - this.$wrapper.trigger('seller-registered'); - }); - }); - } -} - -erpnext.hub.Profile = class Profile extends SubPage { - make_wrapper() { - super.make_wrapper(); - } - - refresh() { - this.get_hub_seller_profile(this.keyword) - .then(profile => this.render(profile)); - } - - get_hub_seller_profile() { - return hub.call('get_hub_seller_profile', { hub_seller: hub.settings.company_email }); - } - - render(profile) { - const p = profile; - const content_by_log_type = this.get_content_by_log_type(); - - let activity_logs = (p.hub_seller_activity || []).sort((a, b) => { - return new Date(b.creation) - new Date(a.creation); - }); - - const timeline_items_html = activity_logs - .map(log => { - const stats = JSON.parse(log.stats); - const no_of_items = stats && stats.push_update || ''; - - const content = content_by_log_type[log.type]; - const message = content.get_message(no_of_items); - const icon = content.icon; - return this.get_timeline_log_item(log.pretty_date, message, icon); - }) - .join(''); - - const profile_html = `
          -
          -
          - -
          -
          -
          -
          -
          - -
          -
          -
          -

          ${p.company}

          -
          -

          ${p.country}

          -

          ${p.site_name}

          -
          -
          -
          - ${'description' - ? `

          ${p.company_description}

          ` - : `

          __('No description') -

          -
          - -
          -
          - ${timeline_items_html} -
          -
          - -
          `; - - this.$wrapper.html(profile_html); - } - - get_timeline_log_item(pretty_date, message, icon) { - return `
          -
          - - ${pretty_date} ${message} -
          -
          `; - } - - get_content_by_log_type() { - return { - "Created": { - icon: 'octicon-heart', - get_message: () => 'Joined Marketplace' - }, - "Items Publish": { - icon: 'octicon-bookmark', - get_message: (no_of_items) => - `Published ${no_of_items} product${no_of_items > 1 ? 's' : ''} to Marketplace` - } - } - } -} - -erpnext.hub.Publish = class Publish extends SubPage { - make_wrapper() { - super.make_wrapper(); - this.items_to_publish = []; - this.unpublished_items = []; - this.fetched_items = []; - - frappe.realtime.on("items-sync", (data) => { - this.$wrapper.find('.progress-bar').css('width', data.progress_percent+'%'); - - if(data.progress_percent === 100 || data.progress_percent === '100') { - setTimeout(() => { - hub.settings.sync_in_progress = 0; - frappe.db.get_doc('Hub Settings') - .then(doc => { - hub.settings = doc; - this.refresh(); - }); - }, 500); - } - }); - } - - refresh() { - if(!hub.settings.sync_in_progress) { - this.make_publish_ready_state(); - } else { - this.make_publish_in_progress_state(); - } - } - - make_publish_ready_state() { - this.$wrapper.empty(); - this.$wrapper.append(this.get_publishing_header()); - - make_search_bar({ - wrapper: this.$wrapper, - on_search: keyword => { - this.search_value = keyword; - this.get_items_and_render(); - }, - placeholder: __('Search Items') - }); - - this.setup_publishing_events(); - - if(hub.settings.last_sync_datetime) { - this.show_message(`Last sync was ${comment_when(hub.settings.last_sync_datetime)}. - See your Published Products.`); - } - - this.get_items_and_render(); - } - - get_publishing_header() { - const title_html = `${__('Select Products to Publish')}`; - - const subtitle_html = `

          - ${__(`Only products with an image, description and category can be published. - Please update them if an item in your inventory does not appear.`)} -

          `; - - const publish_button_html = ``; - - return $(` -
          -
          - ${title_html} - ${subtitle_html} -
          - ${publish_button_html} -
          - `); - } - - setup_publishing_events() { - this.$wrapper.find('.publish-items').on('click', () => { - this.publish_selected_items() - .then(this.refresh.bind(this)) - }); - - this.$wrapper.on('click', '.hub-card', (e) => { - const $target = $(e.currentTarget); - $target.toggleClass('active'); - - // Get total items - const total_items = this.$wrapper.find('.hub-card.active').length; - - let button_label; - if (total_items > 0) { - const more_than_one = total_items > 1; - button_label = __('Publish {0} item{1}', [total_items, more_than_one ? 's' : '']); - } else { - button_label = __('Publish'); - } - - this.$wrapper.find('.publish-items') - .text(button_label) - .prop('disabled', total_items === 0); - }); - } - - show_message(message) { - const $message = $(`
          -

          - - ${message} - - -

          -
          `); - - $message.find('.octicon-x').on('click', () => { - $message.remove(); - }); - - this.$wrapper.prepend($message); - } - - make_publish_in_progress_state() { - this.$wrapper.empty(); - - this.$wrapper.append(this.show_publish_progress()); - - const subtitle_html = `

          - ${__(`Only products with an image, description and category can be published. - Please update them if an item in your inventory does not appear.`)} -

          `; - - this.$wrapper.append(subtitle_html); - - // Show search list with only desctiption, and don't set any events - make_search_bar({ - wrapper: this.$wrapper, - on_search: keyword => { - this.search_value = keyword; - this.get_items_and_render(); - }, - placeholder: __('Search Items') - }); - - this.get_items_and_render(); - } - - show_publish_progress() { - const items_to_publish = this.items_to_publish.length - ? this.items_to_publish - : JSON.parse(hub.settings.custom_data); - - const $publish_progress = $(`
          -

          ${__(`Syncing ${items_to_publish.length} Products`)}

          -
          -
          -
          - -
          `); - - const items_to_publish_container = $(get_item_card_container_html( - items_to_publish, '', get_local_item_card_html)); - - items_to_publish_container.find('.hub-card').addClass('active'); - - $publish_progress.append(items_to_publish_container); - - return $publish_progress; - } - - get_items_and_render(wrapper = this.$wrapper) { - wrapper.find('.results').remove(); - const items = this.get_valid_items(); - - if(!items.then) { - this.render(items, wrapper); - } else { - items.then(r => { - this.fetched_items = r.message; - this.render(r.message, wrapper); - }); - } - } - - render(items, wrapper) { - const items_container = $(get_item_card_container_html(items, '', get_local_item_card_html)); - items_container.addClass('results'); - wrapper.append(items_container); - } - - get_valid_items() { - if(this.unpublished_items.length) { - return this.unpublished_items; - } - return frappe.call( - 'erpnext.hub_node.get_valid_items', - { - search_value: this.search_value - } - ); - } - - publish_selected_items() { - const item_codes_to_publish = []; - this.$wrapper.find('.hub-card.active').map(function () { - item_codes_to_publish.push($(this).attr("data-id")); - }); - - this.unpublished_items = this.fetched_items.filter(item => { - return !item_codes_to_publish.includes(item.item_code); - }); - - const items_to_publish = this.fetched_items.filter(item => { - return item_codes_to_publish.includes(item.item_code); - }); - this.items_to_publish = items_to_publish; - - return frappe.call( - 'erpnext.hub_node.publish_selected_items', - { - items_to_publish: item_codes_to_publish - } - ) - } -} - -erpnext.hub.PublishedProducts = class PublishedProducts extends SubPage { - get_items_and_render() { - this.$wrapper.find('.hub-card-container').empty(); - this.get_published_products() - .then(items => this.render(items)); - } - - refresh() { - this.get_items_and_render(); - } - - render(items) { - const items_container = $(get_item_card_container_html(items, __('Your Published Products'))); - this.$wrapper.append(items_container); - } - - get_published_products() { - return hub.call('get_items_by_seller', { hub_seller: hub.settings.company_email }); - } -} - -erpnext.hub.NotFound = class NotFound extends SubPage { - refresh() { - this.$wrapper.html(get_empty_state( - __('Sorry! I could not find what you were looking for.'), - `` - )); - } -} - -function get_empty_state(message, action) { - return `
          -

          ${message}

          - ${action ? `

          ${action}

          `: ''} -
          `; -} - -function get_item_card_container_html(items, title='', get_item_html = get_item_card_html) { - const items_html = (items || []).map(item => get_item_html(item)).join(''); - const title_html = title - ? `
          - ${title} -
          ` - : ''; - - const html = `
          - ${title_html} - ${items_html} -
          `; - - return html; -} - -function get_item_card_html(item) { - const item_name = item.item_name || item.name; - const title = strip_html(item_name); - const img_url = item.image; - const company_name = item.company; - - // Subtitle - let subtitle = [comment_when(item.creation)]; - const rating = item.average_rating; - if (rating > 0) { - subtitle.push(rating + ``) - } - subtitle.push(company_name); - - let dot_spacer = ''; - subtitle = subtitle.join(dot_spacer); - - const item_html = ` -
          -
          -
          -
          ${title}
          -
          ${subtitle}
          -
          -
          - -
          -
          -
          -
          - `; - - return item_html; -} - -function get_local_item_card_html(item) { - const item_name = item.item_name || item.name; - const title = strip_html(item_name); - const img_url = item.image; - const company_name = item.company; - - const is_active = item.publish_in_hub; - const id = item.hub_item_code || item.item_code; - - // Subtitle - let subtitle = [comment_when(item.creation)]; - const rating = item.average_rating; - if (rating > 0) { - subtitle.push(rating + ``) - } - subtitle.push(company_name); - - let dot_spacer = ''; - subtitle = subtitle.join(dot_spacer); - - const edit_item_button = `
          - -
          `; - - const item_html = ` -
          -
          -
          -
          ${title}
          -
          ${subtitle}
          - -
          -
          - -
          -
          - ${edit_item_button} -
          -
          -
          -
          -
          - `; - - return item_html; -} - - -function get_rating_html(rating) { - let rating_html = ``; - for (var i = 0; i < 5; i++) { - let star_class = 'fa-star'; - if (i >= rating) star_class = 'fa-star-o'; - rating_html += ``; - } - return rating_html; -} - -function make_search_bar({wrapper, on_search, placeholder = __('Search for anything')}) { - const $search = $(` -
          - -
          ` - ); - wrapper.append($search); - const $search_input = $search.find('input'); - - $search_input.on('keydown', frappe.utils.debounce((e) => { - if (e.which === frappe.ui.keyCode.ENTER) { - const search_value = $search_input.val(); - on_search(search_value); - } - }, 300)); -} - -// caching - -erpnext.hub.cache = {}; -hub.call = function call_hub_method(method, args={}) { - return new Promise((resolve, reject) => { - - // cache - const key = method + JSON.stringify(args); - if (erpnext.hub.cache[key]) { - resolve(erpnext.hub.cache[key]); - } - - // cache invalidation after 5 minutes - const timeout = 5 * 60 * 1000; - - setTimeout(() => { - delete erpnext.hub.cache[key]; - }, timeout); - - frappe.call({ - method: 'erpnext.hub_node.call_hub_method', - args: { - method, - params: args - } - }) - .then(r => { - if (r.message) { - if (r.message.error) { - frappe.throw({ - title: __('Marketplace Error'), - message: r.message.error - }); - } - - erpnext.hub.cache[key] = r.message; - resolve(r.message) - } - reject(r) - }) - .fail(reject) - }); -} diff --git a/erpnext/public/js/hub/pages/base_page.js b/erpnext/public/js/hub/pages/base_page.js new file mode 100644 index 0000000000..70248da366 --- /dev/null +++ b/erpnext/public/js/hub/pages/base_page.js @@ -0,0 +1,35 @@ +export default class SubPage { + constructor(parent, options) { + this.$parent = $(parent); + this.make_wrapper(options); + + // handle broken images after every render + if (this.render) { + this._render = this.render.bind(this); + + this.render = (...args) => { + this._render(...args); + frappe.dom.handle_broken_images(this.$wrapper); + } + } + } + + make_wrapper() { + const page_name = frappe.get_route()[1]; + this.$wrapper = $(`
          `).appendTo(this.$parent); + this.hide(); + } + + empty() { + this.$wrapper.empty(); + } + + show() { + this.refresh(); + this.$wrapper.show(); + } + + hide() { + this.$wrapper.hide(); + } +} \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/category.js b/erpnext/public/js/hub/pages/category.js new file mode 100644 index 0000000000..21dcb328fd --- /dev/null +++ b/erpnext/public/js/hub/pages/category.js @@ -0,0 +1,27 @@ +import SubPage from './base_page'; +import { get_item_card_container_html } from '../helpers'; + +erpnext.hub.Category = class Category extends SubPage { + refresh() { + this.category = frappe.get_route()[2]; + this.get_items_for_category(this.category) + .then(r => { + this.render(r.message); + }); + } + + get_items_for_category(category) { + this.$wrapper.find('.hub-card-container').empty(); + return frappe.call('erpnext.hub_node.get_list', { + doctype: 'Hub Item', + filters: { + hub_category: category + } + }); + } + + render(items) { + const html = get_item_card_container_html(items, __(this.category)); + this.$wrapper.append(html) + } +} \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js new file mode 100644 index 0000000000..9605eb19b0 --- /dev/null +++ b/erpnext/public/js/hub/pages/favourites.js @@ -0,0 +1,21 @@ +import SubPage from './base_page'; +import { get_item_card_container_html } from '../helpers'; + +erpnext.hub.Favourites = class Favourites extends SubPage { + refresh() { + this.get_favourites() + .then(items => { + this.render(items); + }); + } + + get_favourites() { + return hub.call('get_item_favourites'); + } + + render(items) { + this.$wrapper.find('.hub-card-container').empty(); + const html = get_item_card_container_html(items, __('Favourites')); + this.$wrapper.append(html) + } +} \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/home.js b/erpnext/public/js/hub/pages/home.js new file mode 100644 index 0000000000..ff37e812c3 --- /dev/null +++ b/erpnext/public/js/hub/pages/home.js @@ -0,0 +1,41 @@ +import SubPage from './base_page'; +import { make_search_bar, get_item_card_container_html } from '../helpers'; + +erpnext.hub.Home = class Home extends SubPage { + make_wrapper() { + super.make_wrapper(); + + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + frappe.set_route('marketplace', 'search', keyword); + } + }); + } + + refresh() { + this.get_items_and_render(); + } + + get_items_and_render() { + this.$wrapper.find('.hub-card-container').empty(); + this.get_data() + .then(data => { + this.render(data); + }); + } + + get_data() { + return hub.call('get_data_for_homepage', { country: frappe.defaults.get_user_default('country') }); + } + + render(data) { + let html = get_item_card_container_html(data.random_items, __('Explore')); + this.$wrapper.append(html); + + if (data.items_by_country.length) { + html = get_item_card_container_html(data.items_by_country, __('Near you')); + this.$wrapper.append(html); + } + } +} \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js new file mode 100644 index 0000000000..dabddeaab7 --- /dev/null +++ b/erpnext/public/js/hub/pages/item.js @@ -0,0 +1,327 @@ +import SubPage from './base_page'; +import { get_rating_html } from '../helpers'; + +erpnext.hub.Item = class Item extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.setup_events(); + } + + refresh() { + this.show_skeleton(); + this.hub_item_code = frappe.get_route()[2]; + + this.own_item = false; + + this.get_item(this.hub_item_code) + .then(item => { + this.own_item = item.hub_seller === hub.settings.company_email; + this.item = item; + this.render(item); + }); + } + + show_skeleton() { + const skeleton = `
          +
          +
          +
          +
          +
          +

          Name

          +
          +

          Details

          +

          Ratings

          +
          +
          +
          +

          Desc

          +

          Desc

          +
          +
          +
          +
          `; + + this.$wrapper.html(skeleton); + } + + setup_events() { + this.$wrapper.on('click', '.btn-contact-seller', () => { + const d = new frappe.ui.Dialog({ + title: __('Send a message'), + fields: [ + { + fieldname: 'to', + fieldtype: 'Read Only', + label: __('To'), + default: this.item.company + }, + { + fieldtype: 'Text', + fieldname: 'message', + label: __('Message') + } + ] + }); + + d.show(); + }); + } + + get_item(hub_item_code) { + return hub.call('get_item_details', { hub_item_code }); + } + + render(item) { + const title = item.item_name || item.name; + const seller = item.company; + + const who = __('Posted By {0}', [seller]); + const when = comment_when(item.creation); + + const city = item.city ? item.city + ', ' : ''; + const country = item.country ? item.country : ''; + const where = `${city}${country}`; + + const dot_spacer = ''; + + const description = item.description || ''; + + const rating_html = get_rating_html(item.average_rating); + const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet'); + + let edit_buttons_html = ''; + + if(this.own_item) { + edit_buttons_html = `
          + + +
          `; + } + + const html = ` +
          +
          +
          + +
          +
          +
          +
          +
          + +
          +
          +
          +

          ${title}

          +
          +

          ${where}${dot_spacer}${when}

          +

          ${rating_html} (${rating_count})

          +
          +
          +
          + ${description ? + `${__('Description')} +

          ${description}

          + ` : `

          ${__('No description')}

          ` + } +

          + ${edit_buttons_html} +
          +
          + +
          +
          +
          +
          + Seller Information +
          +
          + +
          +
          + + +
          +
          + +
          + +
          +
          + `; + + this.$wrapper.html(html); + + if(this.own_item) { + this.bind_edit_buttons(); + } + + this.make_review_area(); + + this.get_reviews() + .then(reviews => { + this.reviews = reviews; + this.render_reviews(reviews); + }); + } + + bind_edit_buttons() { + this.edit_dialog = new frappe.ui.Dialog({ + title: "Edit Your Product", + fields: [] + }); + + this.$wrapper.find('.edit-item').on('click', this.on_edit.bind(this)); + this.$wrapper.find('.unpublish').on('click', this.on_unpublish.bind(this)); + } + + on_edit() { + this.edit_dialog.show(); + } + + on_unpublish() { + if(!this.unpublish_dialog) { + this.unpublish_dialog = new frappe.ui.Dialog({ + title: "Edit Your Product", + fields: [] + }); + } + + this.unpublish_dialog.show(); + } + + make_review_area() { + this.comment_area = new frappe.ui.ReviewArea({ + parent: this.$wrapper.find('.timeline-head').empty(), + mentions: [], + on_submit: (values) => { + values.user = frappe.session.user; + values.username = frappe.session.user_fullname; + + hub.call('add_item_review', { + hub_item_code: this.hub_item_code, + review: JSON.stringify(values) + }) + .then(review => { + this.reviews = this.reviews || []; + this.reviews.push(review); + this.render_reviews(this.reviews); + + this.comment_area.reset(); + }); + } + }); + } + + get_reviews() { + return hub.call('get_item_reviews', { hub_item_code: this.hub_item_code }).catch(() => {}); + } + + render_reviews(reviews=[]) { + this.$wrapper.find('.timeline-items').empty(); + + reviews.sort((a, b) => { + if (a.modified > b.modified) { + return -1; + } + + if (a.modified < b.modified) { + return 1; + } + + return 0; + }); + + reviews.forEach(review => this.render_review(review)); + } + + render_review(review) { + let username = review.username || review.user || __("Anonymous"); + + let image_html = review.user_image + ? `
          ` + : `
          ${frappe.get_abbr(username)}
          ` + + let edit_html = review.own + ? ` +
          + + ${'data.edit'} + +
          ` + : ''; + + let rating_html = get_rating_html(review.rating); + + const $timeline_items = this.$wrapper.find('.timeline-items'); + + $(this.get_timeline_item(review, image_html, edit_html, rating_html)) + .appendTo($timeline_items); + } + + get_timeline_item(data, image_html, edit_html, rating_html) { + return `
          + +
          +
          +
          ${edit_html}
          + +
          + + ${image_html} + + +
          + + + ${data.username} + + + + + +
          +
          +
          +
          +

          + ${rating_html} +

          +
          ${data.subject}
          +

          + ${data.content} +

          +
          +
          +
          +
          +
          `; + } +} \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/not_found.js b/erpnext/public/js/hub/pages/not_found.js new file mode 100644 index 0000000000..a83d8817de --- /dev/null +++ b/erpnext/public/js/hub/pages/not_found.js @@ -0,0 +1,10 @@ +import SubPage from './base_page'; + +erpnext.hub.NotFound = class NotFound extends SubPage { + refresh() { + this.$wrapper.html(get_empty_state( + __('Sorry! I could not find what you were looking for.'), + `` + )); + } +} diff --git a/erpnext/public/js/hub/pages/profile.js b/erpnext/public/js/hub/pages/profile.js new file mode 100644 index 0000000000..6dd1f8716a --- /dev/null +++ b/erpnext/public/js/hub/pages/profile.js @@ -0,0 +1,98 @@ +import SubPage from './base_page'; + +erpnext.hub.Profile = class Profile extends SubPage { + make_wrapper() { + super.make_wrapper(); + } + + refresh() { + this.get_hub_seller_profile(this.keyword) + .then(profile => this.render(profile)); + } + + get_hub_seller_profile() { + return hub.call('get_hub_seller_profile', { hub_seller: hub.settings.company_email }); + } + + render(profile) { + const p = profile; + const content_by_log_type = this.get_content_by_log_type(); + + let activity_logs = (p.hub_seller_activity || []).sort((a, b) => { + return new Date(b.creation) - new Date(a.creation); + }); + + const timeline_items_html = activity_logs + .map(log => { + const stats = JSON.parse(log.stats); + const no_of_items = stats && stats.push_update || ''; + + const content = content_by_log_type[log.type]; + const message = content.get_message(no_of_items); + const icon = content.icon; + return this.get_timeline_log_item(log.pretty_date, message, icon); + }) + .join(''); + + const profile_html = `
          +
          +
          + +
          +
          +
          +
          +
          + +
          +
          +
          +

          ${p.company}

          +
          +

          ${p.country}

          +

          ${p.site_name}

          +
          +
          +
          + ${'description' + ? `

          ${p.company_description}

          ` + : `

          __('No description') +

          +
          + +
          +
          + ${timeline_items_html} +
          +
          + +
          `; + + this.$wrapper.html(profile_html); + } + + get_timeline_log_item(pretty_date, message, icon) { + return `
          +
          + + ${pretty_date} ${message} +
          +
          `; + } + + get_content_by_log_type() { + return { + "Created": { + icon: 'octicon-heart', + get_message: () => 'Joined Marketplace' + }, + "Items Publish": { + icon: 'octicon-bookmark', + get_message: (no_of_items) => + `Published ${no_of_items} product${no_of_items > 1 ? 's' : ''} to Marketplace` + } + } + } +} \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js new file mode 100644 index 0000000000..6e8caabb4c --- /dev/null +++ b/erpnext/public/js/hub/pages/publish.js @@ -0,0 +1,228 @@ +import SubPage from './base_page'; +import { make_search_bar, get_item_card_container_html, get_local_item_card_html } from '../helpers'; + +erpnext.hub.Publish = class Publish extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.items_to_publish = []; + this.unpublished_items = []; + this.fetched_items = []; + + frappe.realtime.on("items-sync", (data) => { + this.$wrapper.find('.progress-bar').css('width', data.progress_percent+'%'); + + if(data.progress_percent === 100 || data.progress_percent === '100') { + setTimeout(() => { + hub.settings.sync_in_progress = 0; + frappe.db.get_doc('Hub Settings') + .then(doc => { + hub.settings = doc; + this.refresh(); + }); + }, 500); + } + }); + } + + refresh() { + if(!hub.settings.sync_in_progress) { + this.make_publish_ready_state(); + } else { + this.make_publish_in_progress_state(); + } + } + + make_publish_ready_state() { + this.$wrapper.empty(); + this.$wrapper.append(this.get_publishing_header()); + + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + this.search_value = keyword; + this.get_items_and_render(); + }, + placeholder: __('Search Items') + }); + + this.setup_publishing_events(); + + if(hub.settings.last_sync_datetime) { + this.show_message(`Last sync was ${comment_when(hub.settings.last_sync_datetime)}. + See your Published Products.`); + } + + this.get_items_and_render(); + } + + get_publishing_header() { + const title_html = `${__('Select Products to Publish')}`; + + const subtitle_html = `

          + ${__(`Only products with an image, description and category can be published. + Please update them if an item in your inventory does not appear.`)} +

          `; + + const publish_button_html = ``; + + return $(` +
          +
          + ${title_html} + ${subtitle_html} +
          + ${publish_button_html} +
          + `); + } + + setup_publishing_events() { + this.$wrapper.find('.publish-items').on('click', () => { + this.publish_selected_items() + .then(this.refresh.bind(this)) + }); + + this.$wrapper.on('click', '.hub-card', (e) => { + const $target = $(e.currentTarget); + $target.toggleClass('active'); + + // Get total items + const total_items = this.$wrapper.find('.hub-card.active').length; + + let button_label; + if (total_items > 0) { + const more_than_one = total_items > 1; + button_label = __('Publish {0} item{1}', [total_items, more_than_one ? 's' : '']); + } else { + button_label = __('Publish'); + } + + this.$wrapper.find('.publish-items') + .text(button_label) + .prop('disabled', total_items === 0); + }); + } + + show_message(message) { + const $message = $(`
          +

          + + ${message} + + +

          +
          `); + + $message.find('.octicon-x').on('click', () => { + $message.remove(); + }); + + this.$wrapper.prepend($message); + } + + make_publish_in_progress_state() { + this.$wrapper.empty(); + + this.$wrapper.append(this.show_publish_progress()); + + const subtitle_html = `

          + ${__(`Only products with an image, description and category can be published. + Please update them if an item in your inventory does not appear.`)} +

          `; + + this.$wrapper.append(subtitle_html); + + // Show search list with only desctiption, and don't set any events + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + this.search_value = keyword; + this.get_items_and_render(); + }, + placeholder: __('Search Items') + }); + + this.get_items_and_render(); + } + + show_publish_progress() { + const items_to_publish = this.items_to_publish.length + ? this.items_to_publish + : JSON.parse(hub.settings.custom_data); + + const $publish_progress = $(`
          +

          ${__(`Syncing ${items_to_publish.length} Products`)}

          +
          +
          +
          + +
          `); + + const items_to_publish_container = $(get_item_card_container_html( + items_to_publish, '', get_local_item_card_html)); + + items_to_publish_container.find('.hub-card').addClass('active'); + + $publish_progress.append(items_to_publish_container); + + return $publish_progress; + } + + get_items_and_render(wrapper = this.$wrapper) { + wrapper.find('.results').remove(); + const items = this.get_valid_items(); + + if(!items.then) { + this.render(items, wrapper); + } else { + items.then(r => { + this.fetched_items = r.message; + this.render(r.message, wrapper); + }); + } + } + + render(items, wrapper) { + const items_container = $(get_item_card_container_html(items, '', get_local_item_card_html)); + items_container.addClass('results'); + wrapper.append(items_container); + } + + get_valid_items() { + if(this.unpublished_items.length) { + return this.unpublished_items; + } + return frappe.call( + 'erpnext.hub_node.get_valid_items', + { + search_value: this.search_value + } + ); + } + + publish_selected_items() { + const item_codes_to_publish = []; + this.$wrapper.find('.hub-card.active').map(function () { + item_codes_to_publish.push($(this).attr("data-id")); + }); + + this.unpublished_items = this.fetched_items.filter(item => { + return !item_codes_to_publish.includes(item.item_code); + }); + + const items_to_publish = this.fetched_items.filter(item => { + return item_codes_to_publish.includes(item.item_code); + }); + this.items_to_publish = items_to_publish; + + return frappe.call( + 'erpnext.hub_node.publish_selected_items', + { + items_to_publish: item_codes_to_publish + } + ) + } +} \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/published_products.js b/erpnext/public/js/hub/pages/published_products.js new file mode 100644 index 0000000000..f21c6fa7ff --- /dev/null +++ b/erpnext/public/js/hub/pages/published_products.js @@ -0,0 +1,23 @@ +import SubPage from './base_page'; +import { get_item_card_container_html } from '../helpers'; + +erpnext.hub.PublishedProducts = class PublishedProducts extends SubPage { + get_items_and_render() { + this.$wrapper.find('.hub-card-container').empty(); + this.get_published_products() + .then(items => this.render(items)); + } + + refresh() { + this.get_items_and_render(); + } + + render(items) { + const items_container = $(get_item_card_container_html(items, __('Your Published Products'))); + this.$wrapper.append(items_container); + } + + get_published_products() { + return hub.call('get_items_by_seller', { hub_seller: hub.settings.company_email }); + } +} \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/register.js b/erpnext/public/js/hub/pages/register.js new file mode 100644 index 0000000000..d8966f12a1 --- /dev/null +++ b/erpnext/public/js/hub/pages/register.js @@ -0,0 +1,110 @@ +import SubPage from './base_page'; + +erpnext.hub.Register = class Register extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.$register_container = $(`
          `) + .appendTo(this.$wrapper); + this.$form_container = $('
          ') + .appendTo(this.$wrapper); + } + + refresh() { + this.$register_container.empty(); + this.$form_container.empty(); + this.render(); + } + + render() { + this.make_field_group(); + } + + make_field_group() { + const fields = [ + { + fieldtype: 'Link', + fieldname: 'company', + label: __('Company'), + options: 'Company', + onchange: () => { + const value = this.field_group.get_value('company'); + + if (value) { + frappe.db.get_doc('Company', value) + .then(company => { + this.field_group.set_values({ + country: company.country, + company_email: company.email, + currency: company.default_currency + }); + }); + } + } + }, + { + fieldname: 'company_email', + label: __('Email'), + fieldtype: 'Data' + }, + { + fieldname: 'country', + label: __('Country'), + fieldtype: 'Read Only' + }, + { + fieldname: 'currency', + label: __('Currency'), + fieldtype: 'Read Only' + }, + { + fieldtype: 'Text', + label: __('About your Company'), + fieldname: 'company_description' + } + ]; + + this.field_group = new frappe.ui.FieldGroup({ + parent: this.$form_container, + fields + }); + + this.field_group.make(); + + const default_company = frappe.defaults.get_default('company'); + this.field_group.set_value('company', default_company); + + this.$form_container.find('.form-column').append(` +
          + +
          + `); + + this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller')) + + this.$form_container.on('click', '.btn-register', (e) => { + const form_values = this.field_group.get_values(); + + let values_filled = true; + const mandatory_fields = ['company', 'company_email', 'company_description']; + mandatory_fields.forEach(field => { + const value = form_values[field]; + if (!value) { + this.field_group.set_df_property(field, 'reqd', 1); + values_filled = false; + } + }); + if (!values_filled) return; + + frappe.call({ + method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', + args: form_values, + btn: $(e.currentTarget) + }).then(() => { + frappe.set_route('marketplace', 'publish'); + + // custom jquery event + this.$wrapper.trigger('seller-registered'); + }); + }); + } +} diff --git a/erpnext/public/js/hub/pages/search.js b/erpnext/public/js/hub/pages/search.js new file mode 100644 index 0000000000..276c9bc668 --- /dev/null +++ b/erpnext/public/js/hub/pages/search.js @@ -0,0 +1,34 @@ +import SubPage from './base_page'; +import { make_search_bar, get_item_card_container_html } from '../helpers'; + +erpnext.hub.SearchPage = class SearchPage extends SubPage { + make_wrapper() { + super.make_wrapper(); + + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + frappe.set_route('marketplace', 'search', keyword); + } + }); + } + + refresh() { + this.keyword = frappe.get_route()[2] || ''; + this.$wrapper.find('input').val(this.keyword); + + this.get_items_by_keyword(this.keyword) + .then(items => this.render(items)); + } + + get_items_by_keyword(keyword) { + return hub.call('get_items_by_keyword', { keyword }); + } + + render(items) { + this.$wrapper.find('.hub-card-container').remove(); + const title = this.keyword ? __('Search results for "{0}"', [this.keyword]) : ''; + const html = get_item_card_container_html(items, title); + this.$wrapper.append(html); + } +} \ No newline at end of file From fbe9a46d51f9155c9d1a85a0dba547d84ab4cb31 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 1 Aug 2018 15:56:39 +0530 Subject: [PATCH 044/154] Add Marketplace link to user dropdown in toolbar --- erpnext/public/js/hub/hub_factory.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js index b073720430..21f3e8d683 100644 --- a/erpnext/public/js/hub/hub_factory.js +++ b/erpnext/public/js/hub/hub_factory.js @@ -22,3 +22,11 @@ frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views. }); } } + +$(document).on('toolbar_setup', () => { + $('#toolbar-user .navbar-reload').after(` +
        • + ${__('Marketplace')} +
        • + `) +}) \ No newline at end of file From de85f243830c64a3b1fe20d440b4d913ad5380eb Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 1 Aug 2018 15:57:13 +0530 Subject: [PATCH 045/154] Rename base_page to subpage --- erpnext/public/js/hub/pages/category.js | 2 +- erpnext/public/js/hub/pages/favourites.js | 2 +- erpnext/public/js/hub/pages/home.js | 2 +- erpnext/public/js/hub/pages/not_found.js | 2 +- erpnext/public/js/hub/pages/profile.js | 2 +- erpnext/public/js/hub/pages/publish.js | 2 +- erpnext/public/js/hub/pages/published_products.js | 2 +- erpnext/public/js/hub/pages/register.js | 2 +- erpnext/public/js/hub/pages/search.js | 2 +- .../public/js/hub/pages/{base_page.js => subpage.js} | 10 ++++++++++ 10 files changed, 19 insertions(+), 9 deletions(-) rename erpnext/public/js/hub/pages/{base_page.js => subpage.js} (74%) diff --git a/erpnext/public/js/hub/pages/category.js b/erpnext/public/js/hub/pages/category.js index 21dcb328fd..de1281cd18 100644 --- a/erpnext/public/js/hub/pages/category.js +++ b/erpnext/public/js/hub/pages/category.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; import { get_item_card_container_html } from '../helpers'; erpnext.hub.Category = class Category extends SubPage { diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js index 9605eb19b0..704caeabbb 100644 --- a/erpnext/public/js/hub/pages/favourites.js +++ b/erpnext/public/js/hub/pages/favourites.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; import { get_item_card_container_html } from '../helpers'; erpnext.hub.Favourites = class Favourites extends SubPage { diff --git a/erpnext/public/js/hub/pages/home.js b/erpnext/public/js/hub/pages/home.js index ff37e812c3..6a49f6265b 100644 --- a/erpnext/public/js/hub/pages/home.js +++ b/erpnext/public/js/hub/pages/home.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; import { make_search_bar, get_item_card_container_html } from '../helpers'; erpnext.hub.Home = class Home extends SubPage { diff --git a/erpnext/public/js/hub/pages/not_found.js b/erpnext/public/js/hub/pages/not_found.js index a83d8817de..3b864464f3 100644 --- a/erpnext/public/js/hub/pages/not_found.js +++ b/erpnext/public/js/hub/pages/not_found.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; erpnext.hub.NotFound = class NotFound extends SubPage { refresh() { diff --git a/erpnext/public/js/hub/pages/profile.js b/erpnext/public/js/hub/pages/profile.js index 6dd1f8716a..a38cde4276 100644 --- a/erpnext/public/js/hub/pages/profile.js +++ b/erpnext/public/js/hub/pages/profile.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; erpnext.hub.Profile = class Profile extends SubPage { make_wrapper() { diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index 6e8caabb4c..2bcc0c6e45 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; import { make_search_bar, get_item_card_container_html, get_local_item_card_html } from '../helpers'; erpnext.hub.Publish = class Publish extends SubPage { diff --git a/erpnext/public/js/hub/pages/published_products.js b/erpnext/public/js/hub/pages/published_products.js index f21c6fa7ff..11cce4d949 100644 --- a/erpnext/public/js/hub/pages/published_products.js +++ b/erpnext/public/js/hub/pages/published_products.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; import { get_item_card_container_html } from '../helpers'; erpnext.hub.PublishedProducts = class PublishedProducts extends SubPage { diff --git a/erpnext/public/js/hub/pages/register.js b/erpnext/public/js/hub/pages/register.js index d8966f12a1..b95ec04444 100644 --- a/erpnext/public/js/hub/pages/register.js +++ b/erpnext/public/js/hub/pages/register.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; erpnext.hub.Register = class Register extends SubPage { make_wrapper() { diff --git a/erpnext/public/js/hub/pages/search.js b/erpnext/public/js/hub/pages/search.js index 276c9bc668..dbaecba226 100644 --- a/erpnext/public/js/hub/pages/search.js +++ b/erpnext/public/js/hub/pages/search.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; import { make_search_bar, get_item_card_container_html } from '../helpers'; erpnext.hub.SearchPage = class SearchPage extends SubPage { diff --git a/erpnext/public/js/hub/pages/base_page.js b/erpnext/public/js/hub/pages/subpage.js similarity index 74% rename from erpnext/public/js/hub/pages/base_page.js rename to erpnext/public/js/hub/pages/subpage.js index 70248da366..a030e7e33b 100644 --- a/erpnext/public/js/hub/pages/base_page.js +++ b/erpnext/public/js/hub/pages/subpage.js @@ -3,6 +3,16 @@ export default class SubPage { this.$parent = $(parent); this.make_wrapper(options); + // generic action handler + this.$wrapper.on('click', '[data-action]', e => { + const $this = $(e.currentTarget); + const action = $this.data().action; + + if (action && this[action]) { + this[action].apply(this); + } + }) + // handle broken images after every render if (this.render) { this._render = this.render.bind(this); From 2de046fbed103cec165623acb0d295ecefb6b22e Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 1 Aug 2018 15:57:28 +0530 Subject: [PATCH 046/154] Add generic action handler to subpage --- erpnext/public/js/hub/pages/item.js | 45 ++++++++++++----------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index dabddeaab7..b3c63c85f2 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -1,4 +1,4 @@ -import SubPage from './base_page'; +import SubPage from './subpage'; import { get_rating_html } from '../helpers'; erpnext.hub.Item = class Item extends SubPage { @@ -90,20 +90,23 @@ erpnext.hub.Item = class Item extends SubPage { const rating_html = get_rating_html(item.average_rating); const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet'); - let edit_buttons_html = ''; + let menu_items = ''; if(this.own_item) { - edit_buttons_html = `
          - - -
          `; + menu_items = ` +
        • ${__('Edit Details')}
        • +
        • ${__('Unpublish')}
        • `; + } else { + menu_items = ` +
        • ${__('Report this item')}
        • + `; } const html = `
          - +
          @@ -126,7 +129,6 @@ erpnext.hub.Item = class Item extends SubPage { ` : `

          ${__('No description')}

          ` }

          - ${edit_buttons_html}
          @@ -173,10 +174,6 @@ erpnext.hub.Item = class Item extends SubPage { this.$wrapper.html(html); - if(this.own_item) { - this.bind_edit_buttons(); - } - this.make_review_area(); this.get_reviews() @@ -186,21 +183,17 @@ erpnext.hub.Item = class Item extends SubPage { }); } - bind_edit_buttons() { - this.edit_dialog = new frappe.ui.Dialog({ - title: "Edit Your Product", - fields: [] - }); - - this.$wrapper.find('.edit-item').on('click', this.on_edit.bind(this)); - this.$wrapper.find('.unpublish').on('click', this.on_unpublish.bind(this)); - } - - on_edit() { + edit_details() { + if (!this.edit_dialog) { + this.edit_dialog = new frappe.ui.Dialog({ + title: "Edit Your Product", + fields: [] + }); + } this.edit_dialog.show(); } - on_unpublish() { + unpublish_item() { if(!this.unpublish_dialog) { this.unpublish_dialog = new frappe.ui.Dialog({ title: "Edit Your Product", From 5249307f27ed73d16dce1407ae1d1c6432fdaa36 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 1 Aug 2018 16:29:40 +0530 Subject: [PATCH 047/154] Prevent access of registered routes --- erpnext/public/js/hub/marketplace.js | 32 +++++++++++---- erpnext/public/js/hub/pages/item.js | 54 ++++++++++++------------- erpnext/public/js/hub/pages/messages.js | 7 ++++ 3 files changed, 56 insertions(+), 37 deletions(-) create mode 100644 erpnext/public/js/hub/pages/messages.js diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index ab0d4f7e71..d393605d05 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -8,6 +8,7 @@ import './pages/register'; import './pages/profile'; import './pages/publish'; import './pages/published_products'; +import './pages/messages'; import './pages/not_found'; // helpers @@ -62,11 +63,17 @@ erpnext.hub.Marketplace = class Marketplace { $nav_group.empty(); const user_specific_items_html = this.registered - ? `
        • + ? `
        • + ${__('Favorites')} +
        • +
        • ${__('Your Profile')}
        • ${__('Publish Products')} +
        • +
        • + ${__('Messages')}
        • ` : `
        • @@ -77,9 +84,6 @@ erpnext.hub.Marketplace = class Marketplace {
        • ${__('Browse')}
        • -
        • - ${__('Favorites')} -
        • ${user_specific_items_html} `); } @@ -148,10 +152,6 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages.home = new erpnext.hub.Home(this.$body); } - if (route[1] === 'favourites' && !this.subpages.favourites) { - this.subpages.favourites = new erpnext.hub.Favourites(this.$body); - } - if (route[1] === 'search' && !this.subpages.search) { this.subpages.search = new erpnext.hub.SearchPage(this.$body); } @@ -172,6 +172,11 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages.register = new erpnext.hub.Register(this.$body); } + // registered seller routes + if (route[1] === 'favourites' && !this.subpages.favourites) { + this.subpages.favourites = new erpnext.hub.Favourites(this.$body); + } + if (route[1] === 'profile' && !this.subpages.profile) { this.subpages.profile = new erpnext.hub.Profile(this.$body); } @@ -184,6 +189,17 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages['my-products'] = new erpnext.hub.PublishedProducts(this.$body); } + if (route[1] === 'messages' && !this.subpages['messages']) { + this.subpages['messages'] = new erpnext.hub.Messages(this.$body); + } + + // dont allow unregistered users to access registered routes + const registered_routes = ['favourites', 'profile', 'publish', 'my-products', 'messages']; + if (!hub.settings.registered && registered_routes.includes(route[1])) { + frappe.set_route('marketplace', 'home'); + return; + } + if (!Object.keys(this.subpages).includes(route[1])) { if (!this.subpages.not_found) { this.subpages.not_found = new erpnext.hub.NotFound(this.$body); diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index b3c63c85f2..559a4d6d6a 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -2,11 +2,6 @@ import SubPage from './subpage'; import { get_rating_html } from '../helpers'; erpnext.hub.Item = class Item extends SubPage { - make_wrapper() { - super.make_wrapper(); - this.setup_events(); - } - refresh() { this.show_skeleton(); this.hub_item_code = frappe.get_route()[2]; @@ -45,29 +40,6 @@ erpnext.hub.Item = class Item extends SubPage { this.$wrapper.html(skeleton); } - setup_events() { - this.$wrapper.on('click', '.btn-contact-seller', () => { - const d = new frappe.ui.Dialog({ - title: __('Send a message'), - fields: [ - { - fieldname: 'to', - fieldtype: 'Read Only', - label: __('To'), - default: this.item.company - }, - { - fieldtype: 'Text', - fieldname: 'message', - label: __('Message') - } - ] - }); - - d.show(); - }); - } - get_item(hub_item_code) { return hub.call('get_item_details', { hub_item_code }); } @@ -150,7 +122,7 @@ erpnext.hub.Item = class Item extends SubPage {
          -
          @@ -204,6 +176,30 @@ erpnext.hub.Item = class Item extends SubPage { this.unpublish_dialog.show(); } + contact_seller() { + const d = new frappe.ui.Dialog({ + title: __('Send a message'), + fields: [ + { + fieldname: 'to', + fieldtype: 'Read Only', + label: __('To'), + default: this.item.company + }, + { + fieldtype: 'Text', + fieldname: 'message', + label: __('Message') + } + ], + primary_action: ({ message }) => { + if (!message) return; + } + }); + + d.show(); + } + make_review_area() { this.comment_area = new frappe.ui.ReviewArea({ parent: this.$wrapper.find('.timeline-head').empty(), diff --git a/erpnext/public/js/hub/pages/messages.js b/erpnext/public/js/hub/pages/messages.js new file mode 100644 index 0000000000..ac2057dcf3 --- /dev/null +++ b/erpnext/public/js/hub/pages/messages.js @@ -0,0 +1,7 @@ +import SubPage from './subpage'; + +erpnext.hub.Messages = class Messages extends SubPage { + refresh() { + + } +} \ No newline at end of file From 6495d532a61a52da60b376e60826633fb7876fcb Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Wed, 1 Aug 2018 16:38:39 +0530 Subject: [PATCH 048/154] [restructure] make api.py --- erpnext/hub_node/__init__.py | 277 ----------------- erpnext/hub_node/api.py | 285 ++++++++++++++++++ .../item_to_hub_item/item_to_hub_item.json | 62 ++-- .../hub_sync/hub_sync.json | 28 +- erpnext/public/js/hub/hub_call.js | 2 +- erpnext/public/js/hub/pages/category.js | 4 +- erpnext/public/js/hub/pages/publish.js | 6 +- 7 files changed, 336 insertions(+), 328 deletions(-) create mode 100644 erpnext/hub_node/api.py diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 88570e56cc..968445829f 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -14,280 +14,3 @@ def enable_hub(): hub_settings.register() frappe.db.commit() return hub_settings - -@frappe.whitelist() -def call_hub_method(method, params=None): - connection = get_hub_connection() - - if type(params) == unicode: - params = json.loads(params) - - params.update({ - 'cmd': 'hub.hub.api.' + method - }) - - response = connection.post_request(params) - return response - - -#### LOCAL ITEMS -@frappe.whitelist() -def get_valid_items(search_value=''): - items = frappe.get_list( - 'Item', - fields=["*"], - filters={ - 'item_name': ['like', '%' + search_value + '%'], - 'publish_in_hub': 0 - }, - order_by="modified desc" - ) - - valid_items = filter(lambda x: x.image and x.description, items) - - def attach_source_type(item): - item.source_type = "local" - return item - - valid_items = map(lambda x: attach_source_type(x), valid_items) - return valid_items - -@frappe.whitelist() -def publish_selected_items(items_to_publish): - items_to_publish = json.loads(items_to_publish) - if not len(items_to_publish): - return - - for item_code in items_to_publish: - frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - - try: - hub_settings = frappe.get_doc('Hub Settings') - item_sync_preprocess() - hub_settings.sync() - except Exception as e: - frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0) - frappe.throw(e) - -def item_sync_preprocess(): - # Call Hub to make a new activity - # and return an activity ID - # that will be used as the remote ID for the Migration Run - - hub_seller = frappe.db.get_value("Hub Settings", "Hub Settings", "company_email") - - response = call_hub_method('add_hub_seller_activity', { - 'hub_seller': hub_seller, - 'activity_details': json.dumps({ - 'subject': 'Publishing items', - 'status': 'Success' - }) - }) - - if response: - frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1) - return response - else: - frappe.throw('Unable to update remote activity') - -def item_sync_postprocess(sync_details): - hub_seller = frappe.db.get_value("Hub Settings", "Hub Settings", "company_email") - - response = call_hub_method('add_hub_seller_activity', { - 'hub_seller': hub_seller, - 'activity_details': json.dumps({ - 'subject': 'Publishing items:' + sync_details['status'], - 'content': json.dumps(sync_details['stats']) - }) - }) - - if response: - frappe.db.set_value('Hub Settings', 'Hub Settings', 'sync_in_progress', 0) - frappe.db.set_value('Hub Settings', 'Hub Settings', 'last_sync_datetime', frappe.utils.now()) - else: - frappe.throw('Unable to update remote activity') - -@frappe.whitelist() -def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): - doctype = 'Hub Item' - hub_settings = frappe.get_doc('Hub Settings') - item_names_str = hub_settings.get('custom_data') or '[]' - item_names = json.loads(item_names_str) - filters = json.dumps({ - 'hub_item_code': ['in', item_names] - }) - return get_list(doctype, start, limit, fields, filters, order_by) - -@frappe.whitelist() -def update_wishlist_item(item_name, remove=0): - remove = int(remove) - hub_settings = frappe.get_doc('Hub Settings') - data = hub_settings.get('custom_data') - if not data or not json.loads(data): - data = '[]' - hub_settings.custom_data = data - hub_settings.save() - - item_names_str = data - item_names = json.loads(item_names_str) - if not remove and item_name not in item_names: - item_names.append(item_name) - if remove and item_name in item_names: - item_names.remove(item_name) - - item_names_str = json.dumps(item_names) - - hub_settings.custom_data = item_names_str - hub_settings.save() - - -@frappe.whitelist() -def update_category(hub_item_code, category): - connection = get_hub_connection() - - # args = frappe._dict(dict( - # doctype='Hub Category', - # hub_category_name=category - # )) - # response = connection.insert('Hub Category', args) - - response = connection.update('Hub Item', frappe._dict(dict( - doctype='Hub Item', - hub_category = category - )), hub_item_code) - - return response - -def get_hub_connection(): - if frappe.db.exists('Data Migration Connector', 'Hub Connector'): - hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector') - hub_connection = hub_connector.get_connection() - return hub_connection.connection - - # read-only connection - hub_connection = FrappeClient(frappe.conf.hub_url) - return hub_connection - -def make_opportunity(buyer_name, email_id): - buyer_name = "HUB-" + buyer_name - - if not frappe.db.exists('Lead', {'email_id': email_id}): - lead = frappe.new_doc("Lead") - lead.lead_name = buyer_name - lead.email_id = email_id - lead.save(ignore_permissions=True) - - o = frappe.new_doc("Opportunity") - o.enquiry_from = "Lead" - o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"] - o.save(ignore_permissions=True) - -@frappe.whitelist() -def make_rfq_and_send_opportunity(item, supplier): - supplier = make_supplier(supplier) - contact = make_contact(supplier) - item = make_item(item) - rfq = make_rfq(item, supplier, contact) - status = send_opportunity(contact) - - return { - 'rfq': rfq, - 'hub_document_created': status - } - -def make_supplier(supplier): - # make supplier if not already exists - supplier = frappe._dict(json.loads(supplier)) - - if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}): - supplier_doc = frappe.get_doc({ - 'doctype': 'Supplier', - 'supplier_name': supplier.supplier_name, - 'supplier_group': supplier.supplier_group, - 'supplier_email': supplier.supplier_email - }).insert() - else: - supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name) - - return supplier_doc - -def make_contact(supplier): - contact_name = get_default_contact('Supplier', supplier.supplier_name) - # make contact if not already exists - if not contact_name: - contact = frappe.get_doc({ - 'doctype': 'Contact', - 'first_name': supplier.supplier_name, - 'email_id': supplier.supplier_email, - 'is_primary_contact': 1, - 'links': [ - {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name} - ] - }).insert() - else: - contact = frappe.get_doc('Contact', contact_name) - - return contact - -def make_item(item): - # make item if not already exists - item = frappe._dict(json.loads(item)) - - if not frappe.db.exists('Item', {'item_code': item.item_code}): - item_doc = frappe.get_doc({ - 'doctype': 'Item', - 'item_code': item.item_code, - 'item_group': item.item_group, - 'is_item_from_hub': 1 - }).insert() - else: - item_doc = frappe.get_doc('Item', item.item_code) - - return item_doc - -def make_rfq(item, supplier, contact): - # make rfq - rfq = frappe.get_doc({ - 'doctype': 'Request for Quotation', - 'transaction_date': nowdate(), - 'status': 'Draft', - 'company': frappe.db.get_single_value('Hub Settings', 'company'), - 'message_for_supplier': 'Please supply the specified items at the best possible rates', - 'suppliers': [ - { 'supplier': supplier.name, 'contact': contact.name } - ], - 'items': [ - { - 'item_code': item.item_code, - 'qty': 1, - 'schedule_date': nowdate(), - 'warehouse': item.default_warehouse or get_root_of("Warehouse"), - 'description': item.description, - 'uom': item.stock_uom - } - ] - }).insert() - - rfq.save() - rfq.submit() - return rfq - -def send_opportunity(contact): - # Make Hub Message on Hub with lead data - doc = { - 'doctype': 'Lead', - 'lead_name': frappe.db.get_single_value('Hub Settings', 'company'), - 'email_id': frappe.db.get_single_value('Hub Settings', 'user') - } - - args = frappe._dict(dict( - doctype='Hub Message', - reference_doctype='Lead', - data=json.dumps(doc), - user=contact.email_id - )) - - connection = get_hub_connection() - response = connection.insert('Hub Message', args) - - return response.ok diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py new file mode 100644 index 0000000000..a1d9e8a2f9 --- /dev/null +++ b/erpnext/hub_node/api.py @@ -0,0 +1,285 @@ +from __future__ import unicode_literals +import frappe, requests, json +from frappe.utils import now +from frappe.frappeclient import FrappeClient + +@frappe.whitelist() +def call_hub_method(method, params=None): + connection = get_hub_connection() + + if type(params) == unicode: + params = json.loads(params) + + params.update({ + 'cmd': 'hub.hub.api.' + method + }) + + response = connection.post_request(params) + return response + + +#### LOCAL ITEMS +@frappe.whitelist() +def get_valid_items(search_value=''): + items = frappe.get_list( + 'Item', + fields=["*"], + filters={ + 'item_name': ['like', '%' + search_value + '%'], + 'publish_in_hub': 0 + }, + order_by="modified desc" + ) + + valid_items = filter(lambda x: x.image and x.description, items) + + def attach_source_type(item): + item.source_type = "local" + return item + + valid_items = map(lambda x: attach_source_type(x), valid_items) + return valid_items + +@frappe.whitelist() +def publish_selected_items(items_to_publish): + items_to_publish = json.loads(items_to_publish) + if not len(items_to_publish): + return + + for item_code in items_to_publish: + frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) + + try: + hub_settings = frappe.get_doc('Hub Settings') + item_sync_preprocess() + hub_settings.sync() + except Exception as e: + frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0) + frappe.throw(e) + +def item_sync_preprocess(): + # Call Hub to make a new activity + # and return an activity ID + # that will be used as the remote ID for the Migration Run + + hub_seller = frappe.db.get_value("Hub Settings", "Hub Settings", "company_email") + + response = call_hub_method('add_hub_seller_activity', { + 'hub_seller': hub_seller, + 'activity_details': json.dumps({ + 'subject': 'Publishing items', + 'status': 'Success' + }) + }) + + if response: + frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1) + return response + else: + frappe.throw('Unable to update remote activity') + +def item_sync_postprocess(sync_details): + hub_seller = frappe.db.get_value("Hub Settings", "Hub Settings", "company_email") + + response = call_hub_method('add_hub_seller_activity', { + 'hub_seller': hub_seller, + 'activity_details': json.dumps({ + 'subject': 'Publishing items:' + sync_details['status'], + 'content': json.dumps(sync_details['stats']) + }) + }) + + if response: + frappe.db.set_value('Hub Settings', 'Hub Settings', 'sync_in_progress', 0) + frappe.db.set_value('Hub Settings', 'Hub Settings', 'last_sync_datetime', frappe.utils.now()) + else: + frappe.throw('Unable to update remote activity') + +def get_hub_connection(): + if frappe.db.exists('Data Migration Connector', 'Hub Connector'): + hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector') + hub_connection = hub_connector.get_connection() + return hub_connection.connection + + # read-only connection + hub_connection = FrappeClient(frappe.conf.hub_url) + return hub_connection + + + +# Legacy functionality +# ============================================================================= + +@frappe.whitelist() +def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): + doctype = 'Hub Item' + hub_settings = frappe.get_doc('Hub Settings') + item_names_str = hub_settings.get('custom_data') or '[]' + item_names = json.loads(item_names_str) + filters = json.dumps({ + 'hub_item_code': ['in', item_names] + }) + return get_list(doctype, start, limit, fields, filters, order_by) + +@frappe.whitelist() +def update_wishlist_item(item_name, remove=0): + remove = int(remove) + hub_settings = frappe.get_doc('Hub Settings') + data = hub_settings.get('custom_data') + if not data or not json.loads(data): + data = '[]' + hub_settings.custom_data = data + hub_settings.save() + + item_names_str = data + item_names = json.loads(item_names_str) + if not remove and item_name not in item_names: + item_names.append(item_name) + if remove and item_name in item_names: + item_names.remove(item_name) + + item_names_str = json.dumps(item_names) + + hub_settings.custom_data = item_names_str + hub_settings.save() + +@frappe.whitelist() +def update_category(hub_item_code, category): + connection = get_hub_connection() + + # args = frappe._dict(dict( + # doctype='Hub Category', + # hub_category_name=category + # )) + # response = connection.insert('Hub Category', args) + + response = connection.update('Hub Item', frappe._dict(dict( + doctype='Hub Item', + hub_category = category + )), hub_item_code) + + return response + +def make_opportunity(buyer_name, email_id): + buyer_name = "HUB-" + buyer_name + + if not frappe.db.exists('Lead', {'email_id': email_id}): + lead = frappe.new_doc("Lead") + lead.lead_name = buyer_name + lead.email_id = email_id + lead.save(ignore_permissions=True) + + o = frappe.new_doc("Opportunity") + o.enquiry_from = "Lead" + o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"] + o.save(ignore_permissions=True) + +@frappe.whitelist() +def make_rfq_and_send_opportunity(item, supplier): + supplier = make_supplier(supplier) + contact = make_contact(supplier) + item = make_item(item) + rfq = make_rfq(item, supplier, contact) + status = send_opportunity(contact) + + return { + 'rfq': rfq, + 'hub_document_created': status + } + +def make_supplier(supplier): + # make supplier if not already exists + supplier = frappe._dict(json.loads(supplier)) + + if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}): + supplier_doc = frappe.get_doc({ + 'doctype': 'Supplier', + 'supplier_name': supplier.supplier_name, + 'supplier_group': supplier.supplier_group, + 'supplier_email': supplier.supplier_email + }).insert() + else: + supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name) + + return supplier_doc + +def make_contact(supplier): + contact_name = get_default_contact('Supplier', supplier.supplier_name) + # make contact if not already exists + if not contact_name: + contact = frappe.get_doc({ + 'doctype': 'Contact', + 'first_name': supplier.supplier_name, + 'email_id': supplier.supplier_email, + 'is_primary_contact': 1, + 'links': [ + {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name} + ] + }).insert() + else: + contact = frappe.get_doc('Contact', contact_name) + + return contact + +def make_item(item): + # make item if not already exists + item = frappe._dict(json.loads(item)) + + if not frappe.db.exists('Item', {'item_code': item.item_code}): + item_doc = frappe.get_doc({ + 'doctype': 'Item', + 'item_code': item.item_code, + 'item_group': item.item_group, + 'is_item_from_hub': 1 + }).insert() + else: + item_doc = frappe.get_doc('Item', item.item_code) + + return item_doc + +def make_rfq(item, supplier, contact): + # make rfq + rfq = frappe.get_doc({ + 'doctype': 'Request for Quotation', + 'transaction_date': nowdate(), + 'status': 'Draft', + 'company': frappe.db.get_single_value('Hub Settings', 'company'), + 'message_for_supplier': 'Please supply the specified items at the best possible rates', + 'suppliers': [ + { 'supplier': supplier.name, 'contact': contact.name } + ], + 'items': [ + { + 'item_code': item.item_code, + 'qty': 1, + 'schedule_date': nowdate(), + 'warehouse': item.default_warehouse or get_root_of("Warehouse"), + 'description': item.description, + 'uom': item.stock_uom + } + ] + }).insert() + + rfq.save() + rfq.submit() + return rfq + +def send_opportunity(contact): + # Make Hub Message on Hub with lead data + doc = { + 'doctype': 'Lead', + 'lead_name': frappe.db.get_single_value('Hub Settings', 'company'), + 'email_id': frappe.db.get_single_value('Hub Settings', 'user') + } + + args = frappe._dict(dict( + doctype='Hub Message', + reference_doctype='Lead', + data=json.dumps(doc), + user=contact.email_id + )) + + connection = get_hub_connection() + response = connection.insert('Hub Message', args) + + return response.ok diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json index edcf91a370..3ace088a80 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json @@ -1,45 +1,45 @@ { - "condition": "{\"publish_in_hub\": 1}", - "creation": "2017-09-07 13:27:52.726350", - "docstatus": 0, - "doctype": "Data Migration Mapping", + "condition": "{\"publish_in_hub\": 1}", + "creation": "2017-09-07 13:27:52.726350", + "docstatus": 0, + "doctype": "Data Migration Mapping", "fields": [ { - "is_child_table": 0, - "local_fieldname": "item_code", + "is_child_table": 0, + "local_fieldname": "item_code", "remote_fieldname": "item_code" - }, + }, { - "is_child_table": 0, - "local_fieldname": "item_name", + "is_child_table": 0, + "local_fieldname": "item_name", "remote_fieldname": "item_name" - }, + }, { - "is_child_table": 0, - "local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')", + "is_child_table": 0, + "local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')", "remote_fieldname": "hub_seller" - }, + }, { - "is_child_table": 0, - "local_fieldname": "image", + "is_child_table": 0, + "local_fieldname": "image", "remote_fieldname": "image" - }, + }, { - "is_child_table": 0, - "local_fieldname": "item_group", + "is_child_table": 0, + "local_fieldname": "item_group", "remote_fieldname": "item_group" } - ], - "idx": 1, - "local_doctype": "Item", - "mapping_name": "Item to Hub Item", - "mapping_type": "Push", - "migration_id_field": "hub_sync_id", - "modified": "2018-07-28 22:25:35.289335", - "modified_by": "cave@aperture.com", - "name": "Item to Hub Item", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Hub Item", + ], + "idx": 1, + "local_doctype": "Item", + "mapping_name": "Item to Hub Item", + "mapping_type": "Push", + "migration_id_field": "hub_sync_id", + "modified": "2018-08-01 16:37:09.170546", + "modified_by": "Administrator", + "name": "Item to Hub Item", + "owner": "Administrator", + "page_length": 10, + "remote_objectname": "Hub Item", "remote_primary_key": "item_code" -} +} \ No newline at end of file diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json index 7490e432ac..1f772b68f0 100644 --- a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json +++ b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json @@ -1,19 +1,19 @@ { - "creation": "2017-09-07 11:39:38.445902", - "docstatus": 0, - "doctype": "Data Migration Plan", - "idx": 1, + "creation": "2017-09-07 11:39:38.445902", + "docstatus": 0, + "doctype": "Data Migration Plan", + "idx": 1, "mappings": [ { - "enabled": 1, + "enabled": 1, "mapping": "Item to Hub Item" } - ], - "modified": "2018-07-28 22:25:35.216172", - "modified_by": "cave@aperture.com", - "module": "Hub Node", - "name": "Hub Sync", - "owner": "Administrator", - "plan_name": "Hub Sync", - "postprocess_method": "erpnext.hub_node.item_sync_postprocess" -} + ], + "modified": "2018-08-01 16:37:09.027512", + "modified_by": "Administrator", + "module": "Hub Node", + "name": "Hub Sync", + "owner": "Administrator", + "plan_name": "Hub Sync", + "postprocess_method": "erpnext.hub_node.api.item_sync_postprocess" +} \ No newline at end of file diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js index e0fead3e50..6786cf6f90 100644 --- a/erpnext/public/js/hub/hub_call.js +++ b/erpnext/public/js/hub/hub_call.js @@ -19,7 +19,7 @@ hub.call = function call_hub_method(method, args={}) { }, timeout); frappe.call({ - method: 'erpnext.hub_node.call_hub_method', + method: 'erpnext.hub_node.api.call_hub_method', args: { method, params: args diff --git a/erpnext/public/js/hub/pages/category.js b/erpnext/public/js/hub/pages/category.js index de1281cd18..87311abe72 100644 --- a/erpnext/public/js/hub/pages/category.js +++ b/erpnext/public/js/hub/pages/category.js @@ -12,7 +12,7 @@ erpnext.hub.Category = class Category extends SubPage { get_items_for_category(category) { this.$wrapper.find('.hub-card-container').empty(); - return frappe.call('erpnext.hub_node.get_list', { + return frappe.call('erpnext.hub_node.api.get_list', { doctype: 'Hub Item', filters: { hub_category: category @@ -24,4 +24,4 @@ erpnext.hub.Category = class Category extends SubPage { const html = get_item_card_container_html(items, __(this.category)); this.$wrapper.append(html) } -} \ No newline at end of file +} diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index 2bcc0c6e45..859782edbf 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -196,7 +196,7 @@ erpnext.hub.Publish = class Publish extends SubPage { return this.unpublished_items; } return frappe.call( - 'erpnext.hub_node.get_valid_items', + 'erpnext.hub_node.api.get_valid_items', { search_value: this.search_value } @@ -219,10 +219,10 @@ erpnext.hub.Publish = class Publish extends SubPage { this.items_to_publish = items_to_publish; return frappe.call( - 'erpnext.hub_node.publish_selected_items', + 'erpnext.hub_node.api.publish_selected_items', { items_to_publish: item_codes_to_publish } ) } -} \ No newline at end of file +} From 4d4791ab3975d41495d6ad27f2dae0c87b139df5 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Wed, 1 Aug 2018 16:42:09 +0530 Subject: [PATCH 049/154] [chore] remove imports --- erpnext/hub_node/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 968445829f..634746caf2 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -2,11 +2,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, requests, json -from frappe.utils import now, nowdate, cint -from frappe.utils.nestedset import get_root_of -from frappe.frappeclient import FrappeClient -from frappe.contacts.doctype.contact.contact import get_default_contact +import frappe @frappe.whitelist() def enable_hub(): From 3aed5c219f7749d08aad1403e415da49ec380649 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Wed, 1 Aug 2018 17:32:13 +0530 Subject: [PATCH 050/154] [hub] make get_items generic, add legacy.py --- erpnext/hub_node/api.py | 185 ------------------ erpnext/hub_node/legacy.py | 178 +++++++++++++++++ erpnext/public/js/hub/pages/item.js | 7 +- .../public/js/hub/pages/published_products.js | 4 +- erpnext/public/js/hub/pages/search.js | 4 +- 5 files changed, 187 insertions(+), 191 deletions(-) create mode 100644 erpnext/hub_node/legacy.py diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index a1d9e8a2f9..559e22a2c8 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -17,8 +17,6 @@ def call_hub_method(method, params=None): response = connection.post_request(params) return response - -#### LOCAL ITEMS @frappe.whitelist() def get_valid_items(search_value=''): items = frappe.get_list( @@ -58,10 +56,6 @@ def publish_selected_items(items_to_publish): frappe.throw(e) def item_sync_preprocess(): - # Call Hub to make a new activity - # and return an activity ID - # that will be used as the remote ID for the Migration Run - hub_seller = frappe.db.get_value("Hub Settings", "Hub Settings", "company_email") response = call_hub_method('add_hub_seller_activity', { @@ -104,182 +98,3 @@ def get_hub_connection(): # read-only connection hub_connection = FrappeClient(frappe.conf.hub_url) return hub_connection - - - -# Legacy functionality -# ============================================================================= - -@frappe.whitelist() -def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): - doctype = 'Hub Item' - hub_settings = frappe.get_doc('Hub Settings') - item_names_str = hub_settings.get('custom_data') or '[]' - item_names = json.loads(item_names_str) - filters = json.dumps({ - 'hub_item_code': ['in', item_names] - }) - return get_list(doctype, start, limit, fields, filters, order_by) - -@frappe.whitelist() -def update_wishlist_item(item_name, remove=0): - remove = int(remove) - hub_settings = frappe.get_doc('Hub Settings') - data = hub_settings.get('custom_data') - if not data or not json.loads(data): - data = '[]' - hub_settings.custom_data = data - hub_settings.save() - - item_names_str = data - item_names = json.loads(item_names_str) - if not remove and item_name not in item_names: - item_names.append(item_name) - if remove and item_name in item_names: - item_names.remove(item_name) - - item_names_str = json.dumps(item_names) - - hub_settings.custom_data = item_names_str - hub_settings.save() - -@frappe.whitelist() -def update_category(hub_item_code, category): - connection = get_hub_connection() - - # args = frappe._dict(dict( - # doctype='Hub Category', - # hub_category_name=category - # )) - # response = connection.insert('Hub Category', args) - - response = connection.update('Hub Item', frappe._dict(dict( - doctype='Hub Item', - hub_category = category - )), hub_item_code) - - return response - -def make_opportunity(buyer_name, email_id): - buyer_name = "HUB-" + buyer_name - - if not frappe.db.exists('Lead', {'email_id': email_id}): - lead = frappe.new_doc("Lead") - lead.lead_name = buyer_name - lead.email_id = email_id - lead.save(ignore_permissions=True) - - o = frappe.new_doc("Opportunity") - o.enquiry_from = "Lead" - o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"] - o.save(ignore_permissions=True) - -@frappe.whitelist() -def make_rfq_and_send_opportunity(item, supplier): - supplier = make_supplier(supplier) - contact = make_contact(supplier) - item = make_item(item) - rfq = make_rfq(item, supplier, contact) - status = send_opportunity(contact) - - return { - 'rfq': rfq, - 'hub_document_created': status - } - -def make_supplier(supplier): - # make supplier if not already exists - supplier = frappe._dict(json.loads(supplier)) - - if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}): - supplier_doc = frappe.get_doc({ - 'doctype': 'Supplier', - 'supplier_name': supplier.supplier_name, - 'supplier_group': supplier.supplier_group, - 'supplier_email': supplier.supplier_email - }).insert() - else: - supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name) - - return supplier_doc - -def make_contact(supplier): - contact_name = get_default_contact('Supplier', supplier.supplier_name) - # make contact if not already exists - if not contact_name: - contact = frappe.get_doc({ - 'doctype': 'Contact', - 'first_name': supplier.supplier_name, - 'email_id': supplier.supplier_email, - 'is_primary_contact': 1, - 'links': [ - {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name} - ] - }).insert() - else: - contact = frappe.get_doc('Contact', contact_name) - - return contact - -def make_item(item): - # make item if not already exists - item = frappe._dict(json.loads(item)) - - if not frappe.db.exists('Item', {'item_code': item.item_code}): - item_doc = frappe.get_doc({ - 'doctype': 'Item', - 'item_code': item.item_code, - 'item_group': item.item_group, - 'is_item_from_hub': 1 - }).insert() - else: - item_doc = frappe.get_doc('Item', item.item_code) - - return item_doc - -def make_rfq(item, supplier, contact): - # make rfq - rfq = frappe.get_doc({ - 'doctype': 'Request for Quotation', - 'transaction_date': nowdate(), - 'status': 'Draft', - 'company': frappe.db.get_single_value('Hub Settings', 'company'), - 'message_for_supplier': 'Please supply the specified items at the best possible rates', - 'suppliers': [ - { 'supplier': supplier.name, 'contact': contact.name } - ], - 'items': [ - { - 'item_code': item.item_code, - 'qty': 1, - 'schedule_date': nowdate(), - 'warehouse': item.default_warehouse or get_root_of("Warehouse"), - 'description': item.description, - 'uom': item.stock_uom - } - ] - }).insert() - - rfq.save() - rfq.submit() - return rfq - -def send_opportunity(contact): - # Make Hub Message on Hub with lead data - doc = { - 'doctype': 'Lead', - 'lead_name': frappe.db.get_single_value('Hub Settings', 'company'), - 'email_id': frappe.db.get_single_value('Hub Settings', 'user') - } - - args = frappe._dict(dict( - doctype='Hub Message', - reference_doctype='Lead', - data=json.dumps(doc), - user=contact.email_id - )) - - connection = get_hub_connection() - response = connection.insert('Hub Message', args) - - return response.ok diff --git a/erpnext/hub_node/legacy.py b/erpnext/hub_node/legacy.py new file mode 100644 index 0000000000..87d4e1b4ad --- /dev/null +++ b/erpnext/hub_node/legacy.py @@ -0,0 +1,178 @@ +from __future__ import unicode_literals +import frappe, requests, json +from frappe.utils import now, nowdate +from frappe.frappeclient import FrappeClient + +@frappe.whitelist() +def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): + doctype = 'Hub Item' + hub_settings = frappe.get_doc('Hub Settings') + item_names_str = hub_settings.get('custom_data') or '[]' + item_names = json.loads(item_names_str) + filters = json.dumps({ + 'hub_item_code': ['in', item_names] + }) + return get_list(doctype, start, limit, fields, filters, order_by) + +@frappe.whitelist() +def update_wishlist_item(item_name, remove=0): + remove = int(remove) + hub_settings = frappe.get_doc('Hub Settings') + data = hub_settings.get('custom_data') + if not data or not json.loads(data): + data = '[]' + hub_settings.custom_data = data + hub_settings.save() + + item_names_str = data + item_names = json.loads(item_names_str) + if not remove and item_name not in item_names: + item_names.append(item_name) + if remove and item_name in item_names: + item_names.remove(item_name) + + item_names_str = json.dumps(item_names) + + hub_settings.custom_data = item_names_str + hub_settings.save() + +@frappe.whitelist() +def update_category(hub_item_code, category): + connection = get_hub_connection() + + # args = frappe._dict(dict( + # doctype='Hub Category', + # hub_category_name=category + # )) + # response = connection.insert('Hub Category', args) + + response = connection.update('Hub Item', frappe._dict(dict( + doctype='Hub Item', + hub_category = category + )), hub_item_code) + + return response + +def make_opportunity(buyer_name, email_id): + buyer_name = "HUB-" + buyer_name + + if not frappe.db.exists('Lead', {'email_id': email_id}): + lead = frappe.new_doc("Lead") + lead.lead_name = buyer_name + lead.email_id = email_id + lead.save(ignore_permissions=True) + + o = frappe.new_doc("Opportunity") + o.enquiry_from = "Lead" + o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"] + o.save(ignore_permissions=True) + +@frappe.whitelist() +def make_rfq_and_send_opportunity(item, supplier): + supplier = make_supplier(supplier) + contact = make_contact(supplier) + item = make_item(item) + rfq = make_rfq(item, supplier, contact) + status = send_opportunity(contact) + + return { + 'rfq': rfq, + 'hub_document_created': status + } + +def make_supplier(supplier): + # make supplier if not already exists + supplier = frappe._dict(json.loads(supplier)) + + if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}): + supplier_doc = frappe.get_doc({ + 'doctype': 'Supplier', + 'supplier_name': supplier.supplier_name, + 'supplier_group': supplier.supplier_group, + 'supplier_email': supplier.supplier_email + }).insert() + else: + supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name) + + return supplier_doc + +def make_contact(supplier): + contact_name = get_default_contact('Supplier', supplier.supplier_name) + # make contact if not already exists + if not contact_name: + contact = frappe.get_doc({ + 'doctype': 'Contact', + 'first_name': supplier.supplier_name, + 'email_id': supplier.supplier_email, + 'is_primary_contact': 1, + 'links': [ + {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name} + ] + }).insert() + else: + contact = frappe.get_doc('Contact', contact_name) + + return contact + +def make_item(item): + # make item if not already exists + item = frappe._dict(json.loads(item)) + + if not frappe.db.exists('Item', {'item_code': item.item_code}): + item_doc = frappe.get_doc({ + 'doctype': 'Item', + 'item_code': item.item_code, + 'item_group': item.item_group, + 'is_item_from_hub': 1 + }).insert() + else: + item_doc = frappe.get_doc('Item', item.item_code) + + return item_doc + +def make_rfq(item, supplier, contact): + # make rfq + rfq = frappe.get_doc({ + 'doctype': 'Request for Quotation', + 'transaction_date': nowdate(), + 'status': 'Draft', + 'company': frappe.db.get_single_value('Hub Settings', 'company'), + 'message_for_supplier': 'Please supply the specified items at the best possible rates', + 'suppliers': [ + { 'supplier': supplier.name, 'contact': contact.name } + ], + 'items': [ + { + 'item_code': item.item_code, + 'qty': 1, + 'schedule_date': nowdate(), + 'warehouse': item.default_warehouse or get_root_of("Warehouse"), + 'description': item.description, + 'uom': item.stock_uom + } + ] + }).insert() + + rfq.save() + rfq.submit() + return rfq + +def send_opportunity(contact): + # Make Hub Message on Hub with lead data + doc = { + 'doctype': 'Lead', + 'lead_name': frappe.db.get_single_value('Hub Settings', 'company'), + 'email_id': frappe.db.get_single_value('Hub Settings', 'user') + } + + args = frappe._dict(dict( + doctype='Hub Message', + reference_doctype='Lead', + data=json.dumps(doc), + user=contact.email_id + )) + + connection = get_hub_connection() + response = connection.insert('Hub Message', args) + + return response.ok diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index 559a4d6d6a..609479b0ef 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -41,7 +41,10 @@ erpnext.hub.Item = class Item extends SubPage { } get_item(hub_item_code) { - return hub.call('get_item_details', { hub_item_code }); + return hub.call('get_item_details', { + hub_seller: hub.settings.company_email, + hub_item_code + }); } render(item) { @@ -313,4 +316,4 @@ erpnext.hub.Item = class Item extends SubPage {
          `; } -} \ No newline at end of file +} diff --git a/erpnext/public/js/hub/pages/published_products.js b/erpnext/public/js/hub/pages/published_products.js index 11cce4d949..1b19a51da7 100644 --- a/erpnext/public/js/hub/pages/published_products.js +++ b/erpnext/public/js/hub/pages/published_products.js @@ -18,6 +18,6 @@ erpnext.hub.PublishedProducts = class PublishedProducts extends SubPage { } get_published_products() { - return hub.call('get_items_by_seller', { hub_seller: hub.settings.company_email }); + return hub.call('get_items', { hub_seller: hub.settings.company_email }); } -} \ No newline at end of file +} diff --git a/erpnext/public/js/hub/pages/search.js b/erpnext/public/js/hub/pages/search.js index dbaecba226..33c2b78e20 100644 --- a/erpnext/public/js/hub/pages/search.js +++ b/erpnext/public/js/hub/pages/search.js @@ -22,7 +22,7 @@ erpnext.hub.SearchPage = class SearchPage extends SubPage { } get_items_by_keyword(keyword) { - return hub.call('get_items_by_keyword', { keyword }); + return hub.call('get_items', { keyword }); } render(items) { @@ -31,4 +31,4 @@ erpnext.hub.SearchPage = class SearchPage extends SubPage { const html = get_item_card_container_html(items, title); this.$wrapper.append(html); } -} \ No newline at end of file +} From 2a81b4acd6173be6da3650d943d16fca78dfaf79 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 1 Aug 2018 19:19:53 +0530 Subject: [PATCH 051/154] Messages: first cut --- erpnext/public/js/hub/marketplace.js | 6 +- erpnext/public/js/hub/pages/messages.js | 113 +++++++++++++++++++++++- erpnext/public/less/hub.less | 40 +++++++++ 3 files changed, 155 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index d393605d05..e29ae6fc23 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -131,12 +131,12 @@ erpnext.hub.Marketplace = class Marketplace { } update_sidebar() { - const route = frappe.get_route_str(); - const $sidebar_item = this.$sidebar.find(`[data-route="${route}"]`); + const route = frappe.get_route(); + const route_str = route.slice(0, 2).join('/'); + const $sidebar_item = this.$sidebar.find(`[data-route="${route_str}"]`); const $siblings = this.$sidebar.find('[data-route]'); $siblings.removeClass('active').addClass('text-muted'); - $sidebar_item.addClass('active').removeClass('text-muted'); } diff --git a/erpnext/public/js/hub/pages/messages.js b/erpnext/public/js/hub/pages/messages.js index ac2057dcf3..7bc99a7253 100644 --- a/erpnext/public/js/hub/pages/messages.js +++ b/erpnext/public/js/hub/pages/messages.js @@ -1,7 +1,118 @@ import SubPage from './subpage'; +import { make_search_bar } from '../helpers'; erpnext.hub.Messages = class Messages extends SubPage { - refresh() { + make_wrapper() { + super.make_wrapper(); + const html = ` +
          +
          +
          +
          +
          + ${get_message_area_html()} +
          +
          + `; + + make_search_bar({ + wrapper: this.$wrapper, + on_search: keyword => { + + }, + placeholder: __('Search for messages') + }) + + this.$wrapper.append(html); + + this.message_input = new frappe.ui.CommentArea({ + parent: this.$wrapper.find('.message-input'), + on_submit: (message) => { + this.message_input.reset(); + + // append message html + const $message_list = this.$wrapper.find('.message-list'); + const message_html = get_message_html({ + sender: hub.settings.company_email, + content: message + }); + $message_list.append(message_html); + frappe.dom.scroll_to_bottom($message_list); + + const to_seller = frappe.get_route()[2]; + hub.call('send_message', { + from_seller: hub.settings.company_email, + to_seller, + message + }); + }, + no_wrapper: true + }); } + + refresh() { + this.get_interactions() + .then(sellers => { + const html = sellers.map(get_list_item_html).join(''); + this.$wrapper.find('.seller-list').html(html); + }); + + this.get_messages() + .then(messages => { + const $message_list = this.$wrapper.find('.message-list'); + const html = messages.map(get_message_html).join(''); + $message_list.html(html); + frappe.dom.scroll_to_bottom($message_list); + }); + } + + get_interactions() { + return hub.call('get_sellers_with_interactions', { for_seller: hub.settings.company_email }); + } + + get_messages() { + const against_seller = frappe.get_route()[2]; + if (!against_seller) return Promise.resolve([]); + + return hub.call('get_messages', { + for_seller: hub.settings.company_email, + against_seller: against_seller + }); + } +} + +function get_message_area_html() { + return ` +
          +
          +
          +
          +
          +
          + `; +} + +function get_list_item_html(seller) { + const active_class = frappe.get_route()[2] === seller.email ? 'active' : ''; + + return ` +
          +
          + +
          +
          + ${seller.company} +
          +
          + `; +} + +function get_message_html(message) { + return ` +
          +
          ${message.sender}
          +

          ${message.content}

          +
          + `; } \ No newline at end of file diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index c7277f28d9..da51260e6d 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -221,4 +221,44 @@ body[data-route^="marketplace/"] { .hub-item-dropdown { margin-top: 20px; } + + /* messages page */ + + .message-list-item { + display: flex; + align-items: center; + padding: 8px 12px; + cursor: pointer; + + &:not(.active) { + filter: grayscale(1); + color: @text-muted; + } + + &:hover { + background-color: @light-bg; + } + + .list-item-left { + width: 30px; + border-radius: 4px; + overflow: hidden; + margin-right: 15px; + } + + .list-item-body { + font-weight: bold; + padding-bottom: 1px; + } + } + + .message-list { + overflow: scroll; + } + + .message-area { + border-radius: 4px; + justify-content: space-between; + height: calc(100vh - 220px); + } } From 32a21486519254620ff68895666ccbd5ef14ebb2 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 2 Aug 2018 01:42:23 +0530 Subject: [PATCH 052/154] [hub] Company (Seller) page --- erpnext/public/js/hub/marketplace.js | 5 ++ erpnext/public/js/hub/pages/seller.js | 85 +++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 erpnext/public/js/hub/pages/seller.js diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index e29ae6fc23..18a1bb0629 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -4,6 +4,7 @@ import './pages/favourites'; import './pages/search'; import './pages/category'; import './pages/item'; +import './pages/seller'; import './pages/register'; import './pages/profile'; import './pages/publish'; @@ -164,6 +165,10 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages.item = new erpnext.hub.Item(this.$body); } + if (route[1] === 'seller' && !this.subpages['seller']) { + this.subpages['seller'] = new erpnext.hub.Seller(this.$body); + } + if (route[1] === 'register' && !this.subpages.register) { if (this.registered) { frappe.set_route('marketplace', 'home'); diff --git a/erpnext/public/js/hub/pages/seller.js b/erpnext/public/js/hub/pages/seller.js new file mode 100644 index 0000000000..94e83414c7 --- /dev/null +++ b/erpnext/public/js/hub/pages/seller.js @@ -0,0 +1,85 @@ +import SubPage from './subpage'; + +erpnext.hub.Seller = class Seller extends SubPage { + make_wrapper() { + super.make_wrapper(); + } + + refresh() { + this.show_skeleton(); + this.company = frappe.get_route()[2]; + this.get_hub_seller_profile() + .then(this.render.bind(this)); + } + + get_hub_seller_profile() { + return hub.call('get_hub_seller_profile', { company: this.company }); + } + + // get_hub_seller_items(profile) { + // this.profile = profile; + // console.log(profile); + // return hub.call('get_items', { hub_seller: profile.user }); + // } + + show_skeleton() { + const skeleton = `
          +
          +
          +
          +
          +
          +

          Name

          +
          +

          Details

          +

          Ratings

          +
          +
          +
          +

          Desc

          +

          Desc

          +
          +
          +
          +
          `; + + this.$wrapper.html(skeleton); + } + + render(profile) { + const p = profile; + + const profile_html = `
          +
          +
          + +
          +
          +
          +
          +
          + +
          +
          +
          +

          ${p.company}

          +
          +

          ${p.country}

          +

          ${p.site_name}

          +

          ${__(`Joined ${comment_when(p.creation)}`)}

          +
          +
          +
          + ${'description' + ? `

          ${p.company_description}

          ` + : `

          __('No description') +

          +
          + +
          `; + + this.$wrapper.html(profile_html); + } +} From 106856b1ab8ea2d596acf14b9df2611d67f60218 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Thu, 2 Aug 2018 02:07:04 +0530 Subject: [PATCH 053/154] Show View count on items --- erpnext/public/js/hub/pages/item.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index 609479b0ef..7efa21fcb4 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -62,8 +62,16 @@ erpnext.hub.Item = class Item extends SubPage { const description = item.description || ''; - const rating_html = get_rating_html(item.average_rating); - const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet'); + let stats = __('No views yet'); + if(item.view_count) { + const views_message = __(`${item.view_count} Views`); + + const rating_html = get_rating_html(item.average_rating); + const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet'); + + stats = `${views_message}${dot_spacer}${rating_html} (${rating_count})`; + } + let menu_items = ''; @@ -94,7 +102,7 @@ erpnext.hub.Item = class Item extends SubPage {

          ${title}

          ${where}${dot_spacer}${when}

          -

          ${rating_html} (${rating_count})

          +

          ${stats}


          From b614ece9915ce4ee4ecfe24d96d03a2ec66bf66f Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 6 Aug 2018 17:48:45 +0530 Subject: [PATCH 054/154] [hub] Move sync, correct legacy code --- erpnext/hub_node/__init__.py | 5 +++++ .../hub_node/doctype/hub_settings/hub_settings.py | 5 ----- erpnext/hub_node/legacy.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 634746caf2..0ebbec7a0e 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -10,3 +10,8 @@ def enable_hub(): hub_settings.register() frappe.db.commit() return hub_settings + +@frappe.whitelist() +def sync(): + hub_settings = frappe.get_doc('Hub Settings') + hub_settings.sync() diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index bfb332022a..8ec3d5621f 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -109,11 +109,6 @@ def reset_hub_settings(last_sync_datetime = ""): doc.save() frappe.msgprint(_("Successfully unregistered.")) -@frappe.whitelist() -def sync(): - hub_settings = frappe.get_doc('Hub Settings') - hub_settings.sync() - @frappe.whitelist() def register_seller(**kwargs): settings = frappe.get_doc('Hub Settings') diff --git a/erpnext/hub_node/legacy.py b/erpnext/hub_node/legacy.py index 87d4e1b4ad..06d20f344c 100644 --- a/erpnext/hub_node/legacy.py +++ b/erpnext/hub_node/legacy.py @@ -2,6 +2,21 @@ from __future__ import unicode_literals import frappe, requests, json from frappe.utils import now, nowdate from frappe.frappeclient import FrappeClient +from frappe.utils.nestedset import get_root_of +from frappe.contacts.doctype.contact.contact import get_default_contact + +def get_list(doctype, start, limit, fields, filters, order_by): + pass + +def get_hub_connection(): + if frappe.db.exists('Data Migration Connector', 'Hub Connector'): + hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector') + hub_connection = hub_connector.get_connection() + return hub_connection.connection + + # read-only connection + hub_connection = FrappeClient(frappe.conf.hub_url) + return hub_connection @frappe.whitelist() def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): From d3da401ef4b21356a58b5646aec22132ba819a0b Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 6 Aug 2018 18:31:26 +0530 Subject: [PATCH 055/154] Home: Category-wise Items --- erpnext/public/js/hub/pages/home.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/hub/pages/home.js b/erpnext/public/js/hub/pages/home.js index 6a49f6265b..b8e6f01fd7 100644 --- a/erpnext/public/js/hub/pages/home.js +++ b/erpnext/public/js/hub/pages/home.js @@ -37,5 +37,15 @@ erpnext.hub.Home = class Home extends SubPage { html = get_item_card_container_html(data.items_by_country, __('Near you')); this.$wrapper.append(html); } + + const category_items = data.category_items; + + if (category_items) { + Object.keys(category_items).map(category => { + const items = category_items[category]; + html = get_item_card_container_html(items, __(category)); + this.$wrapper.append(html); + }); + } } -} \ No newline at end of file +} From 658d1bbae2a80fdb08492007b74213b0d360e6a8 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 6 Aug 2018 19:13:10 +0530 Subject: [PATCH 056/154] Fix product header styles --- erpnext/public/js/hub/helpers.js | 4 ++-- erpnext/public/less/hub.less | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/hub/helpers.js b/erpnext/public/js/hub/helpers.js index 22e35c3d89..85143b75b5 100644 --- a/erpnext/public/js/hub/helpers.js +++ b/erpnext/public/js/hub/helpers.js @@ -9,7 +9,7 @@ function get_item_card_container_html(items, title='', get_item_html = get_item_ const items_html = (items || []).map(item => get_item_html(item)).join(''); const title_html = title ? `
          - ${title} +

          ${title}

          ` : ''; @@ -140,4 +140,4 @@ export { get_local_item_card_html, get_rating_html, make_search_bar, -} \ No newline at end of file +} diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index da51260e6d..b0ddec413b 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -210,6 +210,13 @@ body[data-route^="marketplace/"] { border-bottom: none; } + .hub-card-container { + h4 { + font-weight: 500; + margin-top: 32px + } + } + .hub-item-container { overflow: hidden; } From 82b9508622ca30221524ca1eb2564ec9d753240e Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 7 Aug 2018 12:02:14 +0530 Subject: [PATCH 057/154] Add Products by Seller --- erpnext/public/js/hub/pages/seller.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/erpnext/public/js/hub/pages/seller.js b/erpnext/public/js/hub/pages/seller.js index 94e83414c7..27b39247ba 100644 --- a/erpnext/public/js/hub/pages/seller.js +++ b/erpnext/public/js/hub/pages/seller.js @@ -1,4 +1,5 @@ import SubPage from './subpage'; +import { get_item_card_container_html } from '../helpers'; erpnext.hub.Seller = class Seller extends SubPage { make_wrapper() { @@ -13,15 +14,9 @@ erpnext.hub.Seller = class Seller extends SubPage { } get_hub_seller_profile() { - return hub.call('get_hub_seller_profile', { company: this.company }); + return hub.call('get_hub_seller_page_info', { company: this.company }); } - // get_hub_seller_items(profile) { - // this.profile = profile; - // console.log(profile); - // return hub.call('get_items', { hub_seller: profile.user }); - // } - show_skeleton() { const skeleton = `
          @@ -46,8 +41,8 @@ erpnext.hub.Seller = class Seller extends SubPage { this.$wrapper.html(skeleton); } - render(profile) { - const p = profile; + render(data) { + const p = data.profile; const profile_html = `
          @@ -81,5 +76,8 @@ erpnext.hub.Seller = class Seller extends SubPage {
          `; this.$wrapper.html(profile_html); + + let html = get_item_card_container_html(data.items, __('Products by ' + p.company)); + this.$wrapper.append(html); } } From 318cca8caecc1ac0526bcd31bf84e9bc4cdce9f4 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 7 Aug 2018 12:41:21 +0530 Subject: [PATCH 058/154] [hub] Add 'See All' for Category items - Also remove special `cursor: pointer` style - Set it for all elements having a data-route --- erpnext/public/js/hub/helpers.js | 5 +++-- erpnext/public/js/hub/pages/home.js | 11 +++++++++-- erpnext/public/less/hub.less | 19 ++++++++++++------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/erpnext/public/js/hub/helpers.js b/erpnext/public/js/hub/helpers.js index 85143b75b5..7c4c9193e0 100644 --- a/erpnext/public/js/hub/helpers.js +++ b/erpnext/public/js/hub/helpers.js @@ -5,11 +5,12 @@ function get_empty_state(message, action) {
          `; } -function get_item_card_container_html(items, title='', get_item_html = get_item_card_html) { +function get_item_card_container_html(items, title='', get_item_html = get_item_card_html, action='') { const items_html = (items || []).map(item => get_item_html(item)).join(''); const title_html = title - ? `
          + ? `

          ${title}

          + ${action}
          ` : ''; diff --git a/erpnext/public/js/hub/pages/home.js b/erpnext/public/js/hub/pages/home.js index b8e6f01fd7..eefe3b8797 100644 --- a/erpnext/public/js/hub/pages/home.js +++ b/erpnext/public/js/hub/pages/home.js @@ -1,5 +1,5 @@ import SubPage from './subpage'; -import { make_search_bar, get_item_card_container_html } from '../helpers'; +import { make_search_bar, get_item_card_container_html, get_item_card_html } from '../helpers'; erpnext.hub.Home = class Home extends SubPage { make_wrapper() { @@ -43,7 +43,14 @@ erpnext.hub.Home = class Home extends SubPage { if (category_items) { Object.keys(category_items).map(category => { const items = category_items[category]; - html = get_item_card_container_html(items, __(category)); + const see_all_link = `

          See All

          `; + + html = get_item_card_container_html( + items, + __(category), + get_item_card_html, + see_all_link + ); this.$wrapper.append(html); }); } diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index b0ddec413b..d8ac9dd9c9 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -6,6 +6,10 @@ body[data-route^="marketplace/"] { padding-right: 25px; } + [data-route] { + cursor: pointer; + } + .layout-main-section { border: none; font-size: @text-medium; @@ -60,7 +64,6 @@ body[data-route^="marketplace/"] { border: 1px solid @border-color; border-radius: 4px; overflow: hidden; - cursor: pointer; &:hover .hub-card-overlay { display: block; @@ -146,8 +149,6 @@ body[data-route^="marketplace/"] { border-radius: 4px; border: 1px solid transparent; - cursor: pointer; - &.active, &:hover:not(.is-title) { border-color: @border-color; } @@ -211,9 +212,14 @@ body[data-route^="marketplace/"] { } .hub-card-container { - h4 { - font-weight: 500; - margin-top: 32px + .hub-card-container-header { + margin-top: 32px; + justify-content: space-between; + align-items: baseline; + + h4 { + font-weight: 500; + } } } @@ -235,7 +241,6 @@ body[data-route^="marketplace/"] { display: flex; align-items: center; padding: 8px 12px; - cursor: pointer; &:not(.active) { filter: grayscale(1); From 28d52dfe0cc4df39e9820b856205e5bd3486daed Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 7 Aug 2018 12:56:56 +0530 Subject: [PATCH 059/154] [hub][fix] Category page api call --- erpnext/public/js/hub/pages/category.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/hub/pages/category.js b/erpnext/public/js/hub/pages/category.js index 87311abe72..13a0a92f8c 100644 --- a/erpnext/public/js/hub/pages/category.js +++ b/erpnext/public/js/hub/pages/category.js @@ -5,15 +5,14 @@ erpnext.hub.Category = class Category extends SubPage { refresh() { this.category = frappe.get_route()[2]; this.get_items_for_category(this.category) - .then(r => { - this.render(r.message); + .then(items => { + this.render(items); }); } get_items_for_category(category) { this.$wrapper.find('.hub-card-container').empty(); - return frappe.call('erpnext.hub_node.api.get_list', { - doctype: 'Hub Item', + return hub.call('get_items', { filters: { hub_category: category } From b777f3dbfd8efba6222ab78235760ec604722c14 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Fri, 10 Aug 2018 10:38:28 +0530 Subject: [PATCH 060/154] [layout][iterate] item detail page actions --- erpnext/public/js/hub/pages/item.js | 45 +++++++++++++++++------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index 7efa21fcb4..ec3d6edc3e 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -92,25 +92,28 @@ erpnext.hub.Item = class Item extends SubPage {
          -
          +
          -
          -

          ${title}

          -
          -

          ${where}${dot_spacer}${when}

          -

          ${stats}

          +
          +
          +

          ${title}

          +
          +

          ${where}${dot_spacer}${when}

          +

          ${stats}

          +
          -
          -
          - ${description ? - `${__('Description')} -

          ${description}

          - ` : `

          ${__('No description')}

          ` - } + +

          + +
          @@ -124,18 +127,24 @@ erpnext.hub.Item = class Item extends SubPage {
          +
          +
          + Product Description +
          +

          + ${description ? description : __('No details')} +

          +
          -
          + +
          Seller Information -
          +
          -
          From c5a9972785174a811c2265f93a779841f298793e Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 13 Aug 2018 21:36:07 +0530 Subject: [PATCH 061/154] [refactor][major] Separate out Components - item_card - item_card_container - detail_view - search_bar - reviews - skeleton_state - empty_state - empty_state --- .../public/js/hub/components/detail_view.js | 160 ++++++++++ .../public/js/hub/components/empty_state.js | 10 + .../{helpers.js => components/item_card.js} | 48 +-- .../js/hub/components/items_container.js | 22 ++ erpnext/public/js/hub/components/reviews.js | 71 +++++ .../public/js/hub/components/search_bar.js | 20 ++ .../js/hub/components/skeleton_state.js | 27 ++ erpnext/public/js/hub/marketplace.js | 1 - erpnext/public/js/hub/pages/category.js | 2 +- erpnext/public/js/hub/pages/favourites.js | 8 +- erpnext/public/js/hub/pages/home.js | 4 +- erpnext/public/js/hub/pages/item.js | 282 ++++-------------- erpnext/public/js/hub/pages/messages.js | 4 +- erpnext/public/js/hub/pages/not_found.js | 1 + erpnext/public/js/hub/pages/publish.js | 4 +- .../public/js/hub/pages/published_products.js | 2 +- erpnext/public/js/hub/pages/register.js | 44 +-- erpnext/public/js/hub/pages/search.js | 3 +- erpnext/public/js/hub/pages/seller.js | 61 +--- erpnext/public/js/hub/pages/subpage.js | 8 +- erpnext/public/less/hub.less | 16 +- 21 files changed, 425 insertions(+), 373 deletions(-) create mode 100644 erpnext/public/js/hub/components/detail_view.js create mode 100644 erpnext/public/js/hub/components/empty_state.js rename erpnext/public/js/hub/{helpers.js => components/item_card.js} (68%) create mode 100644 erpnext/public/js/hub/components/items_container.js create mode 100644 erpnext/public/js/hub/components/reviews.js create mode 100644 erpnext/public/js/hub/components/search_bar.js create mode 100644 erpnext/public/js/hub/components/skeleton_state.js diff --git a/erpnext/public/js/hub/components/detail_view.js b/erpnext/public/js/hub/components/detail_view.js new file mode 100644 index 0000000000..1f0d5426f2 --- /dev/null +++ b/erpnext/public/js/hub/components/detail_view.js @@ -0,0 +1,160 @@ +function get_detail_view_html(item, allow_edit) { + const title = item.item_name || item.name; + const seller = item.company; + + const who = __('Posted By {0}', [seller]); + const when = comment_when(item.creation); + + const city = item.city ? item.city + ', ' : ''; + const country = item.country ? item.country : ''; + const where = `${city}${country}`; + + const dot_spacer = ''; + + const description = item.description || ''; + + let stats = __('No views yet'); + if(item.view_count) { + const views_message = __(`${item.view_count} Views`); + + const rating_html = get_rating_html(item.average_rating); + const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet'); + + stats = `${views_message}${dot_spacer}${rating_html} (${rating_count})`; + } + + + let menu_items = ''; + + if(allow_edit) { + menu_items = ` +
        • ${__('Edit Details')}
        • +
        • ${__('Unpublish')}
        • `; + } else { + menu_items = ` +
        • ${__('Report this item')}
        • + `; + } + + const html = ` +
          +
          +
          + +
          +
          +
          +
          +
          + +
          +
          +
          +
          +

          ${title}

          +
          +

          ${where}${dot_spacer}${when}

          +

          ${stats}

          +
          +
          + +
          + + +
          +
          +
          + +
          +
          +
          +
          + Product Description +
          +

          + ${description ? description : __('No details')} +

          +
          +
          + +
          + Seller Information +
          +
          + +
          +
          + +
          +
          + +
          + +
          +
          + `; + + return html; +} + +function get_profile_html(profile) { + const p = profile; + const profile_html = `
          +
          +
          + +
          +
          +
          +
          +
          + +
          +
          +
          +

          ${p.company}

          +
          +

          ${p.country}

          +

          ${p.site_name}

          +

          ${__(`Joined ${comment_when(p.creation)}`)}

          +
          +
          +
          + ${'description' + ? `

          ${p.company_description}

          ` + : `

          __('No description') +

          +
          + +
          `; + + return profile_html; +} + +export { + get_detail_view_html, + get_profile_html +} diff --git a/erpnext/public/js/hub/components/empty_state.js b/erpnext/public/js/hub/components/empty_state.js new file mode 100644 index 0000000000..0e1ad46d2f --- /dev/null +++ b/erpnext/public/js/hub/components/empty_state.js @@ -0,0 +1,10 @@ +function get_empty_state(message, action) { + return `
          +

          ${message}

          + ${action ? `

          ${action}

          `: ''} +
          `; +} + +export { + get_empty_state +} diff --git a/erpnext/public/js/hub/helpers.js b/erpnext/public/js/hub/components/item_card.js similarity index 68% rename from erpnext/public/js/hub/helpers.js rename to erpnext/public/js/hub/components/item_card.js index 7c4c9193e0..733df62e6a 100644 --- a/erpnext/public/js/hub/helpers.js +++ b/erpnext/public/js/hub/components/item_card.js @@ -1,27 +1,3 @@ -function get_empty_state(message, action) { - return `
          -

          ${message}

          - ${action ? `

          ${action}

          `: ''} -
          `; -} - -function get_item_card_container_html(items, title='', get_item_html = get_item_card_html, action='') { - const items_html = (items || []).map(item => get_item_html(item)).join(''); - const title_html = title - ? `
          -

          ${title}

          - ${action} -
          ` - : ''; - - const html = `
          - ${title_html} - ${items_html} -
          `; - - return html; -} - function get_item_card_html(item) { const item_name = item.item_name || item.name; const title = strip_html(item_name); @@ -117,28 +93,8 @@ function get_rating_html(rating) { return rating_html; } -function make_search_bar({wrapper, on_search, placeholder = __('Search for anything')}) { - const $search = $(` -
          - -
          ` - ); - wrapper.append($search); - const $search_input = $search.find('input'); - - $search_input.on('keydown', frappe.utils.debounce((e) => { - if (e.which === frappe.ui.keyCode.ENTER) { - const search_value = $search_input.val(); - on_search(search_value); - } - }, 300)); -} - export { - get_empty_state, - get_item_card_container_html, get_item_card_html, - get_local_item_card_html, - get_rating_html, - make_search_bar, + get_local_item_card_html, + get_rating_html } diff --git a/erpnext/public/js/hub/components/items_container.js b/erpnext/public/js/hub/components/items_container.js new file mode 100644 index 0000000000..dd29836917 --- /dev/null +++ b/erpnext/public/js/hub/components/items_container.js @@ -0,0 +1,22 @@ +import { get_item_card_html } from './item_card'; + +function get_item_card_container_html(items, title='', get_item_html = get_item_card_html, action='') { + const items_html = (items || []).map(item => get_item_html(item)).join(''); + const title_html = title + ? `
          +

          ${title}

          + ${action} +
          ` + : ''; + + const html = `
          + ${title_html} + ${items_html} +
          `; + + return html; +} + +export { + get_item_card_container_html +} diff --git a/erpnext/public/js/hub/components/reviews.js b/erpnext/public/js/hub/components/reviews.js new file mode 100644 index 0000000000..616f2fb705 --- /dev/null +++ b/erpnext/public/js/hub/components/reviews.js @@ -0,0 +1,71 @@ +import { get_rating_html } from './item_card'; + +function get_review_html(review) { + let username = review.username || review.user || __("Anonymous"); + + let image_html = review.user_image + ? `
          ` + : `
          ${frappe.get_abbr(username)}
          ` + + let edit_html = review.own + ? ` +
          + + ${'data.edit'} + +
          ` + : ''; + + let rating_html = get_rating_html(review.rating); + + return get_timeline_item(review, image_html, edit_html, rating_html); +} + +function get_timeline_item(data, image_html, edit_html, rating_html) { + return `
          + +
          +
          +
          ${edit_html}
          + +
          + + ${image_html} + + +
          + + + ${data.username} + + + + + +
          +
          +
          +
          +

          + ${rating_html} +

          +
          ${data.subject}
          +

          + ${data.content} +

          +
          +
          +
          +
          +
          `; +} + +export { + get_review_html +} diff --git a/erpnext/public/js/hub/components/search_bar.js b/erpnext/public/js/hub/components/search_bar.js new file mode 100644 index 0000000000..9526516ee9 --- /dev/null +++ b/erpnext/public/js/hub/components/search_bar.js @@ -0,0 +1,20 @@ +function make_search_bar({wrapper, on_search, placeholder = __('Search for anything')}) { + const $search = $(` +
          + +
          ` + ); + wrapper.append($search); + const $search_input = $search.find('input'); + + $search_input.on('keydown', frappe.utils.debounce((e) => { + if (e.which === frappe.ui.keyCode.ENTER) { + const search_value = $search_input.val(); + on_search(search_value); + } + }, 300)); +} + +export { + make_search_bar +} diff --git a/erpnext/public/js/hub/components/skeleton_state.js b/erpnext/public/js/hub/components/skeleton_state.js new file mode 100644 index 0000000000..7c6880224c --- /dev/null +++ b/erpnext/public/js/hub/components/skeleton_state.js @@ -0,0 +1,27 @@ +function get_detail_skeleton_html() { + const skeleton = `
          +
          +
          +
          +
          +
          +

          Name

          +
          +

          Details

          +

          Ratings

          +
          +
          +
          +

          Desc

          +

          Desc

          +
          +
          +
          +
          `; + + return skeleton; +} + +export { + get_detail_skeleton_html +} diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 18a1bb0629..154324338d 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -13,7 +13,6 @@ import './pages/messages'; import './pages/not_found'; // helpers -import './helpers'; import './hub_call'; frappe.provide('hub'); diff --git a/erpnext/public/js/hub/pages/category.js b/erpnext/public/js/hub/pages/category.js index 13a0a92f8c..118d196d55 100644 --- a/erpnext/public/js/hub/pages/category.js +++ b/erpnext/public/js/hub/pages/category.js @@ -1,5 +1,5 @@ import SubPage from './subpage'; -import { get_item_card_container_html } from '../helpers'; +import { get_item_card_container_html } from '../components/items_container'; erpnext.hub.Category = class Category extends SubPage { refresh() { diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js index 704caeabbb..d4a8cb3ee0 100644 --- a/erpnext/public/js/hub/pages/favourites.js +++ b/erpnext/public/js/hub/pages/favourites.js @@ -1,5 +1,5 @@ import SubPage from './subpage'; -import { get_item_card_container_html } from '../helpers'; +import { get_item_card_container_html } from '../components/items_container'; erpnext.hub.Favourites = class Favourites extends SubPage { refresh() { @@ -10,7 +10,9 @@ erpnext.hub.Favourites = class Favourites extends SubPage { } get_favourites() { - return hub.call('get_item_favourites'); + return hub.call('get_favourite_items_of_seller', { + hub_seller: hub.settings.company_email + }); } render(items) { @@ -18,4 +20,4 @@ erpnext.hub.Favourites = class Favourites extends SubPage { const html = get_item_card_container_html(items, __('Favourites')); this.$wrapper.append(html) } -} \ No newline at end of file +} diff --git a/erpnext/public/js/hub/pages/home.js b/erpnext/public/js/hub/pages/home.js index eefe3b8797..b7a55cec05 100644 --- a/erpnext/public/js/hub/pages/home.js +++ b/erpnext/public/js/hub/pages/home.js @@ -1,5 +1,7 @@ import SubPage from './subpage'; -import { make_search_bar, get_item_card_container_html, get_item_card_html } from '../helpers'; +import { make_search_bar } from '../components/search_bar'; +import { get_item_card_container_html } from '../components/items_container'; +import { get_item_card_html } from '../components/item_card'; erpnext.hub.Home = class Home extends SubPage { make_wrapper() { diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index ec3d6edc3e..207c94cb90 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -1,5 +1,7 @@ import SubPage from './subpage'; -import { get_rating_html } from '../helpers'; +import { get_detail_view_html } from '../components/detail_view'; +import { get_detail_skeleton_html } from '../components/skeleton_state'; +import { get_review_html } from '../components/reviews'; erpnext.hub.Item = class Item extends SubPage { refresh() { @@ -16,30 +18,12 @@ erpnext.hub.Item = class Item extends SubPage { }); } - show_skeleton() { - const skeleton = `
          -
          -
          -
          -
          -
          -

          Name

          -
          -

          Details

          -

          Ratings

          -
          -
          -
          -

          Desc

          -

          Desc

          -
          -
          -
          -
          `; - this.$wrapper.html(skeleton); + show_skeleton() { + this.$wrapper.html(get_detail_skeleton_html()); } + get_item(hub_item_code) { return hub.call('get_item_details', { hub_seller: hub.settings.company_email, @@ -47,123 +31,9 @@ erpnext.hub.Item = class Item extends SubPage { }); } + render(item) { - const title = item.item_name || item.name; - const seller = item.company; - - const who = __('Posted By {0}', [seller]); - const when = comment_when(item.creation); - - const city = item.city ? item.city + ', ' : ''; - const country = item.country ? item.country : ''; - const where = `${city}${country}`; - - const dot_spacer = ''; - - const description = item.description || ''; - - let stats = __('No views yet'); - if(item.view_count) { - const views_message = __(`${item.view_count} Views`); - - const rating_html = get_rating_html(item.average_rating); - const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet'); - - stats = `${views_message}${dot_spacer}${rating_html} (${rating_count})`; - } - - - let menu_items = ''; - - if(this.own_item) { - menu_items = ` -
        • ${__('Edit Details')}
        • -
        • ${__('Unpublish')}
        • `; - } else { - menu_items = ` -
        • ${__('Report this item')}
        • - `; - } - - const html = ` -
          -
          -
          - -
          -
          -
          -
          -
          - -
          -
          -
          -
          -

          ${title}

          -
          -

          ${where}${dot_spacer}${when}

          -

          ${stats}

          -
          -
          - -
          - - -
          -
          -
          - -
          -
          -
          -
          - Product Description -
          -

          - ${description ? description : __('No details')} -

          -
          -
          - -
          - Seller Information -
          -
          - -
          -
          - -
          -
          - -
          - -
          -
          - `; - + const html = get_detail_view_html(item, this.own_item); this.$wrapper.html(html); this.make_review_area(); @@ -171,10 +41,11 @@ erpnext.hub.Item = class Item extends SubPage { this.get_reviews() .then(reviews => { this.reviews = reviews; - this.render_reviews(reviews); + this.render_reviews(); }); } + edit_details() { if (!this.edit_dialog) { this.edit_dialog = new frappe.ui.Dialog({ @@ -185,6 +56,7 @@ erpnext.hub.Item = class Item extends SubPage { this.edit_dialog.show(); } + unpublish_item() { if(!this.unpublish_dialog) { this.unpublish_dialog = new frappe.ui.Dialog({ @@ -196,6 +68,16 @@ erpnext.hub.Item = class Item extends SubPage { this.unpublish_dialog.show(); } + + add_to_favourites(favourite_button) { + $(favourite_button).html('Added to Favourites').addClass('disabled'); + return hub.call('remove_item_from_seller_favourites', { + hub_item_code: this.hub_item_code, + hub_seller: hub.settings.company_email + }); + } + + contact_seller() { const d = new frappe.ui.Dialog({ title: __('Send a message'), @@ -220,37 +102,48 @@ erpnext.hub.Item = class Item extends SubPage { d.show(); } + make_review_area() { this.comment_area = new frappe.ui.ReviewArea({ parent: this.$wrapper.find('.timeline-head').empty(), mentions: [], - on_submit: (values) => { - values.user = frappe.session.user; - values.username = frappe.session.user_fullname; - - hub.call('add_item_review', { - hub_item_code: this.hub_item_code, - review: JSON.stringify(values) - }) - .then(review => { - this.reviews = this.reviews || []; - this.reviews.push(review); - this.render_reviews(this.reviews); - - this.comment_area.reset(); - }); - } + on_submit: this.on_submit_review.bind(this) }); } + + on_submit_review(values) { + values.user = frappe.session.user; + values.username = frappe.session.user_fullname; + + hub.call('add_item_review', { + hub_item_code: this.hub_item_code, + review: JSON.stringify(values) + }) + .then(this.push_review_in_review_area.bind(this)); + } + + + push_review_in_review_area(review) { + this.reviews = this.reviews || []; + this.reviews.push(review); + this.render_reviews(); + + this.comment_area.reset(); + } + + get_reviews() { return hub.call('get_item_reviews', { hub_item_code: this.hub_item_code }).catch(() => {}); } - render_reviews(reviews=[]) { - this.$wrapper.find('.timeline-items').empty(); - reviews.sort((a, b) => { + render_reviews() { + const $timeline = this.$wrapper.find('.timeline-items'); + + $timeline.empty(); + + this.reviews.sort((a, b) => { if (a.modified > b.modified) { return -1; } @@ -262,75 +155,8 @@ erpnext.hub.Item = class Item extends SubPage { return 0; }); - reviews.forEach(review => this.render_review(review)); - } - - render_review(review) { - let username = review.username || review.user || __("Anonymous"); - - let image_html = review.user_image - ? `
          ` - : `
          ${frappe.get_abbr(username)}
          ` - - let edit_html = review.own - ? ` -
          - - ${'data.edit'} - -
          ` - : ''; - - let rating_html = get_rating_html(review.rating); - - const $timeline_items = this.$wrapper.find('.timeline-items'); - - $(this.get_timeline_item(review, image_html, edit_html, rating_html)) - .appendTo($timeline_items); - } - - get_timeline_item(data, image_html, edit_html, rating_html) { - return `
          - -
          -
          -
          ${edit_html}
          - -
          - - ${image_html} - - -
          - - - ${data.username} - - - - - -
          -
          -
          -
          -

          - ${rating_html} -

          -
          ${data.subject}
          -

          - ${data.content} -

          -
          -
          -
          -
          -
          `; + this.reviews.forEach(review => { + $(get_review_html(review)).appendTo($timeline); + }); } } diff --git a/erpnext/public/js/hub/pages/messages.js b/erpnext/public/js/hub/pages/messages.js index 7bc99a7253..8531e0b3f9 100644 --- a/erpnext/public/js/hub/pages/messages.js +++ b/erpnext/public/js/hub/pages/messages.js @@ -1,5 +1,5 @@ import SubPage from './subpage'; -import { make_search_bar } from '../helpers'; +import { make_search_bar } from '../components/search_bar'; erpnext.hub.Messages = class Messages extends SubPage { make_wrapper() { @@ -115,4 +115,4 @@ function get_message_html(message) {

          ${message.content}

          `; -} \ No newline at end of file +} diff --git a/erpnext/public/js/hub/pages/not_found.js b/erpnext/public/js/hub/pages/not_found.js index 3b864464f3..f7ccc2f5b3 100644 --- a/erpnext/public/js/hub/pages/not_found.js +++ b/erpnext/public/js/hub/pages/not_found.js @@ -1,4 +1,5 @@ import SubPage from './subpage'; +import { get_empty_state } from '../components/empty_state'; erpnext.hub.NotFound = class NotFound extends SubPage { refresh() { diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index 859782edbf..a76f9467c6 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -1,5 +1,7 @@ import SubPage from './subpage'; -import { make_search_bar, get_item_card_container_html, get_local_item_card_html } from '../helpers'; +import { get_item_card_container_html } from '../components/items_container'; +import { get_local_item_card_html } from '../components/item_card'; +import { make_search_bar } from '../components/search_bar'; erpnext.hub.Publish = class Publish extends SubPage { make_wrapper() { diff --git a/erpnext/public/js/hub/pages/published_products.js b/erpnext/public/js/hub/pages/published_products.js index 1b19a51da7..f20fb27762 100644 --- a/erpnext/public/js/hub/pages/published_products.js +++ b/erpnext/public/js/hub/pages/published_products.js @@ -1,5 +1,5 @@ import SubPage from './subpage'; -import { get_item_card_container_html } from '../helpers'; +import { get_item_card_container_html } from '../components/items_container'; erpnext.hub.PublishedProducts = class PublishedProducts extends SubPage { get_items_and_render() { diff --git a/erpnext/public/js/hub/pages/register.js b/erpnext/public/js/hub/pages/register.js index b95ec04444..9b07f29a6b 100644 --- a/erpnext/public/js/hub/pages/register.js +++ b/erpnext/public/js/hub/pages/register.js @@ -82,29 +82,33 @@ erpnext.hub.Register = class Register extends SubPage { this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller')) this.$form_container.on('click', '.btn-register', (e) => { - const form_values = this.field_group.get_values(); + this.register_seller(); + }); + } - let values_filled = true; - const mandatory_fields = ['company', 'company_email', 'company_description']; - mandatory_fields.forEach(field => { - const value = form_values[field]; - if (!value) { - this.field_group.set_df_property(field, 'reqd', 1); - values_filled = false; - } - }); - if (!values_filled) return; + register_seller() { + const form_values = this.field_group.get_values(); - frappe.call({ - method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', - args: form_values, - btn: $(e.currentTarget) - }).then(() => { - frappe.set_route('marketplace', 'publish'); + let values_filled = true; + const mandatory_fields = ['company', 'company_email', 'company_description']; + mandatory_fields.forEach(field => { + const value = form_values[field]; + if (!value) { + this.field_group.set_df_property(field, 'reqd', 1); + values_filled = false; + } + }); + if (!values_filled) return; - // custom jquery event - this.$wrapper.trigger('seller-registered'); - }); + frappe.call({ + method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', + args: form_values, + btn: $(e.currentTarget) + }).then(() => { + frappe.set_route('marketplace', 'publish'); + + // custom jquery event + this.$wrapper.trigger('seller-registered'); }); } } diff --git a/erpnext/public/js/hub/pages/search.js b/erpnext/public/js/hub/pages/search.js index 33c2b78e20..f3dd6fb0c5 100644 --- a/erpnext/public/js/hub/pages/search.js +++ b/erpnext/public/js/hub/pages/search.js @@ -1,5 +1,6 @@ import SubPage from './subpage'; -import { make_search_bar, get_item_card_container_html } from '../helpers'; +import { make_search_bar } from '../components/search_bar'; +import { get_item_card_container_html } from '../components/items_container'; erpnext.hub.SearchPage = class SearchPage extends SubPage { make_wrapper() { diff --git a/erpnext/public/js/hub/pages/seller.js b/erpnext/public/js/hub/pages/seller.js index 27b39247ba..b86e46e544 100644 --- a/erpnext/public/js/hub/pages/seller.js +++ b/erpnext/public/js/hub/pages/seller.js @@ -1,5 +1,7 @@ import SubPage from './subpage'; -import { get_item_card_container_html } from '../helpers'; +import { get_profile_html } from '../components/detail_view'; +import { get_item_card_container_html } from '../components/items_container'; +import { get_detail_skeleton_html } from '../components/skeleton_state'; erpnext.hub.Seller = class Seller extends SubPage { make_wrapper() { @@ -18,64 +20,11 @@ erpnext.hub.Seller = class Seller extends SubPage { } show_skeleton() { - const skeleton = `
          -
          -
          -
          -
          -
          -

          Name

          -
          -

          Details

          -

          Ratings

          -
          -
          -
          -

          Desc

          -

          Desc

          -
          -
          -
          -
          `; - - this.$wrapper.html(skeleton); + this.$wrapper.html(get_detail_skeleton_html()); } render(data) { - const p = data.profile; - - const profile_html = `
          -
          -
          - -
          -
          -
          -
          -
          - -
          -
          -
          -

          ${p.company}

          -
          -

          ${p.country}

          -

          ${p.site_name}

          -

          ${__(`Joined ${comment_when(p.creation)}`)}

          -
          -
          -
          - ${'description' - ? `

          ${p.company_description}

          ` - : `

          __('No description') -

          -
          - -
          `; - - this.$wrapper.html(profile_html); + this.$wrapper.html(get_profile_html(data.profile)); let html = get_item_card_container_html(data.items, __('Products by ' + p.company)); this.$wrapper.append(html); diff --git a/erpnext/public/js/hub/pages/subpage.js b/erpnext/public/js/hub/pages/subpage.js index a030e7e33b..7c75b1379e 100644 --- a/erpnext/public/js/hub/pages/subpage.js +++ b/erpnext/public/js/hub/pages/subpage.js @@ -5,11 +5,11 @@ export default class SubPage { // generic action handler this.$wrapper.on('click', '[data-action]', e => { - const $this = $(e.currentTarget); - const action = $this.data().action; + const $target = $(e.currentTarget); + const action = $target.data().action; if (action && this[action]) { - this[action].apply(this); + this[action].apply(this, $target); } }) @@ -42,4 +42,4 @@ export default class SubPage { hide() { this.$wrapper.hide(); } -} \ No newline at end of file +} diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index d8ac9dd9c9..7ac168486a 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -25,15 +25,15 @@ body[data-route^="marketplace/"] { font-size: @text-medium; } - .btn-primary { - background-color: #89da28; - border-color: #61ca23; - } + // .btn-primary { + // background-color: #89da28; + // border-color: #61ca23; + // } - .btn-primary:hover { - background-color: #61ca23; - border-color: #59b81c; - } + // .btn-primary:hover { + // background-color: #61ca23; + // border-color: #59b81c; + // } .progress-bar { background-color: #89da28; From 1a0fb5b63adf461c0d65b5fe7725227d6ca6f84d Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 14 Aug 2018 01:31:03 +0530 Subject: [PATCH 062/154] Add detail page favourited state --- .../public/js/hub/components/detail_view.js | 13 ++++++++++--- erpnext/public/js/hub/components/item_card.js | 14 +------------- erpnext/public/js/hub/components/reviews.js | 17 +++++++++++++---- erpnext/public/js/hub/hub_call.js | 4 ++-- erpnext/public/js/hub/pages/item.js | 18 ++++++++++++++---- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/erpnext/public/js/hub/components/detail_view.js b/erpnext/public/js/hub/components/detail_view.js index 1f0d5426f2..9ef48d4d0a 100644 --- a/erpnext/public/js/hub/components/detail_view.js +++ b/erpnext/public/js/hub/components/detail_view.js @@ -1,3 +1,5 @@ +import { get_rating_html } from './reviews'; + function get_detail_view_html(item, allow_edit) { const title = item.item_name || item.name; const seller = item.company; @@ -23,6 +25,13 @@ function get_detail_view_html(item, allow_edit) { stats = `${views_message}${dot_spacer}${rating_html} (${rating_count})`; } + let favourite_button = !item.favourited + ? `` + : ``; let menu_items = ''; @@ -59,9 +68,7 @@ function get_detail_view_html(item, allow_edit) {
          - + ${favourite_button} diff --git a/erpnext/public/js/hub/components/item_card.js b/erpnext/public/js/hub/components/item_card.js index 733df62e6a..262ed212f5 100644 --- a/erpnext/public/js/hub/components/item_card.js +++ b/erpnext/public/js/hub/components/item_card.js @@ -82,19 +82,7 @@ function get_local_item_card_html(item) { return item_html; } - -function get_rating_html(rating) { - let rating_html = ``; - for (var i = 0; i < 5; i++) { - let star_class = 'fa-star'; - if (i >= rating) star_class = 'fa-star-o'; - rating_html += ``; - } - return rating_html; -} - export { get_item_card_html, - get_local_item_card_html, - get_rating_html + get_local_item_card_html } diff --git a/erpnext/public/js/hub/components/reviews.js b/erpnext/public/js/hub/components/reviews.js index 616f2fb705..e34d68038f 100644 --- a/erpnext/public/js/hub/components/reviews.js +++ b/erpnext/public/js/hub/components/reviews.js @@ -1,5 +1,3 @@ -import { get_rating_html } from './item_card'; - function get_review_html(review) { let username = review.username || review.user || __("Anonymous"); @@ -66,6 +64,17 @@ function get_timeline_item(data, image_html, edit_html, rating_html) {
          `; } -export { - get_review_html +function get_rating_html(rating) { + let rating_html = ``; + for (var i = 0; i < 5; i++) { + let star_class = 'fa-star'; + if (i >= rating) star_class = 'fa-star-o'; + rating_html += ``; + } + return rating_html; +} + +export { + get_review_html, + get_rating_html } diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js index 6786cf6f90..6bc1701551 100644 --- a/erpnext/public/js/hub/hub_call.js +++ b/erpnext/public/js/hub/hub_call.js @@ -35,9 +35,9 @@ hub.call = function call_hub_method(method, args={}) { } erpnext.hub.cache[key] = r.message; - resolve(r.message) + resolve(r.message); } - reject(r) + reject(r); }) .fail(reject) }); diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index 207c94cb90..ee43391a22 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -70,10 +70,18 @@ erpnext.hub.Item = class Item extends SubPage { add_to_favourites(favourite_button) { - $(favourite_button).html('Added to Favourites').addClass('disabled'); - return hub.call('remove_item_from_seller_favourites', { + $(favourite_button).addClass('disabled'); + + hub.call('add_item_to_seller_favourites', { hub_item_code: this.hub_item_code, hub_seller: hub.settings.company_email + }) + .then(() => { + $(favourite_button).html('Saved'); + frappe.show_alert(__('Saved to Favourites')); + }) + .catch(e => { + console.log(e); }); } @@ -143,7 +151,9 @@ erpnext.hub.Item = class Item extends SubPage { $timeline.empty(); - this.reviews.sort((a, b) => { + const reviews = this.reviews || []; + + reviews.sort((a, b) => { if (a.modified > b.modified) { return -1; } @@ -155,7 +165,7 @@ erpnext.hub.Item = class Item extends SubPage { return 0; }); - this.reviews.forEach(review => { + reviews.forEach(review => { $(get_review_html(review)).appendTo($timeline); }); } From 460e2798cde8901df6c818d24a193f1cb33ea605 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 14 Aug 2018 11:42:40 +0530 Subject: [PATCH 063/154] [hub] Favourites UI - remove from listing - add to favourites --- erpnext/public/js/hub/components/item_card.js | 25 ++++++--- erpnext/public/js/hub/pages/favourites.js | 52 ++++++++++++++++++- erpnext/public/less/hub.less | 12 +++++ 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/hub/components/item_card.js b/erpnext/public/js/hub/components/item_card.js index 262ed212f5..f7f5975db6 100644 --- a/erpnext/public/js/hub/components/item_card.js +++ b/erpnext/public/js/hub/components/item_card.js @@ -17,15 +17,24 @@ function get_item_card_html(item) { const item_html = `
          -
          -
          -
          ${title}
          -
          ${subtitle}
          +
          + +
          +
          +
          ${title}
          +
          ${subtitle}
          +
          + +
          +
          `; @@ -62,9 +71,11 @@ function get_local_item_card_html(item) { const item_html = `
          -
          -
          ${title}
          -
          ${subtitle}
          +
          +
          +
          ${title}
          +
          ${subtitle}
          +
          diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js index d4a8cb3ee0..6c2fd65580 100644 --- a/erpnext/public/js/hub/pages/favourites.js +++ b/erpnext/public/js/hub/pages/favourites.js @@ -2,6 +2,22 @@ import SubPage from './subpage'; import { get_item_card_container_html } from '../components/items_container'; erpnext.hub.Favourites = class Favourites extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.bind_events(); + } + + bind_events() { + this.$wrapper.on('click', '.hub-card', (e) => { + const $target = $(e.target); + if($target.hasClass('octicon-x')) { + e.stopPropagation(); + const hub_item_code = $target.attr('data-hub-item-code'); + this.on_item_remove(hub_item_code); + } + }); + } + refresh() { this.get_favourites() .then(items => { @@ -18,6 +34,40 @@ erpnext.hub.Favourites = class Favourites extends SubPage { render(items) { this.$wrapper.find('.hub-card-container').empty(); const html = get_item_card_container_html(items, __('Favourites')); - this.$wrapper.append(html) + this.$wrapper.append(html); + + this.$wrapper.find('.hub-card').addClass('closable'); } + + on_item_remove(hub_item_code, $hub_card = '') { + const $message = $(__(`${hub_item_code} removed. + Undo`)); + + frappe.show_alert($message); + + $hub_card = this.$wrapper.find(`.hub-card[data-hub-item-code="${hub_item_code}"]`); + + $hub_card.hide(); + + const grace_period = 5000; + + setTimeout(() => { + this.remove_item(hub_item_code, $hub_card); + }, grace_period); + } + + remove_item(hub_item_code, $hub_card) { + hub.call('remove_item_from_seller_favourites', { + hub_item_code, + hub_seller: hub.settings.company_email + }) + .then(() => { + $hub_card.remove(); + }) + .catch(e => { + console.log(e); + }); + } + + undo_remove(hub_item_code) { } } diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 7ac168486a..51fe340535 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -68,6 +68,18 @@ body[data-route^="marketplace/"] { &:hover .hub-card-overlay { display: block; } + + .octicon-x { + display: none; + margin-left: 10px; + font-size: 20px; + } + } + + .hub-card.closable { + .octicon-x { + display: block; + } } .hub-card.is-local { From aa46567439d9ca410c7220a93651ccc2c209d70c Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 17 Aug 2018 14:29:22 +0530 Subject: [PATCH 064/154] refactor: rename hub-card-container to hub-items-container --- erpnext/public/js/hub/components/items_container.js | 4 ++-- erpnext/public/js/hub/pages/category.js | 2 +- erpnext/public/js/hub/pages/home.js | 2 +- erpnext/public/js/hub/pages/published_products.js | 2 +- erpnext/public/less/hub.less | 9 ++------- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/erpnext/public/js/hub/components/items_container.js b/erpnext/public/js/hub/components/items_container.js index dd29836917..918569bd16 100644 --- a/erpnext/public/js/hub/components/items_container.js +++ b/erpnext/public/js/hub/components/items_container.js @@ -3,13 +3,13 @@ import { get_item_card_html } from './item_card'; function get_item_card_container_html(items, title='', get_item_html = get_item_card_html, action='') { const items_html = (items || []).map(item => get_item_html(item)).join(''); const title_html = title - ? `
          + ? `

          ${title}

          ${action}
          ` : ''; - const html = `
          + const html = `
          ${title_html} ${items_html}
          `; diff --git a/erpnext/public/js/hub/pages/category.js b/erpnext/public/js/hub/pages/category.js index 118d196d55..c279c1aab1 100644 --- a/erpnext/public/js/hub/pages/category.js +++ b/erpnext/public/js/hub/pages/category.js @@ -11,7 +11,7 @@ erpnext.hub.Category = class Category extends SubPage { } get_items_for_category(category) { - this.$wrapper.find('.hub-card-container').empty(); + this.$wrapper.find('.hub-items-container').empty(); return hub.call('get_items', { filters: { hub_category: category diff --git a/erpnext/public/js/hub/pages/home.js b/erpnext/public/js/hub/pages/home.js index b7a55cec05..990223894f 100644 --- a/erpnext/public/js/hub/pages/home.js +++ b/erpnext/public/js/hub/pages/home.js @@ -20,7 +20,7 @@ erpnext.hub.Home = class Home extends SubPage { } get_items_and_render() { - this.$wrapper.find('.hub-card-container').empty(); + this.$wrapper.find('.hub-items-container').empty(); this.get_data() .then(data => { this.render(data); diff --git a/erpnext/public/js/hub/pages/published_products.js b/erpnext/public/js/hub/pages/published_products.js index f20fb27762..17c115e70f 100644 --- a/erpnext/public/js/hub/pages/published_products.js +++ b/erpnext/public/js/hub/pages/published_products.js @@ -3,7 +3,7 @@ import { get_item_card_container_html } from '../components/items_container'; erpnext.hub.PublishedProducts = class PublishedProducts extends SubPage { get_items_and_render() { - this.$wrapper.find('.hub-card-container').empty(); + this.$wrapper.find('.hub-items-container').empty(); this.get_published_products() .then(items => this.render(items)); } diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 51fe340535..ac0aa42ae1 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -223,15 +223,10 @@ body[data-route^="marketplace/"] { border-bottom: none; } - .hub-card-container { - .hub-card-container-header { - margin-top: 32px; + .hub-items-container { + .hub-items-header { justify-content: space-between; align-items: baseline; - - h4 { - font-weight: 500; - } } } From c1fe1c45c7544ac14a4326d3db2515c21ecd9591 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 17 Aug 2018 14:29:42 +0530 Subject: [PATCH 065/154] feat: empty states for favourites and search page --- erpnext/public/js/hub/pages/favourites.js | 15 ++++++++++++--- erpnext/public/js/hub/pages/search.js | 9 +++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js index 6c2fd65580..6f2c67c01e 100644 --- a/erpnext/public/js/hub/pages/favourites.js +++ b/erpnext/public/js/hub/pages/favourites.js @@ -32,11 +32,20 @@ erpnext.hub.Favourites = class Favourites extends SubPage { } render(items) { - this.$wrapper.find('.hub-card-container').empty(); + this.$wrapper.find('.hub-items-container').empty(); const html = get_item_card_container_html(items, __('Favourites')); - this.$wrapper.append(html); - + this.$wrapper.html(html); this.$wrapper.find('.hub-card').addClass('closable'); + + if (!items.length) { + this.render_empty_state(); + } + } + + render_empty_state() { + this.$wrapper.find('.hub-items-container').append(` +
          ${__("You don't have any favourites yet.")}
          + `) } on_item_remove(hub_item_code, $hub_card = '') { diff --git a/erpnext/public/js/hub/pages/search.js b/erpnext/public/js/hub/pages/search.js index f3dd6fb0c5..1cdacb6db6 100644 --- a/erpnext/public/js/hub/pages/search.js +++ b/erpnext/public/js/hub/pages/search.js @@ -27,8 +27,13 @@ erpnext.hub.SearchPage = class SearchPage extends SubPage { } render(items) { - this.$wrapper.find('.hub-card-container').remove(); - const title = this.keyword ? __('Search results for "{0}"', [this.keyword]) : ''; + this.$wrapper.find('.hub-items-container').remove(); + const title = !items.length + ? __('No results found') + : this.keyword + ? __('Search results for "{0}"', [this.keyword]) + : ''; + const html = get_item_card_container_html(items, title); this.$wrapper.append(html); } From 335c0f200fd0729c3dbb149529e63082d319d526 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 17 Aug 2018 16:04:14 +0530 Subject: [PATCH 066/154] feat: custom cache invalidation for hub calls In some cases, we know when to invalidate the cache. This feature allows to do just that. erpnext.hub namespace is now an EventEmitter instance. --- erpnext/public/js/hub/event_emitter.js | 31 +++++++++++++++++++++++ erpnext/public/js/hub/hub_call.js | 20 ++++++++++----- erpnext/public/js/hub/marketplace.js | 3 +++ erpnext/public/js/hub/pages/favourites.js | 8 +++--- erpnext/public/js/hub/pages/item.js | 3 ++- 5 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 erpnext/public/js/hub/event_emitter.js diff --git a/erpnext/public/js/hub/event_emitter.js b/erpnext/public/js/hub/event_emitter.js new file mode 100644 index 0000000000..1e7288191d --- /dev/null +++ b/erpnext/public/js/hub/event_emitter.js @@ -0,0 +1,31 @@ +/** + * Simple EventEmitter which uses jQuery's event system + */ +class EventEmitter { + init() { + this.jq = jQuery(this); + } + + trigger(evt, data) { + !this.jq && this.init(); + this.jq.trigger(evt, data); + } + + once(evt, handler) { + !this.jq && this.init(); + this.jq.one(evt, (e, data) => handler(data)); + } + + on(evt, handler) { + !this.jq && this.init(); + this.jq.bind(evt, (e, data) => handler(data)); + } + + off(evt, handler) { + !this.jq && this.init(); + this.jq.unbind(evt, (e, data) => handler(data)); + } +} + + +export default EventEmitter; \ No newline at end of file diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js index 6bc1701551..d2eaab31b4 100644 --- a/erpnext/public/js/hub/hub_call.js +++ b/erpnext/public/js/hub/hub_call.js @@ -2,7 +2,7 @@ frappe.provide('hub'); frappe.provide('erpnext.hub'); erpnext.hub.cache = {}; -hub.call = function call_hub_method(method, args={}) { +hub.call = function call_hub_method(method, args={}, setup_cache_invalidation = invalidate_after_5_mins) { return new Promise((resolve, reject) => { // cache @@ -11,12 +11,9 @@ hub.call = function call_hub_method(method, args={}) { resolve(erpnext.hub.cache[key]); } - // cache invalidation after 5 minutes - const timeout = 5 * 60 * 1000; - - setTimeout(() => { - delete erpnext.hub.cache[key]; - }, timeout); + // cache invalidation + const clear_cache = () => delete erpnext.hub.cache[key]; + setup_cache_invalidation(clear_cache); frappe.call({ method: 'erpnext.hub_node.api.call_hub_method', @@ -42,3 +39,12 @@ hub.call = function call_hub_method(method, args={}) { .fail(reject) }); } + +function invalidate_after_5_mins(clear_cache) { + // cache invalidation after 5 minutes + const timeout = 5 * 60 * 1000; + + setTimeout(() => { + clear_cache(); + }, timeout); +} diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 154324338d..ee21d4826a 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -14,10 +14,13 @@ import './pages/not_found'; // helpers import './hub_call'; +import EventEmitter from './event_emitter'; frappe.provide('hub'); frappe.provide('erpnext.hub'); +$.extend(erpnext.hub, EventEmitter.prototype); + erpnext.hub.Marketplace = class Marketplace { constructor({ parent }) { this.$parent = $(parent); diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js index 6f2c67c01e..566f5b0e21 100644 --- a/erpnext/public/js/hub/pages/favourites.js +++ b/erpnext/public/js/hub/pages/favourites.js @@ -26,9 +26,11 @@ erpnext.hub.Favourites = class Favourites extends SubPage { } get_favourites() { - return hub.call('get_favourite_items_of_seller', { - hub_seller: hub.settings.company_email - }); + return hub.call( + 'get_favourite_items_of_seller', + { hub_seller: hub.settings.company_email }, + clear_cache => erpnext.hub.on('action:item_favourite', clear_cache) + ); } render(items) { diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index ee43391a22..a94d5845b0 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -79,9 +79,10 @@ erpnext.hub.Item = class Item extends SubPage { .then(() => { $(favourite_button).html('Saved'); frappe.show_alert(__('Saved to Favourites')); + erpnext.hub.trigger('action:item_favourite'); }) .catch(e => { - console.log(e); + console.error(e); }); } From 2ce2e1b6b0822269b6449cc1807e8219964feb97 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 17 Aug 2018 18:39:36 +0530 Subject: [PATCH 067/154] feat(messages): split messages into buying and selling - messages will be organized by their purpose, i.e Buying and Selling - on clicking the buying item, the message room page will be opened where sellers can communicate - invalidate cache using event key --- .../public/js/hub/components/detail_view.js | 10 +- erpnext/public/js/hub/components/item_card.js | 7 +- erpnext/public/js/hub/hub_call.js | 11 +- erpnext/public/js/hub/pages/favourites.js | 8 +- erpnext/public/js/hub/pages/item.js | 12 ++ erpnext/public/js/hub/pages/messages.js | 171 ++++++++---------- 6 files changed, 116 insertions(+), 103 deletions(-) diff --git a/erpnext/public/js/hub/components/detail_view.js b/erpnext/public/js/hub/components/detail_view.js index 9ef48d4d0a..cd734058aa 100644 --- a/erpnext/public/js/hub/components/detail_view.js +++ b/erpnext/public/js/hub/components/detail_view.js @@ -33,6 +33,12 @@ function get_detail_view_html(item, allow_edit) { ${__('Saved')} `; + const contact_seller_button = item.hub_seller !== hub.settings.company_email + ? `` + : ''; + let menu_items = ''; if(allow_edit) { @@ -69,9 +75,7 @@ function get_detail_view_html(item, allow_edit) {
          ${favourite_button} - + ${contact_seller_button}
          diff --git a/erpnext/public/js/hub/components/item_card.js b/erpnext/public/js/hub/components/item_card.js index f7f5975db6..9f5a6ef683 100644 --- a/erpnext/public/js/hub/components/item_card.js +++ b/erpnext/public/js/hub/components/item_card.js @@ -15,11 +15,16 @@ function get_item_card_html(item) { let dot_spacer = ''; subtitle = subtitle.join(dot_spacer); + // route + if (!item.route) { + item.route = `marketplace/item/${item.hub_item_code}` + } + const item_html = `
          + data-route="${item.route}">
          diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js index d2eaab31b4..042569d874 100644 --- a/erpnext/public/js/hub/hub_call.js +++ b/erpnext/public/js/hub/hub_call.js @@ -2,7 +2,7 @@ frappe.provide('hub'); frappe.provide('erpnext.hub'); erpnext.hub.cache = {}; -hub.call = function call_hub_method(method, args={}, setup_cache_invalidation = invalidate_after_5_mins) { +hub.call = function call_hub_method(method, args={}, clear_cache_on_event) { return new Promise((resolve, reject) => { // cache @@ -13,7 +13,14 @@ hub.call = function call_hub_method(method, args={}, setup_cache_invalidation = // cache invalidation const clear_cache = () => delete erpnext.hub.cache[key]; - setup_cache_invalidation(clear_cache); + + if (!clear_cache_on_event) { + invalidate_after_5_mins(clear_cache); + } else { + erpnext.hub.on(clear_cache_on_event, () => { + clear_cache(key) + }); + } frappe.call({ method: 'erpnext.hub_node.api.call_hub_method', diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js index 566f5b0e21..ae08a6813c 100644 --- a/erpnext/public/js/hub/pages/favourites.js +++ b/erpnext/public/js/hub/pages/favourites.js @@ -26,11 +26,9 @@ erpnext.hub.Favourites = class Favourites extends SubPage { } get_favourites() { - return hub.call( - 'get_favourite_items_of_seller', - { hub_seller: hub.settings.company_email }, - clear_cache => erpnext.hub.on('action:item_favourite', clear_cache) - ); + return hub.call('get_favourite_items_of_seller', { + hub_seller: hub.settings.company_email + }, 'action:item_favourite'); } render(items) { diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index a94d5845b0..71a4b0f5c3 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -105,6 +105,18 @@ erpnext.hub.Item = class Item extends SubPage { ], primary_action: ({ message }) => { if (!message) return; + + hub.call('send_message', { + from_seller: hub.settings.company_email, + to_seller: this.item.hub_seller, + hub_item: this.item.hub_item_code, + message + }) + .then(() => { + d.hide(); + frappe.set_route('marketplace', 'messages'); + erpnext.hub.trigger('action:send_message') + }); } }); diff --git a/erpnext/public/js/hub/pages/messages.js b/erpnext/public/js/hub/pages/messages.js index 8531e0b3f9..a1f734f6ef 100644 --- a/erpnext/public/js/hub/pages/messages.js +++ b/erpnext/public/js/hub/pages/messages.js @@ -1,118 +1,105 @@ import SubPage from './subpage'; -import { make_search_bar } from '../components/search_bar'; +import { get_item_card_container_html } from '../components/items_container'; +import { get_empty_state } from '../components/empty_state'; erpnext.hub.Messages = class Messages extends SubPage { - make_wrapper() { - super.make_wrapper(); + make_wrapper() { + super.make_wrapper(); + } - const html = ` -
          -
          -
          -
          -
          - ${get_message_area_html()} -
          -
          - `; + refresh() { + const res = Promise.all([ + this.get_buying_items(), + this.get_selling_items() + ]); - make_search_bar({ - wrapper: this.$wrapper, - on_search: keyword => { + res.then(([buying_items, selling_items]) => { + this.empty(); - }, - placeholder: __('Search for messages') - }) + if (buying_items.length) { + buying_items.map(item => { + item.route = `marketplace/buy/${item.hub_item_code}` + }) + this.render(buying_items, __('Buying')); + } - this.$wrapper.append(html); + if (selling_items.length) { + // selling_items.map(item => { + // item.route = `marketplace/sell/${item.hub_item_code}/${}` + // }); + this.render(selling_items, __('Selling')); + } - this.message_input = new frappe.ui.CommentArea({ - parent: this.$wrapper.find('.message-input'), - on_submit: (message) => { - this.message_input.reset(); + if (!buying_items.length && !selling_items.length) { + this.render_empty_state(); + } + }); + } - // append message html - const $message_list = this.$wrapper.find('.message-list'); - const message_html = get_message_html({ - sender: hub.settings.company_email, - content: message - }); - $message_list.append(message_html); - frappe.dom.scroll_to_bottom($message_list); + render(items = [], title) { + const html = get_item_card_container_html(items, title); + this.$wrapper.append(html); + } - const to_seller = frappe.get_route()[2]; - hub.call('send_message', { - from_seller: hub.settings.company_email, - to_seller, - message - }); - }, - no_wrapper: true - }); - } + render_empty_state() { + const empty_state = get_empty_state(__('You haven\'t interacted with any seller yet.')); + this.$wrapper.html(empty_state); + } - refresh() { - this.get_interactions() - .then(sellers => { - const html = sellers.map(get_list_item_html).join(''); - this.$wrapper.find('.seller-list').html(html); - }); + get_buying_items() { + return hub.call('get_buying_items_for_messages', {}, 'action:send_message'); + } - this.get_messages() - .then(messages => { - const $message_list = this.$wrapper.find('.message-list'); - const html = messages.map(get_message_html).join(''); - $message_list.html(html); - frappe.dom.scroll_to_bottom($message_list); - }); - } + get_selling_items() { + return hub.call('get_selling_items_for_messages'); + } - get_interactions() { - return hub.call('get_sellers_with_interactions', { for_seller: hub.settings.company_email }); - } + get_interactions() { + return hub.call('get_sellers_with_interactions', { for_seller: hub.settings.company_email }); + } - get_messages() { - const against_seller = frappe.get_route()[2]; - if (!against_seller) return Promise.resolve([]); + get_messages() { + const against_seller = frappe.get_route()[2]; + if (!against_seller) return Promise.resolve([]); - return hub.call('get_messages', { - for_seller: hub.settings.company_email, - against_seller: against_seller - }); - } + return hub.call('get_messages', { + for_seller: hub.settings.company_email, + against_seller: against_seller + }); + } } function get_message_area_html() { - return ` -
          -
          -
          -
          -
          -
          - `; + return ` +
          +
          +
          +
          +
          +
          + `; } function get_list_item_html(seller) { - const active_class = frappe.get_route()[2] === seller.email ? 'active' : ''; + const active_class = frappe.get_route()[2] === seller.email ? 'active' : ''; - return ` -
          -
          - -
          -
          - ${seller.company} -
          -
          - `; + return ` +
          +
          + +
          +
          + ${seller.company} +
          +
          + `; } function get_message_html(message) { - return ` -
          -
          ${message.sender}
          -

          ${message.content}

          -
          - `; + return ` +
          +
          ${message.sender}
          +

          ${message.content}

          +
          + `; } From 0a60d1cd964900dd02e81deb8e8cd0db3ab4512d Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Sun, 19 Aug 2018 19:39:00 +0530 Subject: [PATCH 068/154] [hub] Publishing Dialog, Sync category - Hub Category selection - Recycled --- erpnext/hub_node/api.py | 12 +- .../item_to_hub_item/__init__.py | 25 ++-- .../hub_tracked_item/hub_tracked_item.json | 74 +++++++++- erpnext/public/js/hub/pages/publish.js | 126 ++++++++++++++---- 4 files changed, 198 insertions(+), 39 deletions(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index 559e22a2c8..86ad031b0c 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -42,11 +42,19 @@ def get_valid_items(search_value=''): def publish_selected_items(items_to_publish): items_to_publish = json.loads(items_to_publish) if not len(items_to_publish): - return + frappe.throw('No items to publish') - for item_code in items_to_publish: + for item in items_to_publish: + item_code = item.get('item_code') frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) + frappe.get_doc({ + 'doctype': 'Hub Tracked Item', + 'item_code': item_code, + 'hub_category': item.get('hub_category'), + # 'images': item.get('images') + }).insert() + try: hub_settings = frappe.get_doc('Hub Settings') item_sync_preprocess() diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py index 9445e3a8dc..3f1352099c 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py @@ -1,19 +1,24 @@ -import io, base64, urllib, os +import frappe, io, base64, urllib, os def pre_process(doc): - file_path = doc.image - file_name = os.path.basename(file_path) + # file_path = doc.image + # file_name = os.path.basename(file_path) - if file_path.startswith('http'): - url = file_path - file_path = os.path.join('/tmp', file_name) - urllib.urlretrieve(url, file_path) + # if file_path.startswith('http'): + # url = file_path + # file_path = os.path.join('/tmp', file_name) + # urllib.urlretrieve(url, file_path) - with io.open(file_path, 'rb') as f: - doc.image = base64.b64encode(f.read()) + # with io.open(file_path, 'rb') as f: + # doc.image = base64.b64encode(f.read()) - doc.image_file_name = file_name + # doc.image_file_name = file_name + + cached_details = frappe.get_doc('Hub Tracked Item', doc.item_code) + + if cached_details: + doc.hub_category = cached_details.hub_category return doc diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json index 063c87817c..7a0bc2a251 100644 --- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json +++ b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json @@ -3,6 +3,7 @@ "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, + "autoname": "field:item_code", "beta": 0, "creation": "2018-03-18 09:33:50.267762", "custom": 0, @@ -14,6 +15,7 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -41,6 +43,70 @@ "search_index": 0, "set_only_once": 0, "translatable": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "hub_category", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Hub Category", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "images", + "fieldtype": "Long Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Images", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -49,12 +115,12 @@ "hide_toolbar": 0, "idx": 0, "image_view": 0, - "in_create": 1, + "in_create": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-03-18 09:34:01.757713", + "modified": "2018-08-19 19:30:01.449904", "modified_by": "Administrator", "module": "Hub Node", "name": "Hub Tracked Item", @@ -63,7 +129,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -89,5 +154,6 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index a76f9467c6..035278bbcd 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -3,13 +3,17 @@ import { get_item_card_container_html } from '../components/items_container'; import { get_local_item_card_html } from '../components/item_card'; import { make_search_bar } from '../components/search_bar'; + erpnext.hub.Publish = class Publish extends SubPage { make_wrapper() { super.make_wrapper(); - this.items_to_publish = []; + this.items_to_publish = {}; this.unpublished_items = []; this.fetched_items = []; + this.cache = erpnext.hub.cache.items_to_publish; + this.cache = []; + frappe.realtime.on("items-sync", (data) => { this.$wrapper.find('.progress-bar').css('width', data.progress_percent+'%'); @@ -58,7 +62,7 @@ erpnext.hub.Publish = class Publish extends SubPage { } get_publishing_header() { - const title_html = `${__('Select Products to Publish')}`; + const title_html = `
          ${__('Select Products to Publish')}
          `; const subtitle_html = `

          ${__(`Only products with an image, description and category can be published. @@ -71,6 +75,9 @@ erpnext.hub.Publish = class Publish extends SubPage { `; return $(` +

          ${__('Selected Products')}
          +
          +
          ${title_html} @@ -87,27 +94,97 @@ erpnext.hub.Publish = class Publish extends SubPage { .then(this.refresh.bind(this)) }); + this.selected_items_container = this.$wrapper.find('.selected-items'); + + this.$current_selected_card = null; + + this.make_publishing_dialog(); + this.$wrapper.on('click', '.hub-card', (e) => { const $target = $(e.currentTarget); - $target.toggleClass('active'); + const item_code = $target.attr('data-id'); + this.show_publishing_dialog_for_item(item_code); - // Get total items - const total_items = this.$wrapper.find('.hub-card.active').length; + this.$current_selected_card = $target.parent(); - let button_label; - if (total_items > 0) { - const more_than_one = total_items > 1; - button_label = __('Publish {0} item{1}', [total_items, more_than_one ? 's' : '']); - } else { - button_label = __('Publish'); - } + this.update_selected_items_count() - this.$wrapper.find('.publish-items') - .text(button_label) - .prop('disabled', total_items === 0); }); } + make_publishing_dialog() { + this.publishing_dialog = new frappe.ui.Dialog({ + title: __('Edit Publishing Details'), + fields: [ + { + "label": "Item Code", + "fieldname": "item_code", + "fieldtype": "Data", + "read_only": 1 + }, + { + "label": "Hub Category", + "fieldname": "hub_category", + "fieldtype": "Autocomplete", + "options": ["Agriculture", "Books", "Chemicals", "Clothing", + "Electrical", "Electronics", "Energy", "Fashion", "Food and Beverage", + "Health", "Home", "Industrial", "Machinery", "Packaging and Printing", + "Sports", "Transportation" + ], + "reqd": 1 + } + ], + primary_action_label: __('Set Details'), + primary_action: () => { + const values = this.publishing_dialog.get_values(true); + this.items_to_publish[values.item_code] = values; + + this.$current_selected_card.appendTo(this.selected_items_container); + this.$current_selected_card.find('.hub-card').toggleClass('active'); + + this.publishing_dialog.hide(); + }, + secondary_action: () => { + const values = this.publishing_dialog.get_values(true); + this.items_to_publish[values.item_code] = values; + } + }); + } + + show_publishing_dialog_for_item(item_code) { + let item_data = this.items_to_publish[item_code]; + + if(!item_data) { item_data = { item_code }; } + + this.publishing_dialog.clear(); + this.publishing_dialog.set_values(item_data); + this.publishing_dialog.show(); + } + + update_selected_items_count() { + const total_items = this.$wrapper.find('.hub-card.active').length; + + let button_label; + if (total_items > 0) { + const more_than_one = total_items > 1; + button_label = __('Publish {0} item{1}', [total_items, more_than_one ? 's' : '']); + } else { + button_label = __('Publish'); + } + + this.$wrapper.find('.publish-items') + .text(button_label) + .prop('disabled', total_items === 0); + } + + add_item_to_publish() { + // + } + + remove_item_from_publish() { + // + } + show_message(message) { const $message = $(`

          @@ -211,19 +288,22 @@ erpnext.hub.Publish = class Publish extends SubPage { item_codes_to_publish.push($(this).attr("data-id")); }); - this.unpublished_items = this.fetched_items.filter(item => { - return !item_codes_to_publish.includes(item.item_code); - }); + // this.unpublished_items = this.fetched_items.filter(item => { + // return !item_codes_to_publish.includes(item.item_code); + // }); - const items_to_publish = this.fetched_items.filter(item => { - return item_codes_to_publish.includes(item.item_code); - }); - this.items_to_publish = items_to_publish; + // const items_to_publish = this.fetched_items.filter(item => { + // return item_codes_to_publish.includes(item.item_code); + // }); + + // this.items_to_publish = items_to_publish; + + const items_data_to_publish = item_codes_to_publish.map(item_code => this.items_to_publish[item_code]) return frappe.call( 'erpnext.hub_node.api.publish_selected_items', { - items_to_publish: item_codes_to_publish + items_to_publish: items_data_to_publish } ) } From a525d12f695a0b047955307a4affe700603055ea Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Sun, 19 Aug 2018 22:31:33 +0530 Subject: [PATCH 069/154] [hub] sync multiple image urls - using a simple multiselect currently --- erpnext/hub_node/api.py | 9 +++++--- .../item_to_hub_item/__init__.py | 1 + .../item_to_hub_item/item_to_hub_item.json | 12 +++++++++- .../hub_sync/hub_sync.json | 2 +- .../hub_tracked_item/hub_tracked_item.json | 6 ++--- erpnext/public/js/hub/pages/publish.js | 22 ++++++++++++++++++- 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index 86ad031b0c..0c9af1abc1 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import frappe, requests, json from frappe.utils import now from frappe.frappeclient import FrappeClient +from frappe.desk.form.load import get_attachments @frappe.whitelist() def call_hub_method(method, params=None): @@ -31,11 +32,13 @@ def get_valid_items(search_value=''): valid_items = filter(lambda x: x.image and x.description, items) - def attach_source_type(item): + def prepare_item(item): item.source_type = "local" + item.attachments = get_attachments('Item', item.item_code) return item - valid_items = map(lambda x: attach_source_type(x), valid_items) + valid_items = map(lambda x: prepare_item(x), valid_items) + return valid_items @frappe.whitelist() @@ -52,7 +55,7 @@ def publish_selected_items(items_to_publish): 'doctype': 'Hub Tracked Item', 'item_code': item_code, 'hub_category': item.get('hub_category'), - # 'images': item.get('images') + 'image_list': item.get('image_list') }).insert() try: diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py index 3f1352099c..0b6b2bc8a8 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py @@ -19,6 +19,7 @@ def pre_process(doc): if cached_details: doc.hub_category = cached_details.hub_category + doc.image_list = cached_details.image_list return doc diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json index 3ace088a80..bcece69b38 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json @@ -24,10 +24,20 @@ "local_fieldname": "image", "remote_fieldname": "image" }, + { + "is_child_table": 0, + "local_fieldname": "image_list", + "remote_fieldname": "image_list" + }, { "is_child_table": 0, "local_fieldname": "item_group", "remote_fieldname": "item_group" + }, + { + "is_child_table": 0, + "local_fieldname": "hub_category", + "remote_fieldname": "hub_category" } ], "idx": 1, @@ -35,7 +45,7 @@ "mapping_name": "Item to Hub Item", "mapping_type": "Push", "migration_id_field": "hub_sync_id", - "modified": "2018-08-01 16:37:09.170546", + "modified": "2018-08-19 22:20:25.727581", "modified_by": "Administrator", "name": "Item to Hub Item", "owner": "Administrator", diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json index 1f772b68f0..e90b1dd1e8 100644 --- a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json +++ b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json @@ -9,7 +9,7 @@ "mapping": "Item to Hub Item" } ], - "modified": "2018-08-01 16:37:09.027512", + "modified": "2018-08-19 22:20:25.644602", "modified_by": "Administrator", "module": "Hub Node", "name": "Hub Sync", diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json index 7a0bc2a251..9384adbd1d 100644 --- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json +++ b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json @@ -84,7 +84,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "images", + "fieldname": "image_list", "fieldtype": "Long Text", "hidden": 0, "ignore_user_permissions": 0, @@ -93,7 +93,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Images", + "label": "Image List", "length": 0, "no_copy": 0, "permlevel": 0, @@ -120,7 +120,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-19 19:30:01.449904", + "modified": "2018-08-19 22:24:06.207307", "modified_by": "Administrator", "module": "Hub Node", "name": "Hub Tracked Item", diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index 035278bbcd..fcbd0f8b8a 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -10,6 +10,7 @@ erpnext.hub.Publish = class Publish extends SubPage { this.items_to_publish = {}; this.unpublished_items = []; this.fetched_items = []; + this.fetched_items_dict = {}; this.cache = erpnext.hub.cache.items_to_publish; this.cache = []; @@ -132,6 +133,13 @@ erpnext.hub.Publish = class Publish extends SubPage { "Sports", "Transportation" ], "reqd": 1 + }, + { + "label": "Images", + "fieldname": "image_list", + "fieldtype": "MultiSelect", + "options": [], + "reqd": 1 } ], primary_action_label: __('Set Details'), @@ -154,9 +162,17 @@ erpnext.hub.Publish = class Publish extends SubPage { show_publishing_dialog_for_item(item_code) { let item_data = this.items_to_publish[item_code]; - if(!item_data) { item_data = { item_code }; } + if(!item_data) { item_data = { item_code }; }; this.publishing_dialog.clear(); + + const item_doc = this.fetched_items_dict[item_code]; + if(item_doc) { + this.publishing_dialog.fields_dict.image_list.set_data( + item_doc.attachments.map(attachment => attachment.file_url) + ); + } + this.publishing_dialog.set_values(item_data); this.publishing_dialog.show(); } @@ -268,6 +284,10 @@ erpnext.hub.Publish = class Publish extends SubPage { const items_container = $(get_item_card_container_html(items, '', get_local_item_card_html)); items_container.addClass('results'); wrapper.append(items_container); + + items.map(item => { + this.fetched_items_dict[item.item_code] = item; + }) } get_valid_items() { From 305f7375d16c541da9ea0752520e0b566ce31bef Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 20 Aug 2018 00:25:26 +0530 Subject: [PATCH 070/154] [hub] Register in a dialog instead of Page - in order to recycle it for Edit Profile - Also, the register page had too much custom code --- .../js/hub/components/profile_dialog.js | 75 +++++++++++++++++++ erpnext/public/js/hub/marketplace.js | 38 +++++++++- erpnext/public/js/hub/pages/profile.js | 29 ++++++- erpnext/public/js/hub/pages/subpage.js | 10 --- erpnext/public/less/hub.less | 2 +- 5 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 erpnext/public/js/hub/components/profile_dialog.js diff --git a/erpnext/public/js/hub/components/profile_dialog.js b/erpnext/public/js/hub/components/profile_dialog.js new file mode 100644 index 0000000000..0800b8a287 --- /dev/null +++ b/erpnext/public/js/hub/components/profile_dialog.js @@ -0,0 +1,75 @@ +const ProfileDialog = (title = __('Edit Profile'), action={}) => { + const fields = [ + { + fieldtype: 'Link', + fieldname: 'company', + label: __('Company'), + options: 'Company', + onchange: () => { + const value = dialog.get_value('company'); + + if (value) { + frappe.db.get_doc('Company', value) + .then(company => { + dialog.set_values({ + country: company.country, + company_email: company.email, + currency: company.default_currency + }); + }); + } + } + }, + { + fieldname: 'company_email', + label: __('Email'), + fieldtype: 'Data' + }, + { + fieldname: 'country', + label: __('Country'), + fieldtype: 'Read Only' + }, + { + fieldname: 'currency', + label: __('Currency'), + fieldtype: 'Read Only' + }, + { + fieldtype: 'Text', + label: __('About your Company'), + fieldname: 'company_description' + } + ]; + + let dialog = new frappe.ui.Dialog({ + title: title, + fields: fields, + primary_action_label: action.label || __('Update'), + primary_action: () => { + const form_values = dialog.get_values(); + let values_filled = true; + const mandatory_fields = ['company', 'company_email', 'company_description']; + mandatory_fields.forEach(field => { + const value = form_values[field]; + if (!value) { + dialog.set_df_property(field, 'reqd', 1); + values_filled = false; + } + }); + if (!values_filled) return; + + action.on_submit(form_values); + } + }); + + // Post create + const default_company = frappe.defaults.get_default('company'); + dialog.set_value('company', default_company); + + return dialog; +} + +export { + ProfileDialog +} diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 154324338d..cadaea0791 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -12,6 +12,9 @@ import './pages/published_products'; import './pages/messages'; import './pages/not_found'; +// components +import { ProfileDialog } from './components/profile_dialog'; + // helpers import './hub_call'; @@ -46,6 +49,16 @@ erpnext.hub.Marketplace = class Marketplace { const route = $target.data().route; frappe.set_route(route); }); + + // generic action handler + this.$parent.on('click', '[data-action]', e => { + const $target = $(e.currentTarget); + const action = $target.data().action; + + if (action && this[action]) { + this[action].apply(this, $target); + } + }) } make_sidebar() { @@ -76,7 +89,7 @@ erpnext.hub.Marketplace = class Marketplace { ${__('Messages')} ` - : `

        • + : `
        • ${__('Become a seller')}
        • `; @@ -215,4 +228,27 @@ erpnext.hub.Marketplace = class Marketplace { frappe.utils.scroll_to(0); this.subpages[route[1]].show(); } + + show_register_dialog() { + this.profile_dialog = ProfileDialog(__('Become a Seller'), { + label: __('Register'), + on_submit: this.register_seller.bind(this) + }); + + this.profile_dialog.show(); + } + + register_seller(form_values) { + frappe.call({ + method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', + args: form_values, + btn: $(e.currentTarget) + }).then(() => { + this.profile_dialog.hide(); + frappe.set_route('marketplace', 'publish'); + + // custom jquery event + this.$body.trigger('seller-registered'); + }); + } } diff --git a/erpnext/public/js/hub/pages/profile.js b/erpnext/public/js/hub/pages/profile.js index a38cde4276..637e7b9c60 100644 --- a/erpnext/public/js/hub/pages/profile.js +++ b/erpnext/public/js/hub/pages/profile.js @@ -1,11 +1,14 @@ import SubPage from './subpage'; +import { get_detail_skeleton_html } from '../components/skeleton_state'; erpnext.hub.Profile = class Profile extends SubPage { make_wrapper() { super.make_wrapper(); + this.make_edit_profile_dialog(); } refresh() { + this.show_skeleton(); this.get_hub_seller_profile(this.keyword) .then(profile => this.render(profile)); } @@ -14,6 +17,18 @@ erpnext.hub.Profile = class Profile extends SubPage { return hub.call('get_hub_seller_profile', { hub_seller: hub.settings.company_email }); } + make_edit_profile_dialog() { + // this.edit_profile_dialog = new + } + + edit_profile() { + // + } + + show_skeleton() { + this.$wrapper.html(get_detail_skeleton_html()); + } + render(profile) { const p = profile; const content_by_log_type = this.get_content_by_log_type(); @@ -46,7 +61,7 @@ erpnext.hub.Profile = class Profile extends SubPage {
          -
          +

          ${p.company}

          ${p.country}

          @@ -60,6 +75,16 @@ erpnext.hub.Profile = class Profile extends SubPage { }
          +
          @@ -95,4 +120,4 @@ erpnext.hub.Profile = class Profile extends SubPage { } } } -} \ No newline at end of file +} diff --git a/erpnext/public/js/hub/pages/subpage.js b/erpnext/public/js/hub/pages/subpage.js index 7c75b1379e..3f4ed07171 100644 --- a/erpnext/public/js/hub/pages/subpage.js +++ b/erpnext/public/js/hub/pages/subpage.js @@ -3,16 +3,6 @@ export default class SubPage { this.$parent = $(parent); this.make_wrapper(options); - // generic action handler - this.$wrapper.on('click', '[data-action]', e => { - const $target = $(e.currentTarget); - const action = $target.data().action; - - if (action && this[action]) { - this[action].apply(this, $target); - } - }) - // handle broken images after every render if (this.render) { this._render = this.render.bind(this); diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index ac0aa42ae1..0859ae6a30 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -6,7 +6,7 @@ body[data-route^="marketplace/"] { padding-right: 25px; } - [data-route] { + [data-route], [data-action] { cursor: pointer; } From fe67508d609c86dca1025ccb4d069896844f56a5 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 20 Aug 2018 01:18:41 +0530 Subject: [PATCH 071/154] [hub][feat] Edit your Profile, recycle profile dialog --- .../js/hub/components/profile_dialog.js | 4 +- erpnext/public/js/hub/marketplace.js | 15 ++++--- erpnext/public/js/hub/pages/profile.js | 41 +++++++++++++++---- erpnext/public/js/hub/pages/subpage.js | 10 +++++ 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/erpnext/public/js/hub/components/profile_dialog.js b/erpnext/public/js/hub/components/profile_dialog.js index 0800b8a287..800e8c66f0 100644 --- a/erpnext/public/js/hub/components/profile_dialog.js +++ b/erpnext/public/js/hub/components/profile_dialog.js @@ -1,4 +1,4 @@ -const ProfileDialog = (title = __('Edit Profile'), action={}) => { +const ProfileDialog = (title = __('Edit Profile'), action={}, initial_values={}) => { const fields = [ { fieldtype: 'Link', @@ -63,6 +63,8 @@ const ProfileDialog = (title = __('Edit Profile'), action={}) => { } }); + dialog.set_values(initial_values); + // Post create const default_company = frappe.defaults.get_default('company'); dialog.set_value('company', default_company); diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index cadaea0791..1986cfb441 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -230,12 +230,15 @@ erpnext.hub.Marketplace = class Marketplace { } show_register_dialog() { - this.profile_dialog = ProfileDialog(__('Become a Seller'), { - label: __('Register'), - on_submit: this.register_seller.bind(this) - }); + this.register_dialog = ProfileDialog( + __('Become a Seller'), + { + label: __('Register'), + on_submit: this.register_seller.bind(this) + } + ); - this.profile_dialog.show(); + this.register_dialog.show(); } register_seller(form_values) { @@ -244,7 +247,7 @@ erpnext.hub.Marketplace = class Marketplace { args: form_values, btn: $(e.currentTarget) }).then(() => { - this.profile_dialog.hide(); + this.register_dialog.hide(); frappe.set_route('marketplace', 'publish'); // custom jquery event diff --git a/erpnext/public/js/hub/pages/profile.js b/erpnext/public/js/hub/pages/profile.js index 637e7b9c60..a57bc7ceec 100644 --- a/erpnext/public/js/hub/pages/profile.js +++ b/erpnext/public/js/hub/pages/profile.js @@ -1,5 +1,6 @@ import SubPage from './subpage'; import { get_detail_skeleton_html } from '../components/skeleton_state'; +import { ProfileDialog } from '../components/profile_dialog'; erpnext.hub.Profile = class Profile extends SubPage { make_wrapper() { @@ -10,21 +11,16 @@ erpnext.hub.Profile = class Profile extends SubPage { refresh() { this.show_skeleton(); this.get_hub_seller_profile(this.keyword) - .then(profile => this.render(profile)); + .then(profile => { + this.edit_profile_dialog.set_values(profile); + this.render(profile); + }); } get_hub_seller_profile() { return hub.call('get_hub_seller_profile', { hub_seller: hub.settings.company_email }); } - make_edit_profile_dialog() { - // this.edit_profile_dialog = new - } - - edit_profile() { - // - } - show_skeleton() { this.$wrapper.html(get_detail_skeleton_html()); } @@ -98,6 +94,33 @@ erpnext.hub.Profile = class Profile extends SubPage { this.$wrapper.html(profile_html); } + make_edit_profile_dialog() { + this.edit_profile_dialog = ProfileDialog( + __('Edit Profile'), + { + label: __('Update'), + on_submit: this.update_profile.bind(this) + } + ); + } + + edit_profile() { + this.edit_profile_dialog.set_values({ + company_email: hub.settings.company_email + }); + this.edit_profile_dialog.show(); + } + + update_profile(new_values) { + hub.call('update_profile', { + hub_seller: hub.settings.company_email, + updated_profile: new_values + }).then(new_profile => { + this.edit_profile_dialog.hide(); + this.render(new_profile); + }); + } + get_timeline_log_item(pretty_date, message, icon) { return `
          diff --git a/erpnext/public/js/hub/pages/subpage.js b/erpnext/public/js/hub/pages/subpage.js index 3f4ed07171..7c75b1379e 100644 --- a/erpnext/public/js/hub/pages/subpage.js +++ b/erpnext/public/js/hub/pages/subpage.js @@ -3,6 +3,16 @@ export default class SubPage { this.$parent = $(parent); this.make_wrapper(options); + // generic action handler + this.$wrapper.on('click', '[data-action]', e => { + const $target = $(e.currentTarget); + const action = $target.data().action; + + if (action && this[action]) { + this[action].apply(this, $target); + } + }) + // handle broken images after every render if (this.render) { this._render = this.render.bind(this); From 96fdbd467daf50c74d03e114725f685413c4e15a Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 20 Aug 2018 10:07:56 +0530 Subject: [PATCH 072/154] [hub][feat] stateful publishing area --- erpnext/public/js/hub/pages/publish.js | 29 ++++++++++++++++++-------- erpnext/public/less/hub.less | 19 +++++++++++++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index fcbd0f8b8a..46e4545d9b 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -70,21 +70,27 @@ erpnext.hub.Publish = class Publish extends SubPage { Please update them if an item in your inventory does not appear.`)}

          `; - const publish_button_html = ``; return $(` -
          ${__('Selected Products')}
          -
          +
          +
          + ${title_html} + ${publish_button_html} +
          +
          +

          ${__('No Items Selected')}

          +
          +
          +
          - ${title_html} ${subtitle_html}
          - ${publish_button_html}
          `); } @@ -108,8 +114,6 @@ erpnext.hub.Publish = class Publish extends SubPage { this.$current_selected_card = $target.parent(); - this.update_selected_items_count() - }); } @@ -150,6 +154,8 @@ erpnext.hub.Publish = class Publish extends SubPage { this.$current_selected_card.appendTo(this.selected_items_container); this.$current_selected_card.find('.hub-card').toggleClass('active'); + this.update_selected_items_count(); + this.publishing_dialog.hide(); }, secondary_action: () => { @@ -180,6 +186,8 @@ erpnext.hub.Publish = class Publish extends SubPage { update_selected_items_count() { const total_items = this.$wrapper.find('.hub-card.active').length; + const is_empty = total_items === 0; + let button_label; if (total_items > 0) { const more_than_one = total_items > 1; @@ -190,7 +198,10 @@ erpnext.hub.Publish = class Publish extends SubPage { this.$wrapper.find('.publish-items') .text(button_label) - .prop('disabled', total_items === 0); + .prop('disabled', is_empty); + + this.$wrapper.find('.publish-area').toggleClass('empty', is_empty); + this.$wrapper.find('.publish-area').toggleClass('filled', !is_empty); } add_item_to_publish() { @@ -230,7 +241,7 @@ erpnext.hub.Publish = class Publish extends SubPage { this.$wrapper.append(subtitle_html); - // Show search list with only desctiption, and don't set any events + // Show search list with only description, and don't set any events make_search_bar({ wrapper: this.$wrapper, on_search: keyword => { diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 0859ae6a30..2bfb1094a0 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -211,6 +211,25 @@ body[data-route^="marketplace/"] { height: 500px; } + .empty-items-container { + height: 80px; + border-radius: 4px; + border: 1px solid @border-color; + margin: 15px 0px; + } + + .publish-area.filled { + .empty-items-container { + display: none; + } + } + + .publish-area.empty { + .hub-items-container { + display: none; + } + } + .form-container { .frappe-control { max-width: 100% !important; From 4ce54b001100097611d3a6e236b4f0a6df3acade Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 20 Aug 2018 13:07:23 +0530 Subject: [PATCH 073/154] [feat] new component: NotificationMessage --- .../js/hub/components/notification_message.js | 20 +++++++++++++++++++ erpnext/public/js/hub/pages/publish.js | 17 ---------------- erpnext/public/js/hub/pages/subpage.js | 6 ++++++ 3 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 erpnext/public/js/hub/components/notification_message.js diff --git a/erpnext/public/js/hub/components/notification_message.js b/erpnext/public/js/hub/components/notification_message.js new file mode 100644 index 0000000000..890c298674 --- /dev/null +++ b/erpnext/public/js/hub/components/notification_message.js @@ -0,0 +1,20 @@ +const NotificationMessage = (message) => { + const $message = $(`
          +

          + + ${message} + + +

          +
          `); + + $message.find('.octicon-x').on('click', () => { + $message.remove(); + }); + + return $message; +} + +export { + NotificationMessage +} diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index 46e4545d9b..8de7b3844c 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -212,23 +212,6 @@ erpnext.hub.Publish = class Publish extends SubPage { // } - show_message(message) { - const $message = $(`
          -

          - - ${message} - - -

          -
          `); - - $message.find('.octicon-x').on('click', () => { - $message.remove(); - }); - - this.$wrapper.prepend($message); - } - make_publish_in_progress_state() { this.$wrapper.empty(); diff --git a/erpnext/public/js/hub/pages/subpage.js b/erpnext/public/js/hub/pages/subpage.js index 7c75b1379e..fd058387a0 100644 --- a/erpnext/public/js/hub/pages/subpage.js +++ b/erpnext/public/js/hub/pages/subpage.js @@ -1,3 +1,5 @@ +import { NotificationMessage } from '../components/notification_message'; + export default class SubPage { constructor(parent, options) { this.$parent = $(parent); @@ -42,4 +44,8 @@ export default class SubPage { hide() { this.$wrapper.hide(); } + + show_message(message) { + this.$wrapper.prepend(NotificationMessage(message)); + } } From 6faffa5007764e732912d030dd95e711c37b4605 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 20 Aug 2018 14:02:33 +0530 Subject: [PATCH 074/154] [feat] new component: ItemPublishDialog --- .../js/hub/components/item_publish_dialog.js | 39 +++++ .../js/hub/components/publishing_area.js | 36 +++++ erpnext/public/js/hub/pages/publish.js | 149 +++++------------- 3 files changed, 118 insertions(+), 106 deletions(-) create mode 100644 erpnext/public/js/hub/components/item_publish_dialog.js create mode 100644 erpnext/public/js/hub/components/publishing_area.js diff --git a/erpnext/public/js/hub/components/item_publish_dialog.js b/erpnext/public/js/hub/components/item_publish_dialog.js new file mode 100644 index 0000000000..f751be0145 --- /dev/null +++ b/erpnext/public/js/hub/components/item_publish_dialog.js @@ -0,0 +1,39 @@ +function ItemPublishDialog(primary_action, secondary_action) { + let dialog = new frappe.ui.Dialog({ + title: __('Edit Publishing Details'), + fields: [ + { + "label": "Item Code", + "fieldname": "item_code", + "fieldtype": "Data", + "read_only": 1 + }, + { + "label": "Hub Category", + "fieldname": "hub_category", + "fieldtype": "Autocomplete", + "options": ["Agriculture", "Books", "Chemicals", "Clothing", + "Electrical", "Electronics", "Energy", "Fashion", "Food and Beverage", + "Health", "Home", "Industrial", "Machinery", "Packaging and Printing", + "Sports", "Transportation" + ], + "reqd": 1 + }, + { + "label": "Images", + "fieldname": "image_list", + "fieldtype": "MultiSelect", + "options": [], + "reqd": 1 + } + ], + primary_action_label: primary_action.label || __('Set Details'), + primary_action: primary_action.fn, + secondary_action: secondary_action.fn + }); + return dialog; +} + +export { + ItemPublishDialog +} diff --git a/erpnext/public/js/hub/components/publishing_area.js b/erpnext/public/js/hub/components/publishing_area.js new file mode 100644 index 0000000000..4775c3b16e --- /dev/null +++ b/erpnext/public/js/hub/components/publishing_area.js @@ -0,0 +1,36 @@ +function get_publishing_header() { + const title_html = `
          ${__('Select Products to Publish')}
          `; + + const subtitle_html = `

          + ${__(`Only products with an image, description and category can be published. + Please update them if an item in your inventory does not appear.`)} +

          `; + + const publish_button_html = ``; + + return $(` +
          +
          + ${title_html} + ${publish_button_html} +
          +
          +

          ${__('No Items Selected')}

          +
          +
          +
          + +
          +
          + ${subtitle_html} +
          +
          + `); +} + +export { + get_publishing_header +} diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index 8de7b3844c..98b0a61845 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -2,12 +2,13 @@ import SubPage from './subpage'; import { get_item_card_container_html } from '../components/items_container'; import { get_local_item_card_html } from '../components/item_card'; import { make_search_bar } from '../components/search_bar'; - +import { get_publishing_header } from '../components/publishing_area'; +import { ItemPublishDialog } from '../components/item_publish_dialog'; erpnext.hub.Publish = class Publish extends SubPage { make_wrapper() { super.make_wrapper(); - this.items_to_publish = {}; + this.items_data_to_publish = {}; this.unpublished_items = []; this.fetched_items = []; this.fetched_items_dict = {}; @@ -41,7 +42,7 @@ erpnext.hub.Publish = class Publish extends SubPage { make_publish_ready_state() { this.$wrapper.empty(); - this.$wrapper.append(this.get_publishing_header()); + this.$wrapper.append(get_publishing_header()); make_search_bar({ wrapper: this.$wrapper, @@ -53,46 +54,15 @@ erpnext.hub.Publish = class Publish extends SubPage { }); this.setup_publishing_events(); + this.show_last_sync_message(); + this.get_items_and_render(); + } + show_last_sync_message() { if(hub.settings.last_sync_datetime) { this.show_message(`Last sync was ${comment_when(hub.settings.last_sync_datetime)}. See your Published Products.`); } - - this.get_items_and_render(); - } - - get_publishing_header() { - const title_html = `
          ${__('Select Products to Publish')}
          `; - - const subtitle_html = `

          - ${__(`Only products with an image, description and category can be published. - Please update them if an item in your inventory does not appear.`)} -

          `; - - const publish_button_html = ``; - - return $(` -
          -
          - ${title_html} - ${publish_button_html} -
          -
          -

          ${__('No Items Selected')}

          -
          -
          -
          - -
          -
          - ${subtitle_html} -
          -
          - `); } setup_publishing_events() { @@ -118,69 +88,54 @@ erpnext.hub.Publish = class Publish extends SubPage { } make_publishing_dialog() { - this.publishing_dialog = new frappe.ui.Dialog({ - title: __('Edit Publishing Details'), - fields: [ - { - "label": "Item Code", - "fieldname": "item_code", - "fieldtype": "Data", - "read_only": 1 - }, - { - "label": "Hub Category", - "fieldname": "hub_category", - "fieldtype": "Autocomplete", - "options": ["Agriculture", "Books", "Chemicals", "Clothing", - "Electrical", "Electronics", "Energy", "Fashion", "Food and Beverage", - "Health", "Home", "Industrial", "Machinery", "Packaging and Printing", - "Sports", "Transportation" - ], - "reqd": 1 - }, - { - "label": "Images", - "fieldname": "image_list", - "fieldtype": "MultiSelect", - "options": [], - "reqd": 1 + this.item_publish_dialog = ItemPublishDialog( + { + fn: (values) => { + this.add_item_to_publish(values); + this.item_publish_dialog.hide(); } - ], - primary_action_label: __('Set Details'), - primary_action: () => { - const values = this.publishing_dialog.get_values(true); - this.items_to_publish[values.item_code] = values; - - this.$current_selected_card.appendTo(this.selected_items_container); - this.$current_selected_card.find('.hub-card').toggleClass('active'); - - this.update_selected_items_count(); - - this.publishing_dialog.hide(); }, - secondary_action: () => { - const values = this.publishing_dialog.get_values(true); - this.items_to_publish[values.item_code] = values; + { + fn: () => { + const values = this.item_publish_dialog.get_values(true); + this.update_items_data_to_publish(values); + } } - }); + ); + } + + add_item_to_publish(values) { + this.update_items_data_to_publish(values); + this.select_current_card() + } + + update_items_data_to_publish(values) { + this.items_data_to_publish[values.item_code] = values; + } + + select_current_card() { + this.$current_selected_card.appendTo(this.selected_items_container); + this.$current_selected_card.find('.hub-card').toggleClass('active'); + + this.update_selected_items_count(); } show_publishing_dialog_for_item(item_code) { - let item_data = this.items_to_publish[item_code]; + let item_data = this.items_data_to_publish[item_code]; if(!item_data) { item_data = { item_code }; }; - this.publishing_dialog.clear(); + this.item_publish_dialog.clear(); const item_doc = this.fetched_items_dict[item_code]; if(item_doc) { - this.publishing_dialog.fields_dict.image_list.set_data( + this.item_publish_dialog.fields_dict.image_list.set_data( item_doc.attachments.map(attachment => attachment.file_url) ); } - this.publishing_dialog.set_values(item_data); - this.publishing_dialog.show(); + this.item_publish_dialog.set_values(item_data); + this.item_publish_dialog.show(); } update_selected_items_count() { @@ -204,14 +159,6 @@ erpnext.hub.Publish = class Publish extends SubPage { this.$wrapper.find('.publish-area').toggleClass('filled', !is_empty); } - add_item_to_publish() { - // - } - - remove_item_from_publish() { - // - } - make_publish_in_progress_state() { this.$wrapper.empty(); @@ -238,8 +185,8 @@ erpnext.hub.Publish = class Publish extends SubPage { } show_publish_progress() { - const items_to_publish = this.items_to_publish.length - ? this.items_to_publish + const items_to_publish = this.items_data_to_publish.length + ? this.items_data_to_publish : JSON.parse(hub.settings.custom_data); const $publish_progress = $(`
          @@ -302,17 +249,7 @@ erpnext.hub.Publish = class Publish extends SubPage { item_codes_to_publish.push($(this).attr("data-id")); }); - // this.unpublished_items = this.fetched_items.filter(item => { - // return !item_codes_to_publish.includes(item.item_code); - // }); - - // const items_to_publish = this.fetched_items.filter(item => { - // return item_codes_to_publish.includes(item.item_code); - // }); - - // this.items_to_publish = items_to_publish; - - const items_data_to_publish = item_codes_to_publish.map(item_code => this.items_to_publish[item_code]) + const items_data_to_publish = item_codes_to_publish.map(item_code => this.items_data_to_publish[item_code]) return frappe.call( 'erpnext.hub_node.api.publish_selected_items', From e72a2fa9dea215aa3249719879ad6ab8ed4d6207 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 20 Aug 2018 14:18:07 +0530 Subject: [PATCH 075/154] [hub] emit hub call event to set category options while publishing --- .../js/hub/components/item_publish_dialog.js | 15 ++++++++++----- erpnext/public/js/hub/hub_call.js | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/hub/components/item_publish_dialog.js b/erpnext/public/js/hub/components/item_publish_dialog.js index f751be0145..2c215799a4 100644 --- a/erpnext/public/js/hub/components/item_publish_dialog.js +++ b/erpnext/public/js/hub/components/item_publish_dialog.js @@ -12,11 +12,7 @@ function ItemPublishDialog(primary_action, secondary_action) { "label": "Hub Category", "fieldname": "hub_category", "fieldtype": "Autocomplete", - "options": ["Agriculture", "Books", "Chemicals", "Clothing", - "Electrical", "Electronics", "Energy", "Fashion", "Food and Beverage", - "Health", "Home", "Industrial", "Machinery", "Packaging and Printing", - "Sports", "Transportation" - ], + "options": [], "reqd": 1 }, { @@ -31,6 +27,15 @@ function ItemPublishDialog(primary_action, secondary_action) { primary_action: primary_action.fn, secondary_action: secondary_action.fn }); + + const hub_call_key = 'get_categories{}'; + + erpnext.hub.on(`response:${hub_call_key}`, () => { + dialog.fields_dict.hub_category.set_data( + erpnext.hub.cache[hub_call_key].map(d => d.name) + ); + }); + return dialog; } diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js index d2eaab31b4..2ed19f2739 100644 --- a/erpnext/public/js/hub/hub_call.js +++ b/erpnext/public/js/hub/hub_call.js @@ -32,6 +32,7 @@ hub.call = function call_hub_method(method, args={}, setup_cache_invalidation = } erpnext.hub.cache[key] = r.message; + erpnext.hub.trigger(`response:${key}`); resolve(r.message); } reject(r); From 71b41b47713a4617fb4bc93e1c0a203648866d85 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 20 Aug 2018 15:36:13 +0530 Subject: [PATCH 076/154] [hub] response data in hub call event trigger --- .../js/hub/components/item_publish_dialog.js | 16 +++++++++++++--- erpnext/public/js/hub/hub_call.js | 11 ++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/hub/components/item_publish_dialog.js b/erpnext/public/js/hub/components/item_publish_dialog.js index 2c215799a4..a5d312c7b8 100644 --- a/erpnext/public/js/hub/components/item_publish_dialog.js +++ b/erpnext/public/js/hub/components/item_publish_dialog.js @@ -28,12 +28,22 @@ function ItemPublishDialog(primary_action, secondary_action) { secondary_action: secondary_action.fn }); - const hub_call_key = 'get_categories{}'; - erpnext.hub.on(`response:${hub_call_key}`, () => { + function set_hub_category_options(data) { dialog.fields_dict.hub_category.set_data( - erpnext.hub.cache[hub_call_key].map(d => d.name) + data.map(d => d.name) ); + } + + const hub_call_key = 'get_categories{}'; + const categories_cache = erpnext.hub.cache[hub_call_key]; + + if(categories_cache) { + set_hub_category_options(categories_cache); + } + + erpnext.hub.on(`response:${hub_call_key}`, (data) => { + set_hub_category_options(data.response); }); return dialog; diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js index 2ed19f2739..d83aeeb6e4 100644 --- a/erpnext/public/js/hub/hub_call.js +++ b/erpnext/public/js/hub/hub_call.js @@ -24,16 +24,17 @@ hub.call = function call_hub_method(method, args={}, setup_cache_invalidation = }) .then(r => { if (r.message) { - if (r.message.error) { + const response = r.message; + if (response.error) { frappe.throw({ title: __('Marketplace Error'), - message: r.message.error + message: response.error }); } - erpnext.hub.cache[key] = r.message; - erpnext.hub.trigger(`response:${key}`); - resolve(r.message); + erpnext.hub.cache[key] = response; + erpnext.hub.trigger(`response:${key}`, { response }); + resolve(response); } reject(r); }) From d17aedb71989df48e8faef336e7c2fc51331491c Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 20 Aug 2018 18:33:46 +0530 Subject: [PATCH 077/154] Add BuyingMessages page --- erpnext/public/js/hub/marketplace.js | 10 +- .../public/js/hub/pages/buying_messages.js | 96 +++++++++++++++++++ erpnext/public/js/hub/pages/item.js | 1 - erpnext/public/js/hub/pages/messages.js | 14 +-- erpnext/public/js/hub/pages/register.js | 3 +- erpnext/public/js/hub/pages/subpage.js | 37 +++++++ erpnext/public/less/hub.less | 17 ++-- 7 files changed, 159 insertions(+), 19 deletions(-) create mode 100644 erpnext/public/js/hub/pages/buying_messages.js diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 994aebca62..953efe4c71 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -10,6 +10,7 @@ import './pages/profile'; import './pages/publish'; import './pages/published_products'; import './pages/messages'; +import './pages/buying_messages'; import './pages/not_found'; // components @@ -140,7 +141,7 @@ erpnext.hub.Marketplace = class Marketplace { make_body() { this.$body = this.$parent.find('.layout-main-section'); - this.$body.on('seller-registered', () => { + erpnext.hub.on('seller-registered', () => { this.registered = 1; this.make_sidebar_nav_buttons(); }); @@ -213,6 +214,10 @@ erpnext.hub.Marketplace = class Marketplace { this.subpages['messages'] = new erpnext.hub.Messages(this.$body); } + if (route[1] === 'buy' && !this.subpages['buy']) { + this.subpages['buy'] = new erpnext.hub.BuyingMessages(this.$body); + } + // dont allow unregistered users to access registered routes const registered_routes = ['favourites', 'profile', 'publish', 'my-products', 'messages']; if (!hub.settings.registered && registered_routes.includes(route[1])) { @@ -253,8 +258,7 @@ erpnext.hub.Marketplace = class Marketplace { this.register_dialog.hide(); frappe.set_route('marketplace', 'publish'); - // custom jquery event - this.$body.trigger('seller-registered'); + erpnext.hub.trigger('seller-registered'); }); } } diff --git a/erpnext/public/js/hub/pages/buying_messages.js b/erpnext/public/js/hub/pages/buying_messages.js new file mode 100644 index 0000000000..c307b8dfcc --- /dev/null +++ b/erpnext/public/js/hub/pages/buying_messages.js @@ -0,0 +1,96 @@ +import SubPage from './subpage'; + +erpnext.hub.BuyingMessages = class BuyingMessages extends SubPage { + make_wrapper() { + super.make_wrapper(); + this.add_back_link(__('Back to Messages'), 'marketplace/messages'); + this.$message_container = this.add_section({ title: 'Buy' }); + } + + refresh() { + const item_code = frappe.get_route()[2] || null; + if (!item_code) { + frappe.set_route('marketplace/messages'); + return; + } + + this.get_item_details(item_code) + .then(item_details => { + this.item_details = item_details; + + // make chat area + this.$message_container.find('.hub-section-header h4').text(this.item_details.item_name); + this.$message_container.find('.hub-section-body').html(` +
          +
          +
          +
          + `) + this.make_message_input(); + + // fetch messages + this.get_messages(item_details) + .then(messages => { + const $message_list = this.$message_container.find('.message-list'); + const html = messages.map(get_message_html).join(''); + $message_list.html(html); + frappe.dom.scroll_to_bottom($message_list); + }); + }); + + } + + get_messages(item_details) { + return hub.call('get_messages', { + against_seller: item_details.hub_seller, + against_item: item_details.hub_item_code + }); + } + + get_item_details(hub_item_code) { + return hub.call('get_item_details', { hub_item_code }) + } + + make_message_input() { + this.message_input = new frappe.ui.CommentArea({ + parent: this.$message_container.find('.message-input'), + on_submit: (message) => { + this.message_input.reset(); + + // append message html + const $message_list = this.$message_container.find('.message-list'); + const message_html = get_message_html({ + sender: hub.settings.company_email, + content: message + }); + $message_list.append(message_html); + frappe.dom.scroll_to_bottom($message_list); + + // send message + hub.call('send_message', { + from_seller: hub.settings.company_email, + to_seller: this.item_details.hub_seller, + hub_item: this.item_details.hub_item_code, + message + }); + }, + no_wrapper: true + }); + } +} + +function get_message_html(message) { + return ` +
          +
          + ${frappe.avatar(message.sender)} +
          + ${message.content} +
          +
          +
          + ${comment_when(message.creation, true)} +
          +
          + `; +} \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index 71a4b0f5c3..421d7ef29f 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -26,7 +26,6 @@ erpnext.hub.Item = class Item extends SubPage { get_item(hub_item_code) { return hub.call('get_item_details', { - hub_seller: hub.settings.company_email, hub_item_code }); } diff --git a/erpnext/public/js/hub/pages/messages.js b/erpnext/public/js/hub/pages/messages.js index a1f734f6ef..f09baf283e 100644 --- a/erpnext/public/js/hub/pages/messages.js +++ b/erpnext/public/js/hub/pages/messages.js @@ -16,13 +16,6 @@ erpnext.hub.Messages = class Messages extends SubPage { res.then(([buying_items, selling_items]) => { this.empty(); - if (buying_items.length) { - buying_items.map(item => { - item.route = `marketplace/buy/${item.hub_item_code}` - }) - this.render(buying_items, __('Buying')); - } - if (selling_items.length) { // selling_items.map(item => { // item.route = `marketplace/sell/${item.hub_item_code}/${}` @@ -30,6 +23,13 @@ erpnext.hub.Messages = class Messages extends SubPage { this.render(selling_items, __('Selling')); } + if (buying_items.length) { + buying_items.map(item => { + item.route = `marketplace/buy/${item.hub_item_code}` + }) + this.render(buying_items, __('Buying')); + } + if (!buying_items.length && !selling_items.length) { this.render_empty_state(); } diff --git a/erpnext/public/js/hub/pages/register.js b/erpnext/public/js/hub/pages/register.js index 9b07f29a6b..36b08425f9 100644 --- a/erpnext/public/js/hub/pages/register.js +++ b/erpnext/public/js/hub/pages/register.js @@ -107,8 +107,7 @@ erpnext.hub.Register = class Register extends SubPage { }).then(() => { frappe.set_route('marketplace', 'publish'); - // custom jquery event - this.$wrapper.trigger('seller-registered'); + erpnext.hub.trigger('seller-registered'); }); } } diff --git a/erpnext/public/js/hub/pages/subpage.js b/erpnext/public/js/hub/pages/subpage.js index fd058387a0..c8334462cc 100644 --- a/erpnext/public/js/hub/pages/subpage.js +++ b/erpnext/public/js/hub/pages/subpage.js @@ -32,6 +32,43 @@ export default class SubPage { this.hide(); } + add_section({ title, body } = {}) { + this._sections = this._sections || {}; + + if (title && this._sections[title]) { + return this._sections[title]; + } + + const $section = $(` +
          +
          +

          ${title || ''}

          +
          +
          + ${body || ''} +
          +
          + `); + + if (title) { + this._sections[title] = $section; + } + + this.$wrapper.append($section); + return $section; + } + + add_back_link(title, route) { + const $section = this.add_section(); + this.$wrapper.prepend($section); + + $section.addClass('margin-bottom'); + $section.find('.hub-section-header').remove() + $section.find('.hub-section-body').html(` + + `); + } + empty() { this.$wrapper.empty(); } diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 2bfb1094a0..88076c0593 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -290,13 +290,18 @@ body[data-route^="marketplace/"] { } } - .message-list { - overflow: scroll; + .message-container { + display: flex; + flex-direction: column; + border: 1px solid @border-color; + border-radius: 3px; + height: calc(100vh - 300px); + justify-content: space-between; } - .message-area { - border-radius: 4px; - justify-content: space-between; - height: calc(100vh - 220px); + .message-list { + padding-top: 15px; + padding-bottom: 15px; + overflow: scroll; } } From b0a3a106fc2f85a3b05732785a749dfb1b1ad0fa Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 21 Aug 2018 17:43:30 +0530 Subject: [PATCH 078/154] [hub] profile email uneditable, style additions, base64 sync enable --- .../item_to_hub_item/__init__.py | 18 +++++++++--------- erpnext/public/js/hub/pages/profile.js | 2 ++ erpnext/public/js/hub/pages/publish.js | 5 ++--- erpnext/public/less/hub.less | 4 ++++ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py index 0b6b2bc8a8..e981b2dc45 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py @@ -2,18 +2,18 @@ import frappe, io, base64, urllib, os def pre_process(doc): - # file_path = doc.image - # file_name = os.path.basename(file_path) + file_path = doc.image + file_name = os.path.basename(file_path) - # if file_path.startswith('http'): - # url = file_path - # file_path = os.path.join('/tmp', file_name) - # urllib.urlretrieve(url, file_path) + if file_path.startswith('http'): + url = file_path + file_path = os.path.join('/tmp', file_name) + urllib.urlretrieve(url, file_path) - # with io.open(file_path, 'rb') as f: - # doc.image = base64.b64encode(f.read()) + with io.open(file_path, 'rb') as f: + doc.image = base64.b64encode(f.read()) - # doc.image_file_name = file_name + doc.image_file_name = file_name cached_details = frappe.get_doc('Hub Tracked Item', doc.item_code) diff --git a/erpnext/public/js/hub/pages/profile.js b/erpnext/public/js/hub/pages/profile.js index a57bc7ceec..9bfa85d7a6 100644 --- a/erpnext/public/js/hub/pages/profile.js +++ b/erpnext/public/js/hub/pages/profile.js @@ -102,6 +102,8 @@ erpnext.hub.Profile = class Profile extends SubPage { on_submit: this.update_profile.bind(this) } ); + + this.edit_profile_dialog.set_df_property('company_email', 'read_only', 1); } edit_profile() { diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index 98b0a61845..9c5fc24c5b 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -13,9 +13,6 @@ erpnext.hub.Publish = class Publish extends SubPage { this.fetched_items = []; this.fetched_items_dict = {}; - this.cache = erpnext.hub.cache.items_to_publish; - this.cache = []; - frappe.realtime.on("items-sync", (data) => { this.$wrapper.find('.progress-bar').css('width', data.progress_percent+'%'); @@ -110,6 +107,7 @@ erpnext.hub.Publish = class Publish extends SubPage { } update_items_data_to_publish(values) { + // Add item additional data this.items_data_to_publish[values.item_code] = values; } @@ -249,6 +247,7 @@ erpnext.hub.Publish = class Publish extends SubPage { item_codes_to_publish.push($(this).attr("data-id")); }); + // Retrieve item data const items_data_to_publish = item_codes_to_publish.map(item_code => this.items_data_to_publish[item_code]) return frappe.call( diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 88076c0593..286e3be10f 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -50,6 +50,7 @@ body[data-route^="marketplace/"] { margin-top: 0px; margin-bottom: 15px; background-color: #f9fbf7; + border-radius: 4px; justify-content: space-between; } @@ -131,6 +132,8 @@ body[data-route^="marketplace/"] { .hub-card-overlay-button { position: absolute; + right: 15px; + bottom: 15px; } .hub-card-image { @@ -215,6 +218,7 @@ body[data-route^="marketplace/"] { height: 80px; border-radius: 4px; border: 1px solid @border-color; + border-style: dashed; margin: 15px 0px; } From d29a3b35671fdcad3e83e6c6c6299d17fadfc2f0 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 21 Aug 2018 19:12:10 +0530 Subject: [PATCH 079/154] Highlight links with part route or full route --- erpnext/public/js/hub/marketplace.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js index 953efe4c71..a7b7761f26 100644 --- a/erpnext/public/js/hub/marketplace.js +++ b/erpnext/public/js/hub/marketplace.js @@ -149,8 +149,10 @@ erpnext.hub.Marketplace = class Marketplace { update_sidebar() { const route = frappe.get_route(); - const route_str = route.slice(0, 2).join('/'); - const $sidebar_item = this.$sidebar.find(`[data-route="${route_str}"]`); + const route_str = route.join('/'); + const part_route_str = route.slice(0, 2).join('/'); + const $sidebar_item = this.$sidebar.find(`[data-route="${route_str}"], [data-route="${part_route_str}"]`); + const $siblings = this.$sidebar.find('[data-route]'); $siblings.removeClass('active').addClass('text-muted'); From ff189b6853c21cc6160256fae4047150a8d073f2 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 21 Aug 2018 19:14:44 +0530 Subject: [PATCH 080/154] fix: publish items - Pass image data as a json - ignore duplicates while creating Hub Tracked Item --- erpnext/hub_node/api.py | 2 +- .../item_to_hub_item/__init__.py | 29 ++++++++++++------- .../doctype/hub_settings/hub_settings.py | 1 + .../hub_tracked_item/test_hub_tracked_item.js | 23 --------------- erpnext/public/js/hub/pages/publish.js | 2 +- 5 files changed, 21 insertions(+), 36 deletions(-) delete mode 100644 erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index 0c9af1abc1..a8958ab59e 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -56,7 +56,7 @@ def publish_selected_items(items_to_publish): 'item_code': item_code, 'hub_category': item.get('hub_category'), 'image_list': item.get('image_list') - }).insert() + }).insert(ignore_if_duplicate=True) try: hub_settings = frappe.get_doc('Hub Settings') diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py index 0b6b2bc8a8..eb57f3a98a 100644 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py +++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py @@ -1,19 +1,26 @@ -import frappe, io, base64, urllib, os +import frappe, io, base64, urllib, os, json +from frappe.utils.file_manager import get_file_path def pre_process(doc): - # file_path = doc.image - # file_name = os.path.basename(file_path) + file_path = doc.image + file_name = os.path.basename(file_path) - # if file_path.startswith('http'): - # url = file_path - # file_path = os.path.join('/tmp', file_name) - # urllib.urlretrieve(url, file_path) + if file_path.startswith('http'): + url = file_path + file_path = os.path.join('/tmp', file_name) + urllib.urlretrieve(url, file_path) + else: + file_path = os.path.abspath(get_file_path(file_path)) - # with io.open(file_path, 'rb') as f: - # doc.image = base64.b64encode(f.read()) - - # doc.image_file_name = file_name + try: + with io.open(file_path, 'rb') as f: + doc.image = json.dumps({ + 'file_name': file_name, + 'base64': base64.b64encode(f.read()) + }) + except Exception as e: + frappe.log_error(title='Hub Sync Error') cached_details = frappe.get_doc('Hub Tracked Item', doc.item_code) diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 8ec3d5621f..0044cefcbd 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -81,6 +81,7 @@ class HubSettings(Document): def create_hub_connector(self, message): if frappe.db.exists('Data Migration Connector', 'Hub Connector'): hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector') + hub_connector.hostname = self.get_hub_url() hub_connector.username = message['email'] hub_connector.password = message['password'] hub_connector.save() diff --git a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js b/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js deleted file mode 100644 index 9f7314d399..0000000000 --- a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hub Tracked Item", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hub Tracked Item - () => frappe.tests.make('Hub Tracked Item', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index 98b0a61845..ce73d63fd4 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -187,7 +187,7 @@ erpnext.hub.Publish = class Publish extends SubPage { show_publish_progress() { const items_to_publish = this.items_data_to_publish.length ? this.items_data_to_publish - : JSON.parse(hub.settings.custom_data); + : JSON.parse(hub.settings.custom_data || '[]'); const $publish_progress = $(`

          ${__(`Syncing ${items_to_publish.length} Products`)}

          From e7af44f1a6857ccb4dbe44955d5ef512b00dce3d Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 21 Aug 2018 19:48:06 +0530 Subject: [PATCH 081/154] fix: remove items with invalid image --- erpnext/public/js/hub/components/item_card.js | 4 ++-- erpnext/public/js/hub/pages/publish.js | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/hub/components/item_card.js b/erpnext/public/js/hub/components/item_card.js index 9f5a6ef683..a1b0ae022d 100644 --- a/erpnext/public/js/hub/components/item_card.js +++ b/erpnext/public/js/hub/components/item_card.js @@ -21,7 +21,7 @@ function get_item_card_html(item) { } const item_html = ` -
          +
          @@ -74,7 +74,7 @@ function get_local_item_card_html(item) {
          `; const item_html = ` -
          +
          diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js index 5eb317891c..f49365833e 100644 --- a/erpnext/public/js/hub/pages/publish.js +++ b/erpnext/public/js/hub/pages/publish.js @@ -226,7 +226,14 @@ erpnext.hub.Publish = class Publish extends SubPage { items.map(item => { this.fetched_items_dict[item.item_code] = item; - }) + }); + + // remove the items which doesn't have a valid image + setTimeout(() => { + items_container.find('.no-image').each(function() { + $(this).closest('.hub-card-container').remove(); + }); + }, 1000); } get_valid_items() { From e745e85d98ae0bf2b161ff877539eecc9d24cbb4 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 21 Aug 2018 19:48:42 +0530 Subject: [PATCH 082/154] feat: route to message room page after contact seller dialog --- erpnext/public/js/hub/pages/item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js index 421d7ef29f..9f40971476 100644 --- a/erpnext/public/js/hub/pages/item.js +++ b/erpnext/public/js/hub/pages/item.js @@ -113,7 +113,7 @@ erpnext.hub.Item = class Item extends SubPage { }) .then(() => { d.hide(); - frappe.set_route('marketplace', 'messages'); + frappe.set_route('marketplace', 'buy', this.item.hub_item_code); erpnext.hub.trigger('action:send_message') }); } From caadd8af4e11c3671643e0feef9263098ce6d8d2 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 21 Aug 2018 19:59:15 +0530 Subject: [PATCH 083/154] =?UTF-8?q?[Hub]=20sync=20with=20insert=5Fmany,=20?= =?UTF-8?q?Remove=20Data=20Migration=20Run=20=F0=9F=92=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use FrappeClient insert_many to publish items - We will be syncing small amounts of items anyway - Also, we need only the insert action in bulk --- erpnext/hub_node/api.py | 60 ++++++++++++++++--- .../doctype/hub_settings/hub_settings.py | 14 ----- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index 0c9af1abc1..6e11a86b86 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -18,6 +18,29 @@ def call_hub_method(method, params=None): response = connection.post_request(params) return response +def map_fields(items): + field_mappings = get_field_mappings() + table_fields = [d.fieldname for d in frappe.get_meta('Item').get_table_fields()] + + hub_seller = frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email') + + for item in items: + for fieldname in table_fields: + item.pop(fieldname, None) + + for mapping in field_mappings: + local_fieldname = mapping.get('local_fieldname') + remote_fieldname = mapping.get('remote_fieldname') + + value = item.get(local_fieldname) + item.pop(local_fieldname, None) + item[remote_fieldname] = value + + item['doctype'] = 'Hub Item' + item['hub_seller'] = hub_seller + + return items + @frappe.whitelist() def get_valid_items(search_value=''): items = frappe.get_list( @@ -47,21 +70,37 @@ def publish_selected_items(items_to_publish): if not len(items_to_publish): frappe.throw('No items to publish') - for item in items_to_publish: - item_code = item.get('item_code') + publishing_items = [] + + for item_additional_info in items_to_publish: + item_code = item_additional_info.get('item_code') frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) frappe.get_doc({ 'doctype': 'Hub Tracked Item', 'item_code': item_code, - 'hub_category': item.get('hub_category'), - 'image_list': item.get('image_list') + 'hub_category': item_additional_info.get('hub_category'), + 'image_list': item_additional_info.get('image_list') }).insert() + item_data = frappe.get_doc("Item", item_code).as_dict().update(item_additional_info) + publishing_items.append(item_data) + + + items = map_fields(publishing_items) + try: - hub_settings = frappe.get_doc('Hub Settings') item_sync_preprocess() - hub_settings.sync() + + # TODO: Publish Progress + connection = get_hub_connection() + connection.insert_many(items) + + item_sync_postprocess({ + 'status': 'Success', + 'stats': len(items) + }) + except Exception as e: frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0) frappe.throw(e) @@ -90,16 +129,17 @@ def item_sync_postprocess(sync_details): 'hub_seller': hub_seller, 'activity_details': json.dumps({ 'subject': 'Publishing items:' + sync_details['status'], - 'content': json.dumps(sync_details['stats']) + 'content': str(sync_details['stats']) + ' items synced.' }) }) if response: - frappe.db.set_value('Hub Settings', 'Hub Settings', 'sync_in_progress', 0) frappe.db.set_value('Hub Settings', 'Hub Settings', 'last_sync_datetime', frappe.utils.now()) else: frappe.throw('Unable to update remote activity') + frappe.db.set_value('Hub Settings', 'Hub Settings', 'sync_in_progress', 0) + def get_hub_connection(): if frappe.db.exists('Data Migration Connector', 'Hub Connector'): hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector') @@ -109,3 +149,7 @@ def get_hub_connection(): # read-only connection hub_connection = FrappeClient(frappe.conf.hub_url) return hub_connection + + +def get_field_mappings(): + return [] diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 8ec3d5621f..f7e940c16e 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -23,20 +23,6 @@ class HubSettings(Document): def get_hub_url(self): return frappe.conf.hub_url - def sync(self): - """Create and execute Data Migration Run for Hub Sync plan""" - frappe.has_permission('Hub Settings', throw=True) - - doc = frappe.get_doc({ - 'doctype': 'Data Migration Run', - 'data_migration_plan': 'Hub Sync', - 'data_migration_connector': 'Hub Connector', - 'trigger_name': 'items-sync' - }).insert() - - self.sync_in_progress = 1 - doc.run() - def register(self): """ Create a User on hub.erpnext.org and return username/password """ From 3cd0c54438d632c1862bb75701ecfcff4b6b0734 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 21 Aug 2018 16:29:06 +0530 Subject: [PATCH 084/154] Fix pyhton 3 compatibility --- erpnext/hub_node/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index 6e11a86b86..85693c72af 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -3,12 +3,13 @@ import frappe, requests, json from frappe.utils import now from frappe.frappeclient import FrappeClient from frappe.desk.form.load import get_attachments +from six import string_types @frappe.whitelist() def call_hub_method(method, params=None): connection = get_hub_connection() - if type(params) == unicode: + if isinstance(params, string_types): params = json.loads(params) params.update({ From f9e4cca33478dcd93d0f8cd3ee50295c83c4cbc0 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 21 Aug 2018 16:30:33 +0530 Subject: [PATCH 085/154] Throw error if hub_url is not present in site_config --- erpnext/hub_node/doctype/hub_settings/hub_settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 96e3148c13..e445531d1e 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py @@ -21,6 +21,8 @@ class HubSettings(Document): frappe.throw(_("Please select a Price List to publish pricing")) def get_hub_url(self): + if not frappe.conf.hub_url: + frappe.throw('hub_url is not set in site_config') return frappe.conf.hub_url def register(self): From 7a040c4dd8d04646a49d5d4df2b4ee4ec0a4f6c4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 21 Aug 2018 16:32:31 +0530 Subject: [PATCH 086/154] Show favorite button only for registered user --- .../public/js/hub/components/detail_view.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/hub/components/detail_view.js b/erpnext/public/js/hub/components/detail_view.js index cd734058aa..f161e459e0 100644 --- a/erpnext/public/js/hub/components/detail_view.js +++ b/erpnext/public/js/hub/components/detail_view.js @@ -25,13 +25,16 @@ function get_detail_view_html(item, allow_edit) { stats = `${views_message}${dot_spacer}${rating_html} (${rating_count})`; } - let favourite_button = !item.favourited - ? `` - : ``; + let favourite_button = '' + if (hub.settings.registered) { + favourite_button = !item.favourited + ? `` + : ``; + } const contact_seller_button = item.hub_seller !== hub.settings.company_email ? ` +
          + +
          `).appendTo($(parent)); - - new Vue({ - render: h => h(Home) - }).$mount('#vue-area-home'); - } - - show() { - $('[data-page-name="home"]').show(); - } - - hide() { - $('[data-page-name="home"]').hide(); - } -} - -erpnext.hub.SavedProductsPage = class { - constructor(parent) { - this.$wrapper = $(`
          `).appendTo($(parent)); - - new Vue({ - render: h => h(SavedProducts) - }).$mount('#vue-area-saved'); - } - - show() { - $('[data-page-name="saved-products"]').show(); - } - - hide() { - $('[data-page-name="saved-products"]').hide(); - } -} - -erpnext.hub.PublishPage = class { - constructor(parent) { - this.$wrapper = $(`
          `).appendTo($(parent)); - - new Vue({ - render: h => h(Publish) - }).$mount('#vue-area'); - } - - show() { - $('[data-page-name="publish"]').show(); - } - - hide() { - $('[data-page-name="publish"]').hide(); - } - -} - -erpnext.hub.CategoryPage = class { - constructor(parent) { - this.$wrapper = $(`
          `).appendTo($(parent)); - - new Vue({ - render: h => h(Category) - }).$mount('#vue-area-category'); - } - - show() { - $('[data-page-name="category"]').show(); - } - - hide() { - $('[data-page-name="category"]').hide(); - } -} - -erpnext.hub.PublishedProductsPage = class { - constructor(parent) { - this.$wrapper = $(`
          `).appendTo($(parent)); - - new Vue({ - render: h => h(PublishedProducts) - }).$mount('#vue-area-published-products'); - } - - show() { - $('[data-page-name="published-products"]').show(); - } - - hide() { - $('[data-page-name="published-products"]').hide(); - } -} - -erpnext.hub.SearchPage = class { - constructor(parent) { - this.$wrapper = $(`