Merge branch 'hub-redesign' of https://github.com/frappe/erpnext into hub-redesign
diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py
index 559e22a..0c9af1a 100644
--- a/erpnext/hub_node/api.py
+++ b/erpnext/hub_node/api.py
@@ -2,6 +2,7 @@
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,22 +32,32 @@
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()
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'),
+ 'image_list': item.get('image_list')
+ }).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 9445e3a..0b6b2bc 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,25 @@
-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
+ 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 3ace088..bcece69 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
@@ -26,8 +26,18 @@
},
{
"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 1f772b6..e90b1dd 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 063c878..9384adb 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": "image_list",
+ "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": "Image List",
+ "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 22:24:06.207307",
"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/components/profile_dialog.js b/erpnext/public/js/hub/components/profile_dialog.js
new file mode 100644
index 0000000..800e8c6
--- /dev/null
+++ b/erpnext/public/js/hub/components/profile_dialog.js
@@ -0,0 +1,77 @@
+const ProfileDialog = (title = __('Edit Profile'), action={}, initial_values={}) => {
+ 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);
+ }
+ });
+
+ dialog.set_values(initial_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 ee21d48..994aebc 100644
--- a/erpnext/public/js/hub/marketplace.js
+++ b/erpnext/public/js/hub/marketplace.js
@@ -12,6 +12,9 @@
import './pages/messages';
import './pages/not_found';
+// components
+import { ProfileDialog } from './components/profile_dialog';
+
// helpers
import './hub_call';
import EventEmitter from './event_emitter';
@@ -49,6 +52,16 @@
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() {
@@ -79,7 +92,7 @@
${__('Messages')}
</li>`
- : `<li class="hub-sidebar-item text-muted" data-route="marketplace/register">
+ : `<li class="hub-sidebar-item text-muted" data-action="show_register_dialog">
${__('Become a seller')}
</li>`;
@@ -218,4 +231,30 @@
frappe.utils.scroll_to(0);
this.subpages[route[1]].show();
}
+
+ show_register_dialog() {
+ this.register_dialog = ProfileDialog(
+ __('Become a Seller'),
+ {
+ label: __('Register'),
+ on_submit: this.register_seller.bind(this)
+ }
+ );
+
+ this.register_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.register_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 a38cde4..a57bc7c 100644
--- a/erpnext/public/js/hub/pages/profile.js
+++ b/erpnext/public/js/hub/pages/profile.js
@@ -1,19 +1,30 @@
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() {
super.make_wrapper();
+ this.make_edit_profile_dialog();
}
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 });
}
+ 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 +57,7 @@
<img src="${p.logo}">
</div>
</div>
- <div class="col-md-6">
+ <div class="col-md-8">
<h2>${p.company}</h2>
<div class="text-muted">
<p>${p.country}</p>
@@ -60,6 +71,16 @@
}
</div>
</div>
+ <div class="col-md-1">
+ <div class="dropdown pull-right hub-item-dropdown">
+ <a class="dropdown-toggle btn btn-xs btn-default" data-toggle="dropdown">
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu dropdown-right" role="menu">
+ <li><a data-action="edit_profile">${__('Edit Profile')}</a></li>
+ </ul>
+ </div>
+ </div>
</div>
<div class="timeline">
@@ -73,6 +94,33 @@
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 `<div class="media timeline-item notification-content">
<div class="small">
@@ -95,4 +143,4 @@
}
}
}
-}
\ 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 a76f946..46e4545 100644
--- a/erpnext/public/js/hub/pages/publish.js
+++ b/erpnext/public/js/hub/pages/publish.js
@@ -3,12 +3,17 @@
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.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+'%');
@@ -58,25 +63,34 @@
}
get_publishing_header() {
- const title_html = `<b>${__('Select Products to Publish')}</b>`;
+ const title_html = `<h5>${__('Select Products to Publish')}</h5>`;
const subtitle_html = `<p class="text-muted">
${__(`Only products with an image, description and category can be published.
Please update them if an item in your inventory does not appear.`)}
</p>`;
- const publish_button_html = `<button class="btn btn-primary btn-sm publish-items">
+ const publish_button_html = `<button class="btn btn-primary btn-sm publish-items" disabled>
<i class="visible-xs octicon octicon-check"></i>
<span class="hidden-xs">${__('Publish')}</span>
</button>`;
return $(`
+ <div class="publish-area empty">
+ <div class="flex justify-between align-flex-end">
+ ${title_html}
+ ${publish_button_html}
+ </div>
+ <div class="empty-items-container flex align-center flex-column justify-center">
+ <p class="text-muted">${__('No Items Selected')}</p>
+ </div>
+ <div class="row hub-items-container selected-items"></div>
+ </div>
+
<div class='subpage-title flex'>
<div>
- ${title_html}
${subtitle_html}
</div>
- ${publish_button_html}
</div>
`);
}
@@ -87,27 +101,117 @@
.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.$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
+ },
+ {
+ "label": "Images",
+ "fieldname": "image_list",
+ "fieldtype": "MultiSelect",
+ "options": [],
+ "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.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;
+ }
+ });
+ }
+
+ 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();
+
+ 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();
+ }
+
+ 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;
+ 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', is_empty);
+
+ this.$wrapper.find('.publish-area').toggleClass('empty', is_empty);
+ this.$wrapper.find('.publish-area').toggleClass('filled', !is_empty);
+ }
+
+ add_item_to_publish() {
+ //
+ }
+
+ remove_item_from_publish() {
+ //
+ }
+
show_message(message) {
const $message = $(`<div class="subpage-message">
<p class="text-muted flex">
@@ -137,7 +241,7 @@
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 => {
@@ -191,6 +295,10 @@
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() {
@@ -211,19 +319,22 @@
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
}
)
}
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
index ac0aa42..2bfb109 100644
--- a/erpnext/public/less/hub.less
+++ b/erpnext/public/less/hub.less
@@ -6,7 +6,7 @@
padding-right: 25px;
}
- [data-route] {
+ [data-route], [data-action] {
cursor: pointer;
}
@@ -211,6 +211,25 @@
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;