diff --git a/erpnext/demo/data/drug_list.json b/erpnext/demo/data/drug_list.json index f34ca572d23..9b101cb1c81 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 65b1386466f..0ebbec7a0ed 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -2,10 +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.contacts.doctype.contact.contact import get_default_contact +import frappe @frappe.whitelist() def enable_hub(): @@ -15,263 +12,6 @@ def enable_hub(): return hub_settings @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=fields) - - # Bad, need child tables in response - listing = [] - for obj in response: - doc = connection.get_doc(doctype, obj['name']) - listing.append(doc) - - return listing - -@frappe.whitelist() -def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None): - doctype = 'Hub Item' +def sync(): 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 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): - 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 - -@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: - 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() - 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 + hub_settings.sync() diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py new file mode 100644 index 00000000000..be81effe69c --- /dev/null +++ b/erpnext/hub_node/api.py @@ -0,0 +1,169 @@ +from __future__ import unicode_literals +import frappe, json +import io, base64, os, requests +from frappe.frappeclient import FrappeClient +from frappe.desk.form.load import get_attachments +from frappe.utils.file_manager import get_file_path +from six import string_types + +@frappe.whitelist() +def call_hub_method(method, params=None): + connection = get_hub_connection() + + if isinstance(params, string_types): + params = json.loads(params) + + params.update({ + 'cmd': 'hub.hub.api.' + method + }) + + 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 + item.pop('attachments', None) + + return 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 prepare_item(item): + item.source_type = "local" + item.attachments = get_attachments('Item', item.item_code) + return item + + valid_items = map(prepare_item, 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): + frappe.throw('No 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(ignore_if_duplicate=True) + + + items = map_fields(items_to_publish) + + try: + item_sync_preprocess(len(items)) + load_base64_image_from_items(items) + + # TODO: Publish Progress + connection = get_hub_connection() + connection.insert_many(items) + + item_sync_postprocess() + except Exception as e: + frappe.log_error(message=e, title='Hub Sync Error') + +def item_sync_preprocess(intended_item_publish_count): + response = call_hub_method('pre_items_publish', { + 'intended_item_publish_count': intended_item_publish_count + }) + + 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(): + response = call_hub_method('post_items_publish', {}) + if response: + 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 load_base64_image_from_items(items): + for item in items: + file_path = item['image'] + file_name = os.path.basename(file_path) + base64content = None + + if file_path.startswith('http'): + # fetch content and then base64 it + url = file_path + response = requests.get(url) + base64content = base64.b64encode(response.content) + else: + # read file then base64 it + file_path = os.path.abspath(get_file_path(file_path)) + with io.open(file_path, 'rb') as f: + base64content = base64.b64encode(f.read()) + + image_data = json.dumps({ + 'file_name': file_name, + 'base64': base64content + }) + + item['image'] = image_data + + +def get_hub_connection(): + read_only = True + + if frappe.db.exists('Data Migration Connector', 'Hub Connector'): + hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector') + + # full rights to user who registered as hub_seller + if hub_connector.username == frappe.session.user: + read_only = False + + if not read_only: + hub_connection = hub_connector.get_connection() + return hub_connection.connection + + # read-only connection + if read_only: + hub_url = frappe.db.get_single_value('Hub Settings', 'hub_url') + hub_connection = FrappeClient(hub_url) + return hub_connection + +def get_field_mappings(): + return [] 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 7423f2e8a63..bcece69b38c 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\")", - "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": "image_list", + "remote_fieldname": "image_list" + }, + { + "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" + "is_child_table": 0, + "local_fieldname": "hub_category", + "remote_fieldname": "hub_category" } - ], - "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-08-19 22:20:25.727581", + "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 d66ac24b1c2..e90b1dd1e8d 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,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" - }, - { - "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", - "plan_name": "Hub Sync" + ], + "modified": "2018-08-19 22:20:25.644602", + "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/hub_node/doctype/hub_settings/hub_settings.js b/erpnext/hub_node/doctype/hub_settings/hub_settings.js index 29d870b371b..089f4999197 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.js +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.js @@ -1,177 +1,3 @@ frappe.ui.form.on("Hub Settings", { - refresh: function(frm) { - 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_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){ - frm.set_value("hub_user_name", frappe.user.full_name(frm.doc.hub_user_email)); - } - }, - - 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, - method: "register", - args: {}, - freeze: true, - callback: function(r) {}, - onerror: function() { - frappe.msgprint(__("Wrong Password")); - frm.set_value("enabled", 0); - } - }); - }, - - unregister_from_hub: (frm) => { - frappe.verify_password(() => { - var d = frappe.confirm(__('Are you sure you want to unregister?'), () => { - frm.call('unregister'); - }, () => {}, __('Confirm Action')); - d.get_primary_btn().addClass("btn-danger"); - }); - }, + onload_post_render: function() {}, }); - -// 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 7c7109c64c1..e230515aae0 100644 --- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json +++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json @@ -14,74 +14,13 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "enabled", - "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": "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", + "default": "https://hubmarket.org", + "fieldname": "hub_url", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, @@ -90,70 +29,7 @@ "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": "", + "label": "Hub URL", "length": 0, "no_copy": 0, "permlevel": 0, @@ -171,108 +47,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 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", - "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": "company_registered", + "fieldname": "registered", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -281,14 +61,14 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Company Registered", + "label": "Registered", "length": 0, "no_copy": 0, "permlevel": 0, "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, @@ -299,6 +79,39 @@ }, { "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, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -331,6 +144,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -362,6 +176,39 @@ }, { "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, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -394,11 +241,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 +305,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,234 +337,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "users_sb", - "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": "Enabled Users", - "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": "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "enabled", - "fieldname": "publish_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": "Publish", - "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": "publish", - "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": "Publish Items to Hub", - "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": "publish", - "fieldname": "publish_pricing", - "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": "Publish Pricing", - "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": "eval:(doc.publish && doc.publish_pricing)", - "fieldname": "selling_price_list", - "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": "Selling Price List", - "length": 0, - "no_copy": 0, - "options": "Price List", - "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": "publish", - "fieldname": "publish_availability", - "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": "Publish Availability", - "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": "publish", + "depends_on": "", "fieldname": "last_sync_datetime", "fieldtype": "Datetime", "hidden": 0, @@ -700,7 +359,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, @@ -711,6 +370,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -744,6 +404,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -777,6 +438,7 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -817,8 +479,8 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-03-26 00:55:17.929140", - "modified_by": "test1@example.com", + "modified": "2018-08-29 17:46:30.413159", + "modified_by": "Administrator", "module": "Hub Node", "name": "Hub Settings", "name_case": "", @@ -826,7 +488,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 0, @@ -852,5 +513,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/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py index 15ee4b7da35..ab0a7d24ff4 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 @@ -10,107 +10,65 @@ 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: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): - if self.publish_pricing and not self.selling_price_list: - frappe.throw(_("Please select a Price List to publish pricing")) + self.site_name = frappe.utils.get_url() def get_hub_url(self): - return 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' - }).insert() - - doc.run() - - 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 - } - + return self.hub_url def register(self): """ Create a User on hub.erpnext.org and return username/password """ + + if frappe.session.user == 'Administrator': + frappe.throw(_('Please login as another user to register on Marketplace')) + + if 'System Manager' not in frappe.get_roles(): + frappe.throw(_('Only users with System Manager role can register on Marketplace'), frappe.PermissionError) + + self.site_name = frappe.utils.get_url() + data = { - 'email': frappe.session.user + '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'}) - response = requests.post(post_url, data=data) response.raise_for_status() - message = response.json().get('message') - if message and message.get('password'): - self.user = frappe.session.user + if response.ok: + message = response.json().get('message') + else: + frappe.throw(json.loads(response.text)) + + if message.get('email'): self.create_hub_connector(message) - self.company = frappe.defaults.get_user_default('company') - self.enabled = 1 + self.registered = 1 self.save() - def unregister(self): - """ Disable the User on hub.erpnext.org""" + return message or None - hub_connector = frappe.get_doc( - 'Data Migration Connector', 'Hub Connector') + # def unregister(self): + # """ Disable the User on hub.erpnext.org""" - connection = hub_connector.get_connection() - response_doc = connection.update('User', frappe._dict({'enabled': 0}), hub_connector.username) + # hub_connector = frappe.get_doc( + # 'Data Migration Connector', 'Hub Connector') - if response_doc['enabled'] == 0: - self.enabled = 0 - self.save() + # 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() 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() @@ -120,7 +78,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() @@ -140,6 +98,9 @@ def reset_hub_settings(last_sync_datetime = ""): frappe.msgprint(_("Successfully unregistered.")) @frappe.whitelist() -def sync(): - hub_settings = frappe.get_doc('Hub Settings') - hub_settings.sync() +def register_seller(**kwargs): + settings = frappe.get_doc('Hub Settings') + settings.update(kwargs) + message = settings.register() + + return message.get('email') 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 063c87817c6..9384adbd1dd 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/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 9f7314d399f..00000000000 --- 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/hub_node/legacy.py b/erpnext/hub_node/legacy.py new file mode 100644 index 00000000000..7218d3fce29 --- /dev/null +++ b/erpnext/hub_node/legacy.py @@ -0,0 +1,143 @@ +from __future__ import unicode_literals +import frappe, json +from frappe.utils import 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 + +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/patches.txt b/erpnext/patches.txt index cbc02e2980b..8e02e42295d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -557,5 +557,8 @@ erpnext.patches.v11_0.set_department_for_doctypes erpnext.patches.v11_0.update_allow_transfer_for_manufacture 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 +erpnext.patches.v11_0.update_hub_url erpnext.patches.v10_0.set_discount_amount erpnext.patches.v10_0.recalculate_gross_margin_for_project 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 00000000000..fac772ccdd9 --- /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""") diff --git a/erpnext/patches/v11_0/update_hub_url.py b/erpnext/patches/v11_0/update_hub_url.py new file mode 100644 index 00000000000..ced2aaf7e07 --- /dev/null +++ b/erpnext/patches/v11_0/update_hub_url.py @@ -0,0 +1,5 @@ +import frappe + +def execute(): + frappe.reload_doc('hub_node', 'doctype', 'Hub Settings') + frappe.db.set_value('Hub Settings', 'Hub Settings', 'hub_url', 'https://hubmarket.org') diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 75809ce9258..e62ae59f927 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -1,52 +1,55 @@ { - "css/erpnext.css": [ - "public/less/erpnext.less", - "public/less/hub.less" - ], - "js/erpnext-web.min.js": [ - "public/js/website_utils.js", - "public/js/shopping_cart.js" - ], + "css/erpnext.css": [ + "public/less/erpnext.less", + "public/less/hub.less" + ], + "js/erpnext-web.min.js": [ + "public/js/website_utils.js", + "public/js/shopping_cart.js" + ], "css/erpnext-web.css": [ "public/less/website.less" ], - "js/erpnext.min.js": [ - "public/js/conf.js", - "public/js/utils.js", - "public/js/queries.js", - "public/js/sms_manager.js", - "public/js/utils/party.js", - "public/js/templates/address_list.html", - "public/js/templates/contact_list.html", - "public/js/controllers/stock_controller.js", - "public/js/payment/payments.js", - "public/js/controllers/taxes_and_totals.js", - "public/js/controllers/transaction.js", - "public/js/pos/pos.html", - "public/js/pos/pos_bill_item.html", - "public/js/pos/pos_bill_item_new.html", - "public/js/pos/pos_selected_item.html", - "public/js/pos/pos_item.html", - "public/js/pos/pos_tax_row.html", - "public/js/pos/customer_toolbar.html", - "public/js/pos/pos_invoice_list.html", - "public/js/payment/pos_payment.html", - "public/js/payment/payment_details.html", - "public/js/templates/item_selector.html", + "js/marketplace.min.js": [ + "public/js/hub/marketplace.js" + ], + "js/erpnext.min.js": [ + "public/js/conf.js", + "public/js/utils.js", + "public/js/queries.js", + "public/js/sms_manager.js", + "public/js/utils/party.js", + "public/js/templates/address_list.html", + "public/js/templates/contact_list.html", + "public/js/controllers/stock_controller.js", + "public/js/payment/payments.js", + "public/js/controllers/taxes_and_totals.js", + "public/js/controllers/transaction.js", + "public/js/pos/pos.html", + "public/js/pos/pos_bill_item.html", + "public/js/pos/pos_bill_item_new.html", + "public/js/pos/pos_selected_item.html", + "public/js/pos/pos_item.html", + "public/js/pos/pos_tax_row.html", + "public/js/pos/customer_toolbar.html", + "public/js/pos/pos_invoice_list.html", + "public/js/payment/pos_payment.html", + "public/js/payment/payment_details.html", + "public/js/templates/item_selector.html", "public/js/templates/employees_to_mark_attendance.html", - "public/js/utils/item_selector.js", - "public/js/help_links.js", - "public/js/agriculture/ternary_plot.js", - "public/js/templates/item_quick_entry.html", - "public/js/utils/item_quick_entry.js", + "public/js/utils/item_selector.js", + "public/js/help_links.js", + "public/js/agriculture/ternary_plot.js", + "public/js/templates/item_quick_entry.html", + "public/js/utils/item_quick_entry.js", "public/js/utils/customer_quick_entry.js", - "public/js/education/student_button.html", - "public/js/education/assessment_result_tool.html", - "public/js/hub/hub_factory.js" - ], - "js/item-dashboard.min.js": [ - "stock/dashboard/item_dashboard.html", - "stock/dashboard/item_dashboard_list.html", - "stock/dashboard/item_dashboard.js" - ] + "public/js/education/student_button.html", + "public/js/education/assessment_result_tool.html", + "public/js/hub/hub_factory.js" + ], + "js/item-dashboard.min.js": [ + "stock/dashboard/item_dashboard.html", + "stock/dashboard/item_dashboard_list.html", + "stock/dashboard/item_dashboard.js" + ] } diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue new file mode 100644 index 00000000000..cec4c8d7468 --- /dev/null +++ b/erpnext/public/js/hub/PageContainer.vue @@ -0,0 +1,101 @@ + + + diff --git a/erpnext/public/js/hub/Sidebar.vue b/erpnext/public/js/hub/Sidebar.vue new file mode 100644 index 00000000000..cff580e5ff0 --- /dev/null +++ b/erpnext/public/js/hub/Sidebar.vue @@ -0,0 +1,105 @@ + + diff --git a/erpnext/public/js/hub/components/CommentInput.vue b/erpnext/public/js/hub/components/CommentInput.vue new file mode 100644 index 00000000000..cc430d0c289 --- /dev/null +++ b/erpnext/public/js/hub/components/CommentInput.vue @@ -0,0 +1,38 @@ + + diff --git a/erpnext/public/js/hub/components/DetailHeaderItem.vue b/erpnext/public/js/hub/components/DetailHeaderItem.vue new file mode 100644 index 00000000000..8ca4379701c --- /dev/null +++ b/erpnext/public/js/hub/components/DetailHeaderItem.vue @@ -0,0 +1,20 @@ + + + diff --git a/erpnext/public/js/hub/components/DetailView.vue b/erpnext/public/js/hub/components/DetailView.vue new file mode 100644 index 00000000000..70ec94c6c16 --- /dev/null +++ b/erpnext/public/js/hub/components/DetailView.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/erpnext/public/js/hub/components/EmptyState.vue b/erpnext/public/js/hub/components/EmptyState.vue new file mode 100644 index 00000000000..d6216c9ac7a --- /dev/null +++ b/erpnext/public/js/hub/components/EmptyState.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/erpnext/public/js/hub/components/ItemCard.vue b/erpnext/public/js/hub/components/ItemCard.vue new file mode 100644 index 00000000000..f34fddcfd7e --- /dev/null +++ b/erpnext/public/js/hub/components/ItemCard.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/erpnext/public/js/hub/components/ItemCardsContainer.vue b/erpnext/public/js/hub/components/ItemCardsContainer.vue new file mode 100644 index 00000000000..0a20bcdc636 --- /dev/null +++ b/erpnext/public/js/hub/components/ItemCardsContainer.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/erpnext/public/js/hub/components/ItemListCard.vue b/erpnext/public/js/hub/components/ItemListCard.vue new file mode 100644 index 00000000000..70cb5666cc1 --- /dev/null +++ b/erpnext/public/js/hub/components/ItemListCard.vue @@ -0,0 +1,21 @@ + + diff --git a/erpnext/public/js/hub/components/NotificationMessage.vue b/erpnext/public/js/hub/components/NotificationMessage.vue new file mode 100644 index 00000000000..c26672635c3 --- /dev/null +++ b/erpnext/public/js/hub/components/NotificationMessage.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/erpnext/public/js/hub/components/Rating.vue b/erpnext/public/js/hub/components/Rating.vue new file mode 100644 index 00000000000..33290b8e202 --- /dev/null +++ b/erpnext/public/js/hub/components/Rating.vue @@ -0,0 +1,16 @@ + + + diff --git a/erpnext/public/js/hub/components/ReviewArea.vue b/erpnext/public/js/hub/components/ReviewArea.vue new file mode 100644 index 00000000000..070d0a69cb1 --- /dev/null +++ b/erpnext/public/js/hub/components/ReviewArea.vue @@ -0,0 +1,75 @@ + + diff --git a/erpnext/public/js/hub/components/ReviewTimelineItem.vue b/erpnext/public/js/hub/components/ReviewTimelineItem.vue new file mode 100644 index 00000000000..f0fe001973c --- /dev/null +++ b/erpnext/public/js/hub/components/ReviewTimelineItem.vue @@ -0,0 +1,54 @@ + + + + diff --git a/erpnext/public/js/hub/components/SearchInput.vue b/erpnext/public/js/hub/components/SearchInput.vue new file mode 100644 index 00000000000..4b1ce6e4ef9 --- /dev/null +++ b/erpnext/public/js/hub/components/SearchInput.vue @@ -0,0 +1,26 @@ + + + diff --git a/erpnext/public/js/hub/components/SectionHeader.vue b/erpnext/public/js/hub/components/SectionHeader.vue new file mode 100644 index 00000000000..05a2f838a0a --- /dev/null +++ b/erpnext/public/js/hub/components/SectionHeader.vue @@ -0,0 +1,3 @@ + diff --git a/erpnext/public/js/hub/components/TimelineItem.vue b/erpnext/public/js/hub/components/TimelineItem.vue new file mode 100644 index 00000000000..d13c842beb8 --- /dev/null +++ b/erpnext/public/js/hub/components/TimelineItem.vue @@ -0,0 +1,9 @@ +/* Saving this for later */ + 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 00000000000..d80720c1da5 --- /dev/null +++ b/erpnext/public/js/hub/components/detail_view.js @@ -0,0 +1,138 @@ +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; + + 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 favourite_button = '' + if (hub.settings.registered) { + favourite_button = !item.favourited + ? `` + : ``; + } + + const contact_seller_button = item.hub_seller !== hub.settings.company_email + ? `` + : ''; + + 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}

    +
    +
    + +
    + ${favourite_button} + ${contact_seller_button} +
    +
    +
    + +
    +
    +
    +
    + Item Description +
    +

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

    +
    +
    + +
    + Seller Information +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    + `; + + return html; +} + +export { + get_detail_view_html, + get_profile_html +} diff --git a/erpnext/public/js/hub/components/item_card.js b/erpnext/public/js/hub/components/item_card.js new file mode 100644 index 00000000000..41ab33aaf95 --- /dev/null +++ b/erpnext/public/js/hub/components/item_card.js @@ -0,0 +1,80 @@ +function get_buying_item_message_card_html(item) { + const item_name = item.item_name || item.name; + const title = strip_html(item_name); + + const message = item.recent_message + const sender = message.sender === frappe.session.user ? 'You' : message.sender + const content = strip_html(message.content) + + // route + item.route = `marketplace/buying/${item.name}` + + const item_html = ` +
    +
    +
    + +
    +
    ${item_name}
    +
    + ${sender}: + ${content} +
    +
    +
    +
    + ${comment_when(message.creation, true)} +
    +
    +
    + `; + + return item_html; +} + +function get_selling_item_message_card_html(item) { + const item_name = item.item_name || item.name; + const title = strip_html(item_name); + + // route + if (!item.route) { + item.route = `marketplace/item/${item.name}` + } + + let received_messages = ''; + item.received_messages.forEach(message => { + const sender = message.sender === frappe.session.user ? 'You' : message.sender + const content = strip_html(message.content) + + received_messages += ` +
    + ${comment_when(message.creation, true)} +
    + ${sender}: + ${content} +
    +
    + ` + }); + + const item_html = ` +
    +
    + +
    ${item_name}
    +
    + ${received_messages} +
    +
    +
    + `; + + return item_html; +} + +export { + get_item_card_html, + get_local_item_card_html, + get_buying_item_message_card_html, + get_selling_item_message_card_html +} 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 00000000000..e49ba539185 --- /dev/null +++ b/erpnext/public/js/hub/components/item_publish_dialog.js @@ -0,0 +1,42 @@ +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": [], + "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 + }); + + hub.call('get_categories') + .then(categories => { + categories = categories.map(d => d.name); + dialog.fields_dict.hub_category.set_data(categories); + }); + + return dialog; +} + +export { + ItemPublishDialog +} 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 00000000000..e76abfaa0fd --- /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/components/reviews.js b/erpnext/public/js/hub/components/reviews.js new file mode 100644 index 00000000000..e34d68038f7 --- /dev/null +++ b/erpnext/public/js/hub/components/reviews.js @@ -0,0 +1,80 @@ +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} +

    +
    +
    +
    +
    +
    `; +} + +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/event_emitter.js b/erpnext/public/js/hub/event_emitter.js new file mode 100644 index 00000000000..1e7288191d0 --- /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 new file mode 100644 index 00000000000..a8bfa2e8549 --- /dev/null +++ b/erpnext/public/js/hub/hub_call.js @@ -0,0 +1,58 @@ +frappe.provide('hub'); +frappe.provide('erpnext.hub'); + +erpnext.hub.cache = {}; +hub.call = function call_hub_method(method, args={}, clear_cache_on_event) { // eslint-disable-line + return new Promise((resolve, reject) => { + + // cache + const key = method + JSON.stringify(args); + if (erpnext.hub.cache[key]) { + resolve(erpnext.hub.cache[key]); + } + + // cache invalidation + const clear_cache = () => delete erpnext.hub.cache[key]; + + 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', + args: { + method, + params: args + } + }).then(r => { + if (r.message) { + const response = r.message; + if (response.error) { + frappe.throw({ + title: __('Marketplace Error'), + message: response.error + }); + } + + erpnext.hub.cache[key] = response; + erpnext.hub.trigger(`response:${key}`, { response }); + resolve(response); + } + reject(r); + + }).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/hub_factory.js b/erpnext/public/js/hub/hub_factory.js index d656605c07b..57f27f982e2 100644 --- a/erpnext/public/js/hub/hub_factory.js +++ b/erpnext/public/js/hub/hub_factory.js @@ -1,80 +1,32 @@ -frappe.provide('erpnext.hub.pages'); +frappe.provide('erpnext.hub'); -frappe.views.HubFactory = class HubFactory extends frappe.views.Factory { - make(route) { - const page_name = frappe.get_route_str(); - const page = route[1]; +frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views.Factory { + show() { + if (frappe.pages.marketplace) { + frappe.container.change_to('marketplace'); + erpnext.hub.marketplace.refresh(); + } else { + this.make('marketplace'); + } + } - 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'); + make(page_name) { + const assets = [ + '/assets/js/marketplace.min.js' + ]; - 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]; - } + frappe.require(assets, () => { + erpnext.hub.marketplace = new erpnext.hub.Marketplace({ + parent: this.make_page(true, page_name) + }); }); } - - render_offline_card() { - let html = `
    -
    - ${'Failed to connect'} -
    -

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

    -
    - ${ __("Reload") }
    -
    `; - - let page = $('#body_div'); - page.append(html); - - return; - } }; + +$(document).on('toolbar_setup', () => { + $('#toolbar-user .navbar-reload').after(` +
  • + ${__('Marketplace')} +
  • + `); +}); diff --git a/erpnext/public/js/hub/hub_form.js b/erpnext/public/js/hub/hub_form.js deleted file mode 100644 index 9287e6d54f4..00000000000 --- 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 0ff79708d77..00000000000 --- a/erpnext/public/js/hub/hub_listing.js +++ /dev/null @@ -1,718 +0,0 @@ -frappe.provide('erpnext.hub'); - -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(iconHtml + 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(); - }); - } - - 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); - } - } - } - - 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.") }

    -
    - ${ __("Continue") }
    -
    `; - - 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(` -
    - ${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 `
    -
    -
    - Riadco Group - Products by Blah blah -
    -
    -
    -
    - - -
    -
    -
    - ${''} -
    -
    `; - } - - 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(); - - 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")); - } - - 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; - }); - } - - item_html(item) { - 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 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 item_html = ` -
    -
    -
    - - ${title} - -
    -
    - ${ratingHtml} - (${reviewLength}) -
    - -
    - - -
    - `; - - return item_html; - } - -}; - -erpnext.hub.Favourites = 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; - } - -}; \ No newline at end of file diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js new file mode 100644 index 00000000000..cdf3d2332d0 --- /dev/null +++ b/erpnext/public/js/hub/marketplace.js @@ -0,0 +1,115 @@ +import Vue from 'vue/dist/vue.js'; +import './vue-plugins'; + +// components +import PageContainer from './PageContainer.vue'; +import Sidebar from './Sidebar.vue'; +import { ProfileDialog } from './components/profile_dialog'; + +// 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); + this.page = parent.page; + + frappe.db.get_doc('Hub Settings') + .then(doc => { + hub.settings = doc; + const is_registered = hub.settings.registered; + const is_registered_seller = hub.settings.company_email === frappe.session.user; + this.setup_header(); + this.make_sidebar(); + this.make_body(); + this.setup_events(); + this.refresh(); + if (!is_registered && !is_registered_seller && frappe.user_roles.includes('System Manager')) { + this.page.set_primary_action('Become a Seller', this.show_register_dialog.bind(this)) + } + }); + } + + 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); + }); + + // 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() { + this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); + + new Vue({ + el: $('
    ').appendTo(this.$sidebar)[0], + render: h => h(Sidebar) + }); + } + + make_body() { + this.$body = this.$parent.find('.layout-main-section'); + this.$page_container = $('
    ').appendTo(this.$body); + + new Vue({ + el: '.hub-page-container', + render: h => h(PageContainer) + }); + + erpnext.hub.on('seller-registered', () => { + this.page.clear_primary_action() + frappe.db.get_doc('Hub Settings').then((doc)=> { + hub.settings = doc; + }); + }); + } + + refresh() { + + } + + 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 + }).then(() => { + this.register_dialog.hide(); + frappe.set_route('marketplace', 'publish'); + + erpnext.hub.trigger('seller-registered'); + }); + } + +} diff --git a/erpnext/public/js/hub/pages/Buying.vue b/erpnext/public/js/hub/pages/Buying.vue new file mode 100644 index 00000000000..ddb4b11db90 --- /dev/null +++ b/erpnext/public/js/hub/pages/Buying.vue @@ -0,0 +1,53 @@ + + diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue new file mode 100644 index 00000000000..3a0e6bfab83 --- /dev/null +++ b/erpnext/public/js/hub/pages/Category.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue new file mode 100644 index 00000000000..4f9796d2976 --- /dev/null +++ b/erpnext/public/js/hub/pages/Home.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue new file mode 100644 index 00000000000..3305b1da4ca --- /dev/null +++ b/erpnext/public/js/hub/pages/Item.vue @@ -0,0 +1,280 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/Messages.vue b/erpnext/public/js/hub/pages/Messages.vue new file mode 100644 index 00000000000..1930bcb22ad --- /dev/null +++ b/erpnext/public/js/hub/pages/Messages.vue @@ -0,0 +1,105 @@ + + diff --git a/erpnext/public/js/hub/pages/NotFound.vue b/erpnext/public/js/hub/pages/NotFound.vue new file mode 100644 index 00000000000..246d31bc681 --- /dev/null +++ b/erpnext/public/js/hub/pages/NotFound.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/Profile.vue b/erpnext/public/js/hub/pages/Profile.vue new file mode 100644 index 00000000000..a0bc6cba78d --- /dev/null +++ b/erpnext/public/js/hub/pages/Profile.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue new file mode 100644 index 00000000000..b05f12adb6e --- /dev/null +++ b/erpnext/public/js/hub/pages/Publish.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/PublishedItems.vue b/erpnext/public/js/hub/pages/PublishedItems.vue new file mode 100644 index 00000000000..114259b93c0 --- /dev/null +++ b/erpnext/public/js/hub/pages/PublishedItems.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/SavedItems.vue b/erpnext/public/js/hub/pages/SavedItems.vue new file mode 100644 index 00000000000..e5d21cdd3c3 --- /dev/null +++ b/erpnext/public/js/hub/pages/SavedItems.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue new file mode 100644 index 00000000000..5118a814e07 --- /dev/null +++ b/erpnext/public/js/hub/pages/Search.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue new file mode 100644 index 00000000000..c80865bfbda --- /dev/null +++ b/erpnext/public/js/hub/pages/Seller.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/erpnext/public/js/hub/pages/Selling.vue b/erpnext/public/js/hub/pages/Selling.vue new file mode 100644 index 00000000000..9c8ede66642 --- /dev/null +++ b/erpnext/public/js/hub/pages/Selling.vue @@ -0,0 +1,66 @@ + + diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js new file mode 100644 index 00000000000..e18af0c428e --- /dev/null +++ b/erpnext/public/js/hub/vue-plugins.js @@ -0,0 +1,66 @@ +import Vue from 'vue/dist/vue.js'; + +// Global components +import ItemCardsContainer from './components/ItemCardsContainer.vue'; +import SectionHeader from './components/SectionHeader.vue'; +import SearchInput from './components/SearchInput.vue'; +import DetailView from './components/DetailView.vue'; +import DetailHeaderItem from './components/DetailHeaderItem.vue'; +import EmptyState from './components/EmptyState.vue'; + +Vue.prototype.__ = window.__; +Vue.prototype.frappe = window.frappe; + +Vue.component('item-cards-container', ItemCardsContainer); +Vue.component('section-header', SectionHeader); +Vue.component('search-input', SearchInput); +Vue.component('detail-view', DetailView); +Vue.component('detail-header-item', DetailHeaderItem); +Vue.component('empty-state', EmptyState); + +Vue.directive('route', { + bind(el, binding) { + const route = binding.value; + if (!route) return; + el.classList.add('cursor-pointer'); + el.dataset.route = route; + el.addEventListener('click', () => frappe.set_route(route)); + }, + unbind(el) { + el.classList.remove('cursor-pointer'); + } +}); + +const handleImage = (el, src) => { + let img = new Image(); + // add loading class + el.src = ''; + el.classList.add('img-loading'); + + img.onload = () => { + // image loaded, remove loading class + el.classList.remove('img-loading'); + // set src + el.src = src; + } + img.onerror = () => { + el.classList.remove('img-loading'); + el.classList.add('no-image'); + el.src = null; + } + img.src = src; +} + +Vue.directive('img-src', { + bind(el, binding) { + handleImage(el, binding.value); + }, + update(el, binding) { + if (binding.value === binding.oldValue) return; + handleImage(el, binding.value); + } +}); + +Vue.filter('striphtml', function (text) { + return strip_html(text); +}); \ No newline at end of file diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index bdca28f1e74..d40926b7adb 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -1,171 +1,352 @@ @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-left: 5px; + padding-right: 25px; } - .hub-page-title { - margin-left: 10px; + [data-route], [data-action] { + cursor: pointer; } - .img-wrapper { - border: 1px solid #d1d8dd; - border-radius: 3px; - padding: 12px; - overflow: hidden; - text-align: center; - white-space: nowrap; + .layout-main-section { + border: none; + font-size: @text-medium; + padding-top: 25px; - .helper { - height: 100%; - display: inline-block; - vertical-align: middle; + @media (max-width: @screen-xs) { + padding-left: 20px; + padding-right: 20px; } } - .tree { - margin: 10px 0px; - padding: 0px; - height: 100%; + input, textarea { + font-size: @text-medium; + } + + .progress-bar { + background-color: #89da28; + } + + .subpage-title.flex { + align-items: flex-start; + justify-content: space-between; + } + + .hub-card { + margin-bottom: 25px; 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; + border-radius: 4px; + overflow: hidden; + + &:hover .hub-card-overlay { + display: block; + } + } + + .hub-card.is-local { + &.active { + .hub-card-header { + background-color: #f4ffe5; + } + + .octicon-check { + display: inline; + } + } + + .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; + } + + .hub-card-overlay { + display: none; + position: absolute; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.05); + } + + .hub-card-overlay-body { + position: relative; + height: 100%; + } + + .hub-card-overlay-button { + position: absolute; + right: 15px; + bottom: 15px; + } + + .hub-card-image { + position: relative; + width: 100%; + height: 100%; + object-fit: contain; + } + + .hub-search-container { + margin-bottom: 20px; + + input { + 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; + + &.active, &:hover:not(.is-title) { + border-color: @border-color; + } + } + + .hub-item-image { + border: 1px solid @border-color; + border-radius: 4px; + overflow: hidden; + height: 200px; + width: 200px; display: flex; align-items: center; - justify-content: center; + } - img { - border-radius: 4px; + .hub-item-skeleton-image { + border-radius: 4px; + background-color: @light-bg; + overflow: hidden; + height: 200px; + width: 200px; + } + + .hub-skeleton { + background-color: @light-bg; + color: @light-bg; + max-width: 500px; + } + + .hub-item-seller img { + width: 50px; + height: 50px; + 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; + } + + .publish-area.filled { + .empty-items-container { + display: none; } } - .star-icon.fa-star { - color: @indicator-orange; + .publish-area.empty { + .hub-items-container { + display: none; + } } - .octicon-heart.liked { - color: @indicator-red; + .publish-area-head { + display: flex; + justify-content: space-between; + margin-bottom: 20px; } - .margin-vertical-10 { - margin: 10px 0px; + .hub-list-item { + display: flex; + justify-content: space-between; + align-items: center; + border: 1px solid @border-color; + margin-bottom: -1px; + overflow: hidden; } - .margin-vertical-15 { - margin: 15px 0px; + .hub-list-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + .hub-list-item:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; } - .frappe-list .result { - min-height: 100px; + .hub-list-left { + display: flex; + align-items: center; + max-width: 90%; } - .frappe-control[data-fieldtype="Attach Image"] { - width: 140px; - height: 180px; + .hub-list-right { + padding-right: 15px; + } + + .hub-list-image { + position: relative; + width: 58px; + height: 58px; + border-right: 1px solid @border-color; + + &::after { + font-size: 12px; + } + } + + .hub-list-body { + padding: 12px 15px; + } + + .hub-list-title { + font-weight: bold; + } + + .hub-list-subtitle { + color: @text-muted; + } + + .selling-item-message-card { + max-width: 500px; + margin-bottom: 15px; + border-radius: 3px; + border: 1px solid @border-color; + .selling-item-detail { + overflow: auto; + .item-image { + float: left; + height: 80px; + width: 80px; + object-fit: contain; + margin: 5px; + } + .item-name { + margin-left: 10px; + } + } + .received-message-container { + clear: left; + background-color: @light-bg; + .received-message { + border-top: 1px solid @border-color; + padding: 10px; + } + .frappe-timestamp { + float: right; + } + } + } + + .form-container { + .frappe-control { + max-width: 100% !important; + } + } + + .form-message { + padding-top: 0; + padding-bottom: 0; + border-bottom: none; + } + + .hub-items-container { + .hub-items-header { + justify-content: space-between; + align-items: baseline; + } + } + + .hub-item-container { + overflow: hidden; + } + + .hub-item-review-container { + margin-top: calc(30vh); + } + + .hub-item-dropdown { margin-top: 20px; } - .frappe-control[data-fieldtype="Attach Image"] .form-group { - display: none; - } + /* messages page */ - .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; + .message-list-item { 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; - } -} + padding: 8px 12px; -.image-view-container { - .image-view-body { - &:hover .like-button { - opacity: 0.7; + &: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; } } - .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 - } + .message-container { + display: flex; + flex-direction: column; + border: 1px solid @border-color; + border-radius: 3px; + height: calc(100vh - 300px); + justify-content: space-between; + padding: 15px; } - .image-view-body:hover .like-button { - opacity: 0.7; + .message-list { + overflow: scroll; } } - -.rating-area .star-icon { - cursor: pointer; - font-size: 15px; -} diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 779f1c6d63d..9161f2d149b 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -4121,4 +4121,4 @@ "track_changes": 1, "track_seen": 0, "track_views": 0 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 2cf33b9efc6..c79d76d6e27 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -76,8 +76,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'''