fix merge conflict
diff --git a/erpnext/demo/data/drug_list.json b/erpnext/demo/data/drug_list.json
index f34ca57..9b101cb 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 65b1386..634746c 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():
@@ -13,265 +10,3 @@
hub_settings.register()
frappe.db.commit()
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'
- 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
diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py
new file mode 100644
index 0000000..559e22a
--- /dev/null
+++ b/erpnext/hub_node/api.py
@@ -0,0 +1,100 @@
+from __future__ import unicode_literals
+import frappe, requests, json
+from frappe.utils import now
+from frappe.frappeclient import FrappeClient
+
+@frappe.whitelist()
+def call_hub_method(method, params=None):
+ connection = get_hub_connection()
+
+ if type(params) == unicode:
+ params = json.loads(params)
+
+ params.update({
+ 'cmd': 'hub.hub.api.' + method
+ })
+
+ response = connection.post_request(params)
+ return response
+
+@frappe.whitelist()
+def get_valid_items(search_value=''):
+ items = frappe.get_list(
+ 'Item',
+ fields=["*"],
+ filters={
+ 'item_name': ['like', '%' + search_value + '%'],
+ 'publish_in_hub': 0
+ },
+ order_by="modified desc"
+ )
+
+ valid_items = filter(lambda x: x.image and x.description, items)
+
+ def attach_source_type(item):
+ item.source_type = "local"
+ return item
+
+ valid_items = map(lambda x: attach_source_type(x), valid_items)
+ return valid_items
+
+@frappe.whitelist()
+def publish_selected_items(items_to_publish):
+ items_to_publish = json.loads(items_to_publish)
+ if not len(items_to_publish):
+ return
+
+ for item_code in items_to_publish:
+ frappe.db.set_value('Item', item_code, 'publish_in_hub', 1)
+
+ try:
+ hub_settings = frappe.get_doc('Hub Settings')
+ item_sync_preprocess()
+ hub_settings.sync()
+ except Exception as e:
+ frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 0)
+ frappe.throw(e)
+
+def item_sync_preprocess():
+ hub_seller = frappe.db.get_value("Hub Settings", "Hub Settings", "company_email")
+
+ response = call_hub_method('add_hub_seller_activity', {
+ 'hub_seller': hub_seller,
+ 'activity_details': json.dumps({
+ 'subject': 'Publishing items',
+ 'status': 'Success'
+ })
+ })
+
+ if response:
+ frappe.db.set_value("Hub Settings", "Hub Settings", "sync_in_progress", 1)
+ return response
+ else:
+ frappe.throw('Unable to update remote activity')
+
+def item_sync_postprocess(sync_details):
+ hub_seller = frappe.db.get_value("Hub Settings", "Hub Settings", "company_email")
+
+ response = call_hub_method('add_hub_seller_activity', {
+ 'hub_seller': hub_seller,
+ 'activity_details': json.dumps({
+ 'subject': 'Publishing items:' + sync_details['status'],
+ 'content': json.dumps(sync_details['stats'])
+ })
+ })
+
+ if response:
+ frappe.db.set_value('Hub Settings', 'Hub Settings', 'sync_in_progress', 0)
+ frappe.db.set_value('Hub Settings', 'Hub Settings', 'last_sync_datetime', frappe.utils.now())
+ else:
+ frappe.throw('Unable to update remote activity')
+
+def get_hub_connection():
+ if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
+ hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
+ hub_connection = hub_connector.get_connection()
+ return hub_connection.connection
+
+ # read-only connection
+ hub_connection = FrappeClient(frappe.conf.hub_url)
+ return hub_connection
diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py
index e69de29..9445e3a 100644
--- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py
+++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/__init__.py
@@ -0,0 +1,19 @@
+import io, base64, urllib, os
+
+def pre_process(doc):
+
+ file_path = doc.image
+ file_name = os.path.basename(file_path)
+
+ if file_path.startswith('http'):
+ url = file_path
+ file_path = os.path.join('/tmp', file_name)
+ urllib.urlretrieve(url, file_path)
+
+ with io.open(file_path, 'rb') as f:
+ doc.image = base64.b64encode(f.read())
+
+ doc.image_file_name = file_name
+
+ return doc
+
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 7423f2e..3ace088 100644
--- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json
+++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json
@@ -1,55 +1,45 @@
{
- "condition": "{\"publish_in_hub\": 1}",
- "creation": "2017-09-07 13:27:52.726350",
- "docstatus": 0,
- "doctype": "Data Migration Mapping",
+ "condition": "{\"publish_in_hub\": 1}",
+ "creation": "2017-09-07 13:27:52.726350",
+ "docstatus": 0,
+ "doctype": "Data Migration Mapping",
"fields": [
{
- "is_child_table": 0,
- "local_fieldname": "item_code",
+ "is_child_table": 0,
+ "local_fieldname": "item_code",
"remote_fieldname": "item_code"
- },
+ },
{
- "is_child_table": 0,
- "local_fieldname": "item_name",
+ "is_child_table": 0,
+ "local_fieldname": "item_name",
"remote_fieldname": "item_name"
- },
+ },
{
- "is_child_table": 0,
- "local_fieldname": "eval:frappe.db.get_default(\"company\")",
- "remote_fieldname": "company_name"
- },
+ "is_child_table": 0,
+ "local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')",
+ "remote_fieldname": "hub_seller"
+ },
{
- "is_child_table": 0,
- "local_fieldname": "image",
+ "is_child_table": 0,
+ "local_fieldname": "image",
"remote_fieldname": "image"
- },
+ },
{
- "is_child_table": 0,
- "local_fieldname": "item_group",
+ "is_child_table": 0,
+ "local_fieldname": "item_group",
"remote_fieldname": "item_group"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "eval:frappe.session.user",
- "remote_fieldname": "seller"
- },
- {
- "is_child_table": 0,
- "local_fieldname": "eval:frappe.db.get_default(\"country\")",
- "remote_fieldname": "country"
}
- ],
- "idx": 1,
- "local_doctype": "Item",
- "mapping_name": "Item to Hub Item",
- "mapping_type": "Push",
- "migration_id_field": "hub_sync_id",
- "modified": "2018-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-01 16:37:09.170546",
+ "modified_by": "Administrator",
+ "name": "Item to Hub Item",
+ "owner": "Administrator",
+ "page_length": 10,
+ "remote_objectname": "Hub Item",
"remote_primary_key": "item_code"
}
\ No newline at end of file
diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json
index d66ac24..1f772b6 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-01 16:37:09.027512",
+ "modified_by": "Administrator",
+ "module": "Hub Node",
+ "name": "Hub Sync",
+ "owner": "Administrator",
+ "plan_name": "Hub Sync",
+ "postprocess_method": "erpnext.hub_node.api.item_sync_postprocess"
}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.js b/erpnext/hub_node/doctype/hub_settings/hub_settings.js
index 29d870b..4bd3333 100644
--- a/erpnext/hub_node/doctype/hub_settings/hub_settings.js
+++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.js
@@ -1,53 +1,23 @@
frappe.ui.form.on("Hub Settings", {
refresh: function(frm) {
+ frm.disable_save();
frm.add_custom_button(__('Logs'),
() => frappe.set_route('List', 'Data Migration Run', {
data_migration_plan: 'Hub Sync'
}));
- frm.trigger("enabled");
-
if (frm.doc.enabled) {
frm.add_custom_button(__('Sync'),
() => frm.call('sync'));
}
},
- onload: function(frm) {
- let token = frappe.urllib.get_arg("access_token");
- if(token) {
- let email = frm.get_field("user");
- console.log('token', frappe.urllib.get_arg("access_token"));
-
- get_user_details(frm, token, email);
- let row = frappe.model.add_child(frm.doc, "Hub Users", "users");
- row.user = frappe.session.user;
- }
-
- if(!frm.doc.country) {
- frm.set_value("country", frappe.defaults.get_default("Country"));
- }
- if(!frm.doc.company) {
- frm.set_value("company", frappe.defaults.get_default("Company"));
- }
- if(!frm.doc.user) {
- frm.set_value("user", frappe.session.user);
- }
- },
+ onload: function(frm) { },
onload_post_render: function(frm) {
if(frm.get_field("unregister_from_hub").$input)
frm.get_field("unregister_from_hub").$input.addClass("btn-danger");
},
on_update: function(frm) {
},
- enabled: function(frm) {
- if(!frm.doc.enabled) {
- frm.trigger("set_enable_hub_primary_button");
- } else {
- frm.page.set_primary_action(__("Save Settings"), () => {
- frm.save();
- });
- }
- },
hub_user_email: function(frm) {
if(frm.doc.hub_user_email){
@@ -55,39 +25,6 @@
}
},
- set_enable_hub_primary_button: (frm) => {
- frm.page.set_primary_action(__("Enable Hub"), () => {
- if(frappe.session.user === "Administrator") {
- frappe.msgprint(__("Please login as another user."))
- } else {
- // frappe.verify_password(() => {
-
- // } );
-
- frm.trigger("call_pre_reg");
- // frm.trigger("call_register");
-
- }
- });
- },
-
- call_pre_reg: (frm) => {
- this.frm.call({
- doc: this.frm.doc,
- method: "pre_reg",
- args: {},
- freeze: true,
- callback: function(r) {
- console.log(r.message);
- authorize(frm, r.message.client_id, r.message.redirect_uri);
- },
- onerror: function() {
- frappe.msgprint(__("Wrong Password"));
- frm.set_value("enabled", 0);
- }
- });
- },
-
call_register: (frm) => {
this.frm.call({
doc: this.frm.doc,
@@ -111,67 +48,3 @@
});
},
});
-
-// 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 7c7109c..a0d8188 100644
--- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json
+++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json
@@ -14,20 +14,21 @@
"fields": [
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "enabled",
+ "fieldname": "registered",
"fieldtype": "Check",
- "hidden": 1,
+ "hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Enabled",
+ "label": "Registered",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -45,20 +46,21 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "suspended",
+ "fieldname": "sync_in_progress",
"fieldtype": "Check",
- "hidden": 1,
+ "hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Suspended",
+ "label": "Sync in Progress",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -76,198 +78,7 @@
},
{
"allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "enabled",
- "fieldname": "hub_username",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Hub Username",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "user",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "User",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_0",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "enabled",
- "fieldname": "hub_user_status",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Status",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "enabled",
- "fieldname": "language",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Language",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 1,
- "collapsible_depends_on": "eval:(!doc.enabled)",
- "columns": 0,
- "depends_on": "",
- "fieldname": "seller_profile_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Company and Seller Profile",
- "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,
@@ -299,6 +110,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -331,6 +143,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -362,6 +175,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 +240,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 +304,12 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "seller_description",
+ "fieldname": "company_description",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -456,6 +336,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -487,43 +368,12 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 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",
+ "depends_on": "",
"fieldname": "publish_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -551,6 +401,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -582,6 +433,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -614,6 +466,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -647,6 +500,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -679,11 +533,12 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 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 +555,7 @@
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 1,
+ "read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -711,6 +566,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -744,6 +600,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -777,6 +634,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -817,8 +675,8 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-03-26 00:55:17.929140",
- "modified_by": "test1@example.com",
+ "modified": "2018-07-30 10:43:28.818498",
+ "modified_by": "Administrator",
"module": "Hub Node",
"name": "Hub Settings",
"name_case": "",
@@ -826,7 +684,6 @@
"permissions": [
{
"amend": 0,
- "apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py
index 15ee4b7..bfb3320 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,33 +10,18 @@
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):
+ protocol = 'http://'
+ self.site_name = protocol + frappe.local.site + ':' + str(frappe.conf.webserver_port)
if self.publish_pricing and not self.selling_price_list:
frappe.throw(_("Please select a Price List to publish pricing"))
def get_hub_url(self):
- return hub_url
+ return frappe.conf.hub_url
def sync(self):
"""Create and execute Data Migration Run for Hub Sync plan"""
@@ -45,68 +30,53 @@
doc = frappe.get_doc({
'doctype': 'Data Migration Run',
'data_migration_plan': 'Hub Sync',
- 'data_migration_connector': 'Hub Connector'
+ 'data_migration_connector': 'Hub Connector',
+ 'trigger_name': 'items-sync'
}).insert()
+ self.sync_in_progress = 1
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
- }
-
-
def register(self):
""" Create a User on hub.erpnext.org and return username/password """
+
+ # TODO: site_name for cloud sites
+ protocol = 'http://'
+ self.site_name = protocol + frappe.local.site + ':' + str(frappe.conf.webserver_port)
+
data = {
- '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)
+ response = requests.post(post_url, data=data, headers = {'accept': 'application/json'})
+
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'):
@@ -120,7 +90,7 @@
'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()
@@ -143,3 +113,11 @@
def sync():
hub_settings = frappe.get_doc('Hub Settings')
hub_settings.sync()
+
+@frappe.whitelist()
+def register_seller(**kwargs):
+ settings = frappe.get_doc('Hub Settings')
+ settings.update(kwargs)
+ message = settings.register()
+
+ return message.get('email')
diff --git a/erpnext/hub_node/legacy.py b/erpnext/hub_node/legacy.py
new file mode 100644
index 0000000..87d4e1b
--- /dev/null
+++ b/erpnext/hub_node/legacy.py
@@ -0,0 +1,178 @@
+from __future__ import unicode_literals
+import frappe, requests, json
+from frappe.utils import now, nowdate
+from frappe.frappeclient import FrappeClient
+
+@frappe.whitelist()
+def get_item_favourites(start=0, limit=20, fields=["*"], order_by=None):
+ doctype = 'Hub Item'
+ hub_settings = frappe.get_doc('Hub Settings')
+ item_names_str = hub_settings.get('custom_data') or '[]'
+ item_names = json.loads(item_names_str)
+ filters = json.dumps({
+ 'hub_item_code': ['in', item_names]
+ })
+ return get_list(doctype, start, limit, fields, filters, order_by)
+
+@frappe.whitelist()
+def update_wishlist_item(item_name, remove=0):
+ remove = int(remove)
+ hub_settings = frappe.get_doc('Hub Settings')
+ data = hub_settings.get('custom_data')
+ if not data or not json.loads(data):
+ data = '[]'
+ hub_settings.custom_data = data
+ hub_settings.save()
+
+ item_names_str = data
+ item_names = json.loads(item_names_str)
+ if not remove and item_name not in item_names:
+ item_names.append(item_name)
+ if remove and item_name in item_names:
+ item_names.remove(item_name)
+
+ item_names_str = json.dumps(item_names)
+
+ hub_settings.custom_data = item_names_str
+ hub_settings.save()
+
+@frappe.whitelist()
+def update_category(hub_item_code, category):
+ connection = get_hub_connection()
+
+ # args = frappe._dict(dict(
+ # doctype='Hub Category',
+ # hub_category_name=category
+ # ))
+ # response = connection.insert('Hub Category', args)
+
+ response = connection.update('Hub Item', frappe._dict(dict(
+ doctype='Hub Item',
+ hub_category = category
+ )), hub_item_code)
+
+ return response
+
+def make_opportunity(buyer_name, email_id):
+ buyer_name = "HUB-" + buyer_name
+
+ if not frappe.db.exists('Lead', {'email_id': email_id}):
+ lead = frappe.new_doc("Lead")
+ lead.lead_name = buyer_name
+ lead.email_id = email_id
+ lead.save(ignore_permissions=True)
+
+ o = frappe.new_doc("Opportunity")
+ o.enquiry_from = "Lead"
+ o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"]
+ o.save(ignore_permissions=True)
+
+@frappe.whitelist()
+def make_rfq_and_send_opportunity(item, supplier):
+ supplier = make_supplier(supplier)
+ contact = make_contact(supplier)
+ item = make_item(item)
+ rfq = make_rfq(item, supplier, contact)
+ status = send_opportunity(contact)
+
+ return {
+ 'rfq': rfq,
+ 'hub_document_created': status
+ }
+
+def make_supplier(supplier):
+ # make supplier if not already exists
+ supplier = frappe._dict(json.loads(supplier))
+
+ if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}):
+ supplier_doc = frappe.get_doc({
+ 'doctype': 'Supplier',
+ 'supplier_name': supplier.supplier_name,
+ 'supplier_group': supplier.supplier_group,
+ 'supplier_email': supplier.supplier_email
+ }).insert()
+ else:
+ supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name)
+
+ return supplier_doc
+
+def make_contact(supplier):
+ contact_name = get_default_contact('Supplier', supplier.supplier_name)
+ # make contact if not already exists
+ if not contact_name:
+ contact = frappe.get_doc({
+ 'doctype': 'Contact',
+ 'first_name': supplier.supplier_name,
+ 'email_id': supplier.supplier_email,
+ 'is_primary_contact': 1,
+ 'links': [
+ {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name}
+ ]
+ }).insert()
+ else:
+ contact = frappe.get_doc('Contact', contact_name)
+
+ return contact
+
+def make_item(item):
+ # make item if not already exists
+ item = frappe._dict(json.loads(item))
+
+ if not frappe.db.exists('Item', {'item_code': item.item_code}):
+ item_doc = frappe.get_doc({
+ 'doctype': 'Item',
+ 'item_code': item.item_code,
+ 'item_group': item.item_group,
+ 'is_item_from_hub': 1
+ }).insert()
+ else:
+ item_doc = frappe.get_doc('Item', item.item_code)
+
+ return item_doc
+
+def make_rfq(item, supplier, contact):
+ # make rfq
+ rfq = frappe.get_doc({
+ 'doctype': 'Request for Quotation',
+ 'transaction_date': nowdate(),
+ 'status': 'Draft',
+ 'company': frappe.db.get_single_value('Hub Settings', 'company'),
+ 'message_for_supplier': 'Please supply the specified items at the best possible rates',
+ 'suppliers': [
+ { 'supplier': supplier.name, 'contact': contact.name }
+ ],
+ 'items': [
+ {
+ 'item_code': item.item_code,
+ 'qty': 1,
+ 'schedule_date': nowdate(),
+ 'warehouse': item.default_warehouse or get_root_of("Warehouse"),
+ 'description': item.description,
+ 'uom': item.stock_uom
+ }
+ ]
+ }).insert()
+
+ rfq.save()
+ rfq.submit()
+ return rfq
+
+def send_opportunity(contact):
+ # Make Hub Message on Hub with lead data
+ doc = {
+ 'doctype': 'Lead',
+ 'lead_name': frappe.db.get_single_value('Hub Settings', 'company'),
+ 'email_id': frappe.db.get_single_value('Hub Settings', 'user')
+ }
+
+ args = frappe._dict(dict(
+ doctype='Hub Message',
+ reference_doctype='Lead',
+ data=json.dumps(doc),
+ user=contact.email_id
+ ))
+
+ connection = get_hub_connection()
+ response = connection.insert('Hub Message', args)
+
+ return response.ok
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 20def27..154f440 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -559,3 +559,5 @@
erpnext.patches.v11_0.rename_healthcare_doctype_and_fields
erpnext.patches.v11_0.add_item_group_defaults
erpnext.patches.v10_0.update_address_template_for_india
+execute:frappe.delete_doc("Page", "hub")
+erpnext.patches.v11_0.reset_publish_in_hub_for_all_items
diff --git a/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py b/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py
new file mode 100644
index 0000000..fac772c
--- /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/public/build.json b/erpnext/public/build.json
index ed4ebab..7bcf99b 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -7,6 +7,9 @@
"public/js/website_utils.js",
"public/js/shopping_cart.js"
],
+ "js/marketplace.min.js": [
+ "public/js/hub/marketplace.js"
+ ],
"js/erpnext.min.js": [
"public/js/conf.js",
"public/js/utils.js",
diff --git a/erpnext/public/js/hub/helpers.js b/erpnext/public/js/hub/helpers.js
new file mode 100644
index 0000000..22e35c3
--- /dev/null
+++ b/erpnext/public/js/hub/helpers.js
@@ -0,0 +1,143 @@
+function get_empty_state(message, action) {
+ return `<div class="empty-state flex align-center flex-column justify-center">
+ <p class="text-muted">${message}</p>
+ ${action ? `<p>${action}</p>`: ''}
+ </div>`;
+}
+
+function get_item_card_container_html(items, title='', get_item_html = get_item_card_html) {
+ const items_html = (items || []).map(item => get_item_html(item)).join('');
+ const title_html = title
+ ? `<div class="col-sm-12 margin-bottom">
+ <b>${title}</b>
+ </div>`
+ : '';
+
+ const html = `<div class="row hub-card-container">
+ ${title_html}
+ ${items_html}
+ </div>`;
+
+ return html;
+}
+
+function get_item_card_html(item) {
+ const item_name = item.item_name || item.name;
+ const title = strip_html(item_name);
+ const img_url = item.image;
+ const company_name = item.company;
+
+ // Subtitle
+ let subtitle = [comment_when(item.creation)];
+ const rating = item.average_rating;
+ if (rating > 0) {
+ subtitle.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
+ }
+ subtitle.push(company_name);
+
+ let dot_spacer = '<span aria-hidden="true"> · </span>';
+ subtitle = subtitle.join(dot_spacer);
+
+ const item_html = `
+ <div class="col-md-3 col-sm-4 col-xs-6">
+ <div class="hub-card" data-route="marketplace/item/${item.hub_item_code}">
+ <div class="hub-card-header">
+ <div class="hub-card-title ellipsis bold">${title}</div>
+ <div class="hub-card-subtitle ellipsis text-muted">${subtitle}</div>
+ </div>
+ <div class="hub-card-body">
+ <img class="hub-card-image" src="${img_url}" />
+ <div class="overlay hub-card-overlay"></div>
+ </div>
+ </div>
+ </div>
+ `;
+
+ return item_html;
+}
+
+function get_local_item_card_html(item) {
+ const item_name = item.item_name || item.name;
+ const title = strip_html(item_name);
+ const img_url = item.image;
+ const company_name = item.company;
+
+ const is_active = item.publish_in_hub;
+ const id = item.hub_item_code || item.item_code;
+
+ // Subtitle
+ let subtitle = [comment_when(item.creation)];
+ const rating = item.average_rating;
+ if (rating > 0) {
+ subtitle.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
+ }
+ subtitle.push(company_name);
+
+ let dot_spacer = '<span aria-hidden="true"> · </span>';
+ subtitle = subtitle.join(dot_spacer);
+
+ const edit_item_button = `<div class="hub-card-overlay-button" style="right: 15px; bottom: 15px;" data-route="Form/Item/${item.item_name}">
+ <button class="btn btn-default zoom-view">
+ <i class="octicon octicon-pencil text-muted"></i>
+ </button>
+ </div>`;
+
+ const item_html = `
+ <div class="col-md-3 col-sm-4 col-xs-6">
+ <div class="hub-card is-local ${is_active ? 'active' : ''}" data-id="${id}">
+ <div class="hub-card-header">
+ <div class="hub-card-title ellipsis bold">${title}</div>
+ <div class="hub-card-subtitle ellipsis text-muted">${subtitle}</div>
+ <i class="octicon octicon-check text-success"></i>
+ </div>
+ <div class="hub-card-body">
+ <img class="hub-card-image" src="${img_url}" />
+ <div class="hub-card-overlay">
+ <div class="hub-card-overlay-body">
+ ${edit_item_button}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ `;
+
+ return item_html;
+}
+
+
+function get_rating_html(rating) {
+ let rating_html = ``;
+ for (var i = 0; i < 5; i++) {
+ let star_class = 'fa-star';
+ if (i >= rating) star_class = 'fa-star-o';
+ rating_html += `<i class='fa fa-fw ${star_class} star-icon' data-index=${i}></i>`;
+ }
+ return rating_html;
+}
+
+function make_search_bar({wrapper, on_search, placeholder = __('Search for anything')}) {
+ const $search = $(`
+ <div class="hub-search-container">
+ <input type="text" class="form-control" placeholder="${placeholder}">
+ </div>`
+ );
+ wrapper.append($search);
+ const $search_input = $search.find('input');
+
+ $search_input.on('keydown', frappe.utils.debounce((e) => {
+ if (e.which === frappe.ui.keyCode.ENTER) {
+ const search_value = $search_input.val();
+ on_search(search_value);
+ }
+ }, 300));
+}
+
+export {
+ get_empty_state,
+ get_item_card_container_html,
+ get_item_card_html,
+ get_local_item_card_html,
+ get_rating_html,
+ make_search_bar,
+}
\ No newline at end of file
diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js
new file mode 100644
index 0000000..6786cf6
--- /dev/null
+++ b/erpnext/public/js/hub/hub_call.js
@@ -0,0 +1,44 @@
+frappe.provide('hub');
+frappe.provide('erpnext.hub');
+
+erpnext.hub.cache = {};
+hub.call = function call_hub_method(method, args={}) {
+ return new Promise((resolve, reject) => {
+
+ // cache
+ const key = method + JSON.stringify(args);
+ if (erpnext.hub.cache[key]) {
+ resolve(erpnext.hub.cache[key]);
+ }
+
+ // cache invalidation after 5 minutes
+ const timeout = 5 * 60 * 1000;
+
+ setTimeout(() => {
+ delete erpnext.hub.cache[key];
+ }, timeout);
+
+ frappe.call({
+ method: 'erpnext.hub_node.api.call_hub_method',
+ args: {
+ method,
+ params: args
+ }
+ })
+ .then(r => {
+ if (r.message) {
+ if (r.message.error) {
+ frappe.throw({
+ title: __('Marketplace Error'),
+ message: r.message.error
+ });
+ }
+
+ erpnext.hub.cache[key] = r.message;
+ resolve(r.message)
+ }
+ reject(r)
+ })
+ .fail(reject)
+ });
+}
diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js
index d656605..f933600 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 = `<div class='page-card' style='margin: 140px auto;'>
- <div class='page-card-head'>
- <span class='indicator red'>${'Failed to connect'}</span>
- </div>
- <p>${ __("Please check your network connection.") }</p>
- <div><a href='#Hub/Item' class='btn btn-primary btn-sm'>
- ${ __("Reload") }</a></div>
- </div>`;
-
- let page = $('#body_div');
- page.append(html);
-
- return;
- }
-};
+$(document).on('toolbar_setup', () => {
+ $('#toolbar-user .navbar-reload').after(`
+ <li>
+ <a href="#marketplace/home">${__('Marketplace')}
+ </li>
+ `)
+})
diff --git a/erpnext/public/js/hub/hub_form.js b/erpnext/public/js/hub/hub_form.js
deleted file mode 100644
index 9287e6d..0000000
--- 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 `<div class="timeline">
- <div class="timeline-head">
- </div>
- <div class="timeline-new-email">
- <button class="btn btn-default btn-reply-email btn-xs">
- ${__("Reply")}
- </button>
- </div>
- <div class="timeline-items"></div>
- </div>`;
- }
-
- get_footer() {
- return `<div class="form-footer">
- <div class="after-save">
- <div class="form-comments"></div>
- </div>
- <div class="pull-right scroll-to-top">
- <a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
- </div>
- </div>`;
- }
-
- 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] ?
- `<img src="${this.data[this.image_field_name]}">
- <span class="helper"></span>` :
- `<div class="standard-image">${frappe.get_abbr(this.page_title)}</div>`;
-
- 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 = `<div class="form-footer">
- <div class="form-comments"></div>
- <div class="pull-right scroll-to-top">
- <a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
- </div>
- </div>`;
-
- let parent = $('<div>').appendTo(this.page.main.parent());
- this.$footer = $(footerHtml).appendTo(parent);
- }
-
- attachTimeline() {
- let timelineHtml = `<div class="timeline">
- <div class="timeline-head">
- </div>
- <div class="timeline-new-email">
- <button class="btn btn-default btn-reply-email btn-xs">
- ${ __("Reply") }
- </button>
- </div>
- <div class="timeline-items"></div>
- </div>`;
-
- 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
- ? `<div class="avatar-frame" style="background-image: url(${data.user_image})"></div>`
- : `<div class="standard-image" style="background-color: #fafbfc">${frappe.get_abbr(username)}</div>`
-
- let editHtml = data.own
- ? `<div class="pull-right hidden-xs close-btn-container">
- <span class="small text-muted">
- ${'data.delete'}
- </span>
- </div>
- <div class="pull-right edit-btn-container">
- <span class="small text-muted">
- ${'data.edit'}
- </span>
- </div>`
- : '';
-
- let ratingHtml = '';
-
- for(var i = 0; i < 5; i++) {
- let starIcon = 'fa-star-o'
- if(i < data.rating) {
- starIcon = 'fa-star';
- }
- ratingHtml += `<i class="fa fa-fw ${starIcon} star-icon" data-idx='${i}'></i>`;
- }
-
- $(this.getTimelineItem(data, imageHtml, editHtml, ratingHtml))
- .appendTo(this.$timelineList);
- }
-
- getTimelineItem(data, imageHtml, editHtml, ratingHtml) {
- return `<div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
- <span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
- ${imageHtml}
- </span>
-
- <div class="pull-left media-body">
- <div class="media-content-wrapper">
- <div class="action-btns">${editHtml}</div>
-
- <div class="comment-header clearfix small ${'linksActive'}">
- <span class="pull-left avatar avatar-small visible-xs">
- ${imageHtml}
- </span>
-
- <div class="asset-details">
- <span class="author-wrap">
- <i class="octicon octicon-quote hidden-xs fa-fw"></i>
- <span>${data.username}</span>
- </span>
- <a href="#Form/${''}" class="text-muted">
- <span class="text-muted hidden-xs">–</span>
- <span class="indicator-right ${'green'}
- delivery-status-indicator">
- <span class="hidden-xs">${data.pretty_date}</span>
- </span>
- </a>
-
- <a class="text-muted reply-link pull-right timeline-content-show"
- title="${__('Reply')}"> ${''} </a>
- <span class="comment-likes hidden-xs">
- <i class="octicon octicon-heart like-action text-extra-muted not-liked fa-fw">
- </i>
- <span class="likes-count text-muted">10</span>
- </span>
- </div>
- </div>
- <div class="reply timeline-content-show">
- <div class="timeline-item-content">
- <p class="text-muted small">
- <b>${data.subject}</b>
- </p>
-
- <hr>
-
- <p class="text-muted small">
- ${ratingHtml}
- </p>
-
- <hr>
- <p>
- ${data.content}
- </p>
- </div>
- </div>
- </div>
- </div>
- </div>`;
- }
-
- 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(`<span><i class='fa fa-check'></i> ${__('Quote Requested')}</span>`);
- } 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 0ff7970..0000000
--- 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 = `<img class="hub-icon" src="assets/erpnext/images/hub_logo.svg">`;
- let titleHtml = `<span class="hub-page-title">${title}</span>`;
- 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 = `<div class='page-card'>
- <div class='page-card-head'>
- <span class='indicator red'>
- {{ _("Payment Cancelled") }}</span>
- </div>
- <p>${ __("Your payment is cancelled.") }</p>
- <div><a href='' class='btn btn-primary btn-sm'>
- ${ __("Continue") }</a></div>
- </div>`;
-
- 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(`
- <div class="image-view-container small">
- ${html}
- </div>
- `);
- }
-
- 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 `
- <header class="list-row-head text-muted small">
- <div style="display: flex;">
- <div class="list-header-icon">
- <img title="${title}" alt="${title}" src="${image}">
- </div>
- <div class="list-header-info">
- <h5>
- ${title}
- </h5>
- <span class="margin-vertical-10 level-item">
- ${content}
- </span>
- </div>
- </div>
- </header>
- `;
- }
-
- renderHeader() {
- return `<header class="level list-row-head text-muted small">
- <div class="level-left list-header-subject">
- <div class="list-row-col list-subject level ">
- <img title="Riadco%20Group" alt="Riadco Group" src="https://cdn.pbrd.co/images/HdaPxcg.png">
- <span class="level-item">Products by Blah blah</span>
- </div>
- </div>
- <div class="level-left checkbox-actions">
- <div class="level list-subject">
- <input class="level-item list-check-all hidden-xs" type="checkbox" title="${__("Select All")}">
- <span class="level-item list-header-meta"></span>
- </div>
- </div>
- <div class="level-right">
- ${''}
- </div>
- </header>`;
- }
-
- get_image_html(encoded_name, src, alt_text) {
- return `<img data-name="${encoded_name}" src="${ src }" alt="${ alt_text }">`;
- }
-
- get_image_placeholder(title) {
- return `<span class="placeholder-text">${ frappe.get_abbr(title) }</span>`;
- }
-
- 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(`<span><i class='fa fa-check'></i> ${__('Quote Requested')}</span>`);
- } 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 = $(`<div class="border" style="
- margin-top: 10px;
- padding: 0px 10px;
- border-radius: 3px;
- ">
- <h5>Sell on HubMarket</h5>
- <p>Over 2000 products listed. Register your company to start selling.</p>
- </div>`);
-
- 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 += `<i class='fa fa-fw ${starClass} star-icon' data-index=${i}></i>`;
- }
-
- let item_html = `
- <div class="image-view-item">
- <div class="image-view-header">
- <div class="list-row-col list-subject ellipsis level">
- <span class="level-item bold ellipsis" title="McGuffin">
- <a href="${route}">${title}</a>
- </span>
- </div>
- <div class="text-muted small" style="margin: 5px 0px;">
- ${ratingHtml}
- (${reviewLength})
- </div>
- <div class="list-row-col">
- <a href="${'#Hub/Company/'+company_name+'/Items'}"><p>${ company_name }</p></a>
- </div>
- </div>
- <div class="image-view-body">
- <a data-name="${encoded_name}"
- title="${encoded_name}"
- href="${route}"
- >
- <div class="image-field ${_class}"
- data-name="${encoded_name}"
- >
- <button class="btn btn-default zoom-view" data-name="${encoded_name}">
- <i class="octicon octicon-eye" data-name="${encoded_name}"></i>
- </button>
- <button class="btn btn-default like-button" data-name="${encoded_name}">
- <i class="octicon octicon-heart" data-name="${encoded_name}"></i>
- </button>
- </div>
- </a>
- </div>
-
- </div>
- `;
-
- 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 ?
- `<img src="${company.company_logo}"><span class="helper"></span>` :
- `<div class="standard-image">${frappe.get_abbr(company.company_name)}</div>`;
-
- let item_html = `
- <div class="image-view-item">
- <div class="image-view-header">
- <div class="list-row-col list-subject ellipsis level">
- <span class="level-item bold ellipsis" title="McGuffin">
- <a href="${route}">${title}</a>
- </span>
- </div>
- </div>
- <div class="image-view-body">
- <a data-name="${encoded_name}"
- title="${encoded_name}"
- href="${route}">
- <div class="image-field ${_class}"
- data-name="${encoded_name}">
- </div>
- </a>
- </div>
-
- </div>
- `;
-
- 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 0000000..18a1bb0
--- /dev/null
+++ b/erpnext/public/js/hub/marketplace.js
@@ -0,0 +1,219 @@
+// pages
+import './pages/home';
+import './pages/favourites';
+import './pages/search';
+import './pages/category';
+import './pages/item';
+import './pages/seller';
+import './pages/register';
+import './pages/profile';
+import './pages/publish';
+import './pages/published_products';
+import './pages/messages';
+import './pages/not_found';
+
+// helpers
+import './helpers';
+import './hub_call';
+
+frappe.provide('hub');
+frappe.provide('erpnext.hub');
+
+erpnext.hub.Marketplace = class Marketplace {
+ constructor({ parent }) {
+ this.$parent = $(parent);
+ this.page = parent.page;
+
+ frappe.db.get_doc('Hub Settings')
+ .then(doc => {
+ hub.settings = doc;
+ this.registered = doc.registered;
+
+ this.setup_header();
+ this.make_sidebar();
+ this.make_body();
+ this.setup_events();
+ this.refresh();
+ });
+ }
+
+ setup_header() {
+ this.page.set_title(__('Marketplace'));
+ }
+
+ setup_events() {
+ this.$parent.on('click', '[data-route]', (e) => {
+ const $target = $(e.currentTarget);
+ const route = $target.data().route;
+ frappe.set_route(route);
+ });
+ }
+
+ make_sidebar() {
+ this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs');
+
+ this.make_sidebar_nav_buttons();
+ this.make_sidebar_categories();
+ }
+
+ make_sidebar_nav_buttons() {
+ let $nav_group = this.$sidebar.find('[data-nav-buttons]');
+ if (!$nav_group.length) {
+ $nav_group = $('<ul class="list-unstyled hub-sidebar-group" data-nav-buttons>').appendTo(this.$sidebar);
+ }
+ $nav_group.empty();
+
+ const user_specific_items_html = this.registered
+ ? `<li class="hub-sidebar-item" data-route="marketplace/favourites">
+ ${__('Favorites')}
+ </li>
+ <li class="hub-sidebar-item text-muted" data-route="marketplace/profile">
+ ${__('Your Profile')}
+ </li>
+ <li class="hub-sidebar-item text-muted" data-route="marketplace/publish">
+ ${__('Publish Products')}
+ </li>
+ <li class="hub-sidebar-item text-muted" data-route="marketplace/messages">
+ ${__('Messages')}
+ </li>`
+
+ : `<li class="hub-sidebar-item text-muted" data-route="marketplace/register">
+ ${__('Become a seller')}
+ </li>`;
+
+ $nav_group.append(`
+ <li class="hub-sidebar-item" data-route="marketplace/home">
+ ${__('Browse')}
+ </li>
+ ${user_specific_items_html}
+ `);
+ }
+
+ make_sidebar_categories() {
+ hub.call('get_categories')
+ .then(categories => {
+ categories = categories.map(d => d.name);
+
+ const sidebar_items = [
+ `<li class="hub-sidebar-item bold is-title">
+ ${__('Category')}
+ </li>`,
+ `<li class="hub-sidebar-item active" data-route="marketplace/home">
+ ${__('All')}
+ </li>`,
+ ...(this.registered
+ ? [`<li class="hub-sidebar-item active" data-route="marketplace/my-products">
+ ${__('Your Products')}
+ </li>`]
+ : []),
+ ...categories.map(category => `
+ <li class="hub-sidebar-item text-muted" data-route="marketplace/category/${category}">
+ ${__(category)}
+ </li>
+ `)
+ ];
+
+ this.$sidebar.append(`
+ <ul class="list-unstyled">
+ ${sidebar_items.join('')}
+ </ul>
+ `);
+
+ this.update_sidebar();
+ });
+ }
+
+ make_body() {
+ this.$body = this.$parent.find('.layout-main-section');
+ this.$body.on('seller-registered', () => {
+ this.registered = 1;
+ this.make_sidebar_nav_buttons();
+ });
+ }
+
+ update_sidebar() {
+ const route = frappe.get_route();
+ const route_str = route.slice(0, 2).join('/');
+ const $sidebar_item = this.$sidebar.find(`[data-route="${route_str}"]`);
+
+ const $siblings = this.$sidebar.find('[data-route]');
+ $siblings.removeClass('active').addClass('text-muted');
+ $sidebar_item.addClass('active').removeClass('text-muted');
+ }
+
+ refresh() {
+ const route = frappe.get_route();
+ this.subpages = this.subpages || {};
+
+ for (let page in this.subpages) {
+ this.subpages[page].hide();
+ }
+
+ if (route[1] === 'home' && !this.subpages.home) {
+ this.subpages.home = new erpnext.hub.Home(this.$body);
+ }
+
+ if (route[1] === 'search' && !this.subpages.search) {
+ this.subpages.search = new erpnext.hub.SearchPage(this.$body);
+ }
+
+ if (route[1] === 'category' && route[2] && !this.subpages.category) {
+ this.subpages.category = new erpnext.hub.Category(this.$body);
+ }
+
+ if (route[1] === 'item' && route[2] && !this.subpages.item) {
+ this.subpages.item = new erpnext.hub.Item(this.$body);
+ }
+
+ if (route[1] === 'seller' && !this.subpages['seller']) {
+ this.subpages['seller'] = new erpnext.hub.Seller(this.$body);
+ }
+
+ if (route[1] === 'register' && !this.subpages.register) {
+ if (this.registered) {
+ frappe.set_route('marketplace', 'home');
+ return;
+ }
+ this.subpages.register = new erpnext.hub.Register(this.$body);
+ }
+
+ // registered seller routes
+ if (route[1] === 'favourites' && !this.subpages.favourites) {
+ this.subpages.favourites = new erpnext.hub.Favourites(this.$body);
+ }
+
+ if (route[1] === 'profile' && !this.subpages.profile) {
+ this.subpages.profile = new erpnext.hub.Profile(this.$body);
+ }
+
+ if (route[1] === 'publish' && !this.subpages.publish) {
+ this.subpages.publish = new erpnext.hub.Publish(this.$body);
+ }
+
+ if (route[1] === 'my-products' && !this.subpages['my-products']) {
+ this.subpages['my-products'] = new erpnext.hub.PublishedProducts(this.$body);
+ }
+
+ if (route[1] === 'messages' && !this.subpages['messages']) {
+ this.subpages['messages'] = new erpnext.hub.Messages(this.$body);
+ }
+
+ // dont allow unregistered users to access registered routes
+ const registered_routes = ['favourites', 'profile', 'publish', 'my-products', 'messages'];
+ if (!hub.settings.registered && registered_routes.includes(route[1])) {
+ frappe.set_route('marketplace', 'home');
+ return;
+ }
+
+ if (!Object.keys(this.subpages).includes(route[1])) {
+ if (!this.subpages.not_found) {
+ this.subpages.not_found = new erpnext.hub.NotFound(this.$body);
+ }
+ route[1] = 'not_found';
+ }
+
+ this.update_sidebar();
+ frappe.utils.scroll_to(0);
+ this.subpages[route[1]].show();
+ }
+}
diff --git a/erpnext/public/js/hub/pages/category.js b/erpnext/public/js/hub/pages/category.js
new file mode 100644
index 0000000..87311ab
--- /dev/null
+++ b/erpnext/public/js/hub/pages/category.js
@@ -0,0 +1,27 @@
+import SubPage from './subpage';
+import { get_item_card_container_html } from '../helpers';
+
+erpnext.hub.Category = class Category extends SubPage {
+ refresh() {
+ this.category = frappe.get_route()[2];
+ this.get_items_for_category(this.category)
+ .then(r => {
+ this.render(r.message);
+ });
+ }
+
+ get_items_for_category(category) {
+ this.$wrapper.find('.hub-card-container').empty();
+ return frappe.call('erpnext.hub_node.api.get_list', {
+ doctype: 'Hub Item',
+ filters: {
+ hub_category: category
+ }
+ });
+ }
+
+ render(items) {
+ const html = get_item_card_container_html(items, __(this.category));
+ this.$wrapper.append(html)
+ }
+}
diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js
new file mode 100644
index 0000000..704caea
--- /dev/null
+++ b/erpnext/public/js/hub/pages/favourites.js
@@ -0,0 +1,21 @@
+import SubPage from './subpage';
+import { get_item_card_container_html } from '../helpers';
+
+erpnext.hub.Favourites = class Favourites extends SubPage {
+ refresh() {
+ this.get_favourites()
+ .then(items => {
+ this.render(items);
+ });
+ }
+
+ get_favourites() {
+ return hub.call('get_item_favourites');
+ }
+
+ render(items) {
+ this.$wrapper.find('.hub-card-container').empty();
+ const html = get_item_card_container_html(items, __('Favourites'));
+ this.$wrapper.append(html)
+ }
+}
\ No newline at end of file
diff --git a/erpnext/public/js/hub/pages/home.js b/erpnext/public/js/hub/pages/home.js
new file mode 100644
index 0000000..6a49f62
--- /dev/null
+++ b/erpnext/public/js/hub/pages/home.js
@@ -0,0 +1,41 @@
+import SubPage from './subpage';
+import { make_search_bar, get_item_card_container_html } from '../helpers';
+
+erpnext.hub.Home = class Home extends SubPage {
+ make_wrapper() {
+ super.make_wrapper();
+
+ make_search_bar({
+ wrapper: this.$wrapper,
+ on_search: keyword => {
+ frappe.set_route('marketplace', 'search', keyword);
+ }
+ });
+ }
+
+ refresh() {
+ this.get_items_and_render();
+ }
+
+ get_items_and_render() {
+ this.$wrapper.find('.hub-card-container').empty();
+ this.get_data()
+ .then(data => {
+ this.render(data);
+ });
+ }
+
+ get_data() {
+ return hub.call('get_data_for_homepage', { country: frappe.defaults.get_user_default('country') });
+ }
+
+ render(data) {
+ let html = get_item_card_container_html(data.random_items, __('Explore'));
+ this.$wrapper.append(html);
+
+ if (data.items_by_country.length) {
+ html = get_item_card_container_html(data.items_by_country, __('Near you'));
+ this.$wrapper.append(html);
+ }
+ }
+}
\ No newline at end of file
diff --git a/erpnext/public/js/hub/pages/item.js b/erpnext/public/js/hub/pages/item.js
new file mode 100644
index 0000000..7efa21f
--- /dev/null
+++ b/erpnext/public/js/hub/pages/item.js
@@ -0,0 +1,327 @@
+import SubPage from './subpage';
+import { get_rating_html } from '../helpers';
+
+erpnext.hub.Item = class Item extends SubPage {
+ refresh() {
+ this.show_skeleton();
+ this.hub_item_code = frappe.get_route()[2];
+
+ this.own_item = false;
+
+ this.get_item(this.hub_item_code)
+ .then(item => {
+ this.own_item = item.hub_seller === hub.settings.company_email;
+ this.item = item;
+ this.render(item);
+ });
+ }
+
+ show_skeleton() {
+ const skeleton = `<div class="hub-item-container">
+ <div class="row">
+ <div class="col-md-3">
+ <div class="hub-item-skeleton-image"></div>
+ </div>
+ <div class="col-md-6">
+ <h2 class="hub-skeleton" style="width: 75%;">Name</h2>
+ <div class="text-muted">
+ <p class="hub-skeleton" style="width: 35%;">Details</p>
+ <p class="hub-skeleton" style="width: 50%;">Ratings</p>
+ </div>
+ <hr>
+ <div class="hub-item-description">
+ <p class="hub-skeleton">Desc</p>
+ <p class="hub-skeleton" style="width: 85%;">Desc</p>
+ </div>
+ </div>
+ </div>
+ </div>`;
+
+ this.$wrapper.html(skeleton);
+ }
+
+ get_item(hub_item_code) {
+ return hub.call('get_item_details', {
+ hub_seller: hub.settings.company_email,
+ hub_item_code
+ });
+ }
+
+ render(item) {
+ const title = item.item_name || item.name;
+ const seller = item.company;
+
+ const who = __('Posted By {0}', [seller]);
+ const when = comment_when(item.creation);
+
+ const city = item.city ? item.city + ', ' : '';
+ const country = item.country ? item.country : '';
+ const where = `${city}${country}`;
+
+ const dot_spacer = '<span aria-hidden="true"> · </span>';
+
+ const description = item.description || '';
+
+ let stats = __('No views yet');
+ if(item.view_count) {
+ const views_message = __(`${item.view_count} Views`);
+
+ const rating_html = get_rating_html(item.average_rating);
+ const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet');
+
+ stats = `${views_message}${dot_spacer}${rating_html} (${rating_count})`;
+ }
+
+
+ let menu_items = '';
+
+ if(this.own_item) {
+ menu_items = `
+ <li><a data-action="edit_details">${__('Edit Details')}</a></li>
+ <li><a data-action="unpublish_item">${__('Unpublish')}</a></li>`;
+ } else {
+ menu_items = `
+ <li><a data-action="report_item">${__('Report this item')}</a></li>
+ `;
+ }
+
+ const html = `
+ <div class="hub-item-container">
+ <div class="row visible-xs">
+ <div class="col-xs-12 margin-bottom">
+ <button class="btn btn-xs btn-default" data-route="marketplace/home">${__('Back to home')}</button>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-3">
+ <div class="hub-item-image">
+ <img src="${item.image}">
+ </div>
+ </div>
+ <div class="col-md-8">
+ <h2>${title}</h2>
+ <div class="text-muted">
+ <p>${where}${dot_spacer}${when}</p>
+ <p>${stats}</p>
+ </div>
+ <hr>
+ <div class="hub-item-description">
+ ${description ?
+ `<b>${__('Description')}</b>
+ <p>${description}</p>
+ ` : `<p>${__('No description')}<p>`
+ }
+ </div>
+ </div>
+ <div class="col-md-1">
+ <div class="dropdown pull-right hub-item-dropdown">
+ <a class="dropdown-toggle btn btn-xs btn-default" data-toggle="dropdown">
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu dropdown-right" role="menu">
+ ${menu_items}
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="row hub-item-seller">
+ <div class="col-md-12 margin-top margin-bottom">
+ <b class="text-muted">Seller Information</b>
+ </div>
+ <div class="col-md-1">
+ <img src="https://picsum.photos/200">
+ </div>
+ <div class="col-md-8">
+ <div class="margin-bottom"><a href="#marketplace/seller/${seller}" class="bold">${seller}</a></div>
+ <button class="btn btn-xs btn-default text-muted" data-action="contact_seller">
+ ${__('Contact Seller')}
+ </button>
+ </div>
+ </div>
+ <!-- review area -->
+ <div class="row hub-item-review-container">
+ <div class="col-md-12 form-footer">
+ <div class="form-comments">
+ <div class="timeline">
+ <div class="timeline-head"></div>
+ <div class="timeline-items"></div>
+ </div>
+ </div>
+ <div class="pull-right scroll-to-top">
+ <a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
+ </div>
+ </div>
+ </div>
+ </div>
+ `;
+
+ this.$wrapper.html(html);
+
+ this.make_review_area();
+
+ this.get_reviews()
+ .then(reviews => {
+ this.reviews = reviews;
+ this.render_reviews(reviews);
+ });
+ }
+
+ edit_details() {
+ if (!this.edit_dialog) {
+ this.edit_dialog = new frappe.ui.Dialog({
+ title: "Edit Your Product",
+ fields: []
+ });
+ }
+ this.edit_dialog.show();
+ }
+
+ unpublish_item() {
+ if(!this.unpublish_dialog) {
+ this.unpublish_dialog = new frappe.ui.Dialog({
+ title: "Edit Your Product",
+ fields: []
+ });
+ }
+
+ this.unpublish_dialog.show();
+ }
+
+ contact_seller() {
+ const d = new frappe.ui.Dialog({
+ title: __('Send a message'),
+ fields: [
+ {
+ fieldname: 'to',
+ fieldtype: 'Read Only',
+ label: __('To'),
+ default: this.item.company
+ },
+ {
+ fieldtype: 'Text',
+ fieldname: 'message',
+ label: __('Message')
+ }
+ ],
+ primary_action: ({ message }) => {
+ if (!message) return;
+ }
+ });
+
+ d.show();
+ }
+
+ make_review_area() {
+ this.comment_area = new frappe.ui.ReviewArea({
+ parent: this.$wrapper.find('.timeline-head').empty(),
+ mentions: [],
+ on_submit: (values) => {
+ values.user = frappe.session.user;
+ values.username = frappe.session.user_fullname;
+
+ hub.call('add_item_review', {
+ hub_item_code: this.hub_item_code,
+ review: JSON.stringify(values)
+ })
+ .then(review => {
+ this.reviews = this.reviews || [];
+ this.reviews.push(review);
+ this.render_reviews(this.reviews);
+
+ this.comment_area.reset();
+ });
+ }
+ });
+ }
+
+ get_reviews() {
+ return hub.call('get_item_reviews', { hub_item_code: this.hub_item_code }).catch(() => {});
+ }
+
+ render_reviews(reviews=[]) {
+ this.$wrapper.find('.timeline-items').empty();
+
+ reviews.sort((a, b) => {
+ if (a.modified > b.modified) {
+ return -1;
+ }
+
+ if (a.modified < b.modified) {
+ return 1;
+ }
+
+ return 0;
+ });
+
+ reviews.forEach(review => this.render_review(review));
+ }
+
+ render_review(review) {
+ let username = review.username || review.user || __("Anonymous");
+
+ let image_html = review.user_image
+ ? `<div class="avatar-frame" style="background-image: url(${review.user_image})"></div>`
+ : `<div class="standard-image" style="background-color: #fafbfc">${frappe.get_abbr(username)}</div>`
+
+ let edit_html = review.own
+ ? `<div class="pull-right hidden-xs close-btn-container">
+ <span class="small text-muted">
+ ${'data.delete'}
+ </span>
+ </div>
+ <div class="pull-right edit-btn-container">
+ <span class="small text-muted">
+ ${'data.edit'}
+ </span>
+ </div>`
+ : '';
+
+ let rating_html = get_rating_html(review.rating);
+
+ const $timeline_items = this.$wrapper.find('.timeline-items');
+
+ $(this.get_timeline_item(review, image_html, edit_html, rating_html))
+ .appendTo($timeline_items);
+ }
+
+ get_timeline_item(data, image_html, edit_html, rating_html) {
+ return `<div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
+ <span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
+ ${image_html}
+ </span>
+ <div class="pull-left media-body">
+ <div class="media-content-wrapper">
+ <div class="action-btns">${edit_html}</div>
+
+ <div class="comment-header clearfix">
+ <span class="pull-left avatar avatar-small visible-xs">
+ ${image_html}
+ </span>
+
+ <div class="asset-details">
+ <span class="author-wrap">
+ <i class="octicon octicon-quote hidden-xs fa-fw"></i>
+ <span>${data.username}</span>
+ </span>
+ <a class="text-muted">
+ <span class="text-muted hidden-xs">–</span>
+ <span class="hidden-xs">${comment_when(data.modified)}</span>
+ </a>
+ </div>
+ </div>
+ <div class="reply timeline-content-show">
+ <div class="timeline-item-content">
+ <p class="text-muted">
+ ${rating_html}
+ </p>
+ <h6 class="bold">${data.subject}</h6>
+ <p class="text-muted">
+ ${data.content}
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>`;
+ }
+}
diff --git a/erpnext/public/js/hub/pages/messages.js b/erpnext/public/js/hub/pages/messages.js
new file mode 100644
index 0000000..7bc99a72
--- /dev/null
+++ b/erpnext/public/js/hub/pages/messages.js
@@ -0,0 +1,118 @@
+import SubPage from './subpage';
+import { make_search_bar } from '../helpers';
+
+erpnext.hub.Messages = class Messages extends SubPage {
+ make_wrapper() {
+ super.make_wrapper();
+
+ const html = `
+ <div class="row">
+ <div class="col-md-5">
+ <div class="seller-list"></div>
+ </div>
+ <div class="col-md-7">
+ ${get_message_area_html()}
+ </div>
+ </div>
+ `;
+
+ make_search_bar({
+ wrapper: this.$wrapper,
+ on_search: keyword => {
+
+ },
+ placeholder: __('Search for messages')
+ })
+
+ this.$wrapper.append(html);
+
+ this.message_input = new frappe.ui.CommentArea({
+ parent: this.$wrapper.find('.message-input'),
+ on_submit: (message) => {
+ this.message_input.reset();
+
+ // append message html
+ const $message_list = this.$wrapper.find('.message-list');
+ const message_html = get_message_html({
+ sender: hub.settings.company_email,
+ content: message
+ });
+ $message_list.append(message_html);
+ frappe.dom.scroll_to_bottom($message_list);
+
+ const to_seller = frappe.get_route()[2];
+ hub.call('send_message', {
+ from_seller: hub.settings.company_email,
+ to_seller,
+ message
+ });
+ },
+ no_wrapper: true
+ });
+ }
+
+ refresh() {
+ this.get_interactions()
+ .then(sellers => {
+ const html = sellers.map(get_list_item_html).join('');
+ this.$wrapper.find('.seller-list').html(html);
+ });
+
+ this.get_messages()
+ .then(messages => {
+ const $message_list = this.$wrapper.find('.message-list');
+ const html = messages.map(get_message_html).join('');
+ $message_list.html(html);
+ frappe.dom.scroll_to_bottom($message_list);
+ });
+ }
+
+ get_interactions() {
+ return hub.call('get_sellers_with_interactions', { for_seller: hub.settings.company_email });
+ }
+
+ get_messages() {
+ const against_seller = frappe.get_route()[2];
+ if (!against_seller) return Promise.resolve([]);
+
+ return hub.call('get_messages', {
+ for_seller: hub.settings.company_email,
+ against_seller: against_seller
+ });
+ }
+}
+
+function get_message_area_html() {
+ return `
+ <div class="message-area border padding flex flex-column">
+ <div class="message-list">
+ </div>
+ <div class="message-input">
+ </div>
+ </div>
+ `;
+}
+
+function get_list_item_html(seller) {
+ const active_class = frappe.get_route()[2] === seller.email ? 'active' : '';
+
+ return `
+ <div class="message-list-item ${active_class}" data-route="marketplace/messages/${seller.email}">
+ <div class="list-item-left">
+ <img src="${seller.image || 'https://picsum.photos/200?random'}">
+ </div>
+ <div class="list-item-body">
+ ${seller.company}
+ </div>
+ </div>
+ `;
+}
+
+function get_message_html(message) {
+ return `
+ <div>
+ <h5>${message.sender}</h5>
+ <p>${message.content}</p>
+ </div>
+ `;
+}
\ No newline at end of file
diff --git a/erpnext/public/js/hub/pages/not_found.js b/erpnext/public/js/hub/pages/not_found.js
new file mode 100644
index 0000000..3b86446
--- /dev/null
+++ b/erpnext/public/js/hub/pages/not_found.js
@@ -0,0 +1,10 @@
+import SubPage from './subpage';
+
+erpnext.hub.NotFound = class NotFound extends SubPage {
+ refresh() {
+ this.$wrapper.html(get_empty_state(
+ __('Sorry! I could not find what you were looking for.'),
+ `<button class="btn btn-default btn-xs" data-route="marketplace/home">${__('Back to home')}</button>`
+ ));
+ }
+}
diff --git a/erpnext/public/js/hub/pages/profile.js b/erpnext/public/js/hub/pages/profile.js
new file mode 100644
index 0000000..a38cde4
--- /dev/null
+++ b/erpnext/public/js/hub/pages/profile.js
@@ -0,0 +1,98 @@
+import SubPage from './subpage';
+
+erpnext.hub.Profile = class Profile extends SubPage {
+ make_wrapper() {
+ super.make_wrapper();
+ }
+
+ refresh() {
+ this.get_hub_seller_profile(this.keyword)
+ .then(profile => this.render(profile));
+ }
+
+ get_hub_seller_profile() {
+ return hub.call('get_hub_seller_profile', { hub_seller: hub.settings.company_email });
+ }
+
+ render(profile) {
+ const p = profile;
+ const content_by_log_type = this.get_content_by_log_type();
+
+ let activity_logs = (p.hub_seller_activity || []).sort((a, b) => {
+ return new Date(b.creation) - new Date(a.creation);
+ });
+
+ const timeline_items_html = activity_logs
+ .map(log => {
+ const stats = JSON.parse(log.stats);
+ const no_of_items = stats && stats.push_update || '';
+
+ const content = content_by_log_type[log.type];
+ const message = content.get_message(no_of_items);
+ const icon = content.icon;
+ return this.get_timeline_log_item(log.pretty_date, message, icon);
+ })
+ .join('');
+
+ const profile_html = `<div class="hub-item-container">
+ <div class="row visible-xs">
+ <div class="col-xs-12 margin-bottom">
+ <button class="btn btn-xs btn-default" data-route="marketplace/home">Back to home</button>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-3">
+ <div class="hub-item-image">
+ <img src="${p.logo}">
+ </div>
+ </div>
+ <div class="col-md-6">
+ <h2>${p.company}</h2>
+ <div class="text-muted">
+ <p>${p.country}</p>
+ <p>${p.site_name}</p>
+ </div>
+ <hr>
+ <div class="hub-item-description">
+ ${'description'
+ ? `<p>${p.company_description}</p>`
+ : `<p>__('No description')</p`
+ }
+ </div>
+ </div>
+ </div>
+
+ <div class="timeline">
+ <div class="timeline-items">
+ ${timeline_items_html}
+ </div>
+ </div>
+
+ </div>`;
+
+ this.$wrapper.html(profile_html);
+ }
+
+ get_timeline_log_item(pretty_date, message, icon) {
+ return `<div class="media timeline-item notification-content">
+ <div class="small">
+ <i class="octicon ${icon} fa-fw"></i>
+ <span title="Administrator"><b>${pretty_date}</b> ${message}</span>
+ </div>
+ </div>`;
+ }
+
+ get_content_by_log_type() {
+ return {
+ "Created": {
+ icon: 'octicon-heart',
+ get_message: () => 'Joined Marketplace'
+ },
+ "Items Publish": {
+ icon: 'octicon-bookmark',
+ get_message: (no_of_items) =>
+ `Published ${no_of_items} product${no_of_items > 1 ? 's' : ''} to Marketplace`
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/erpnext/public/js/hub/pages/publish.js b/erpnext/public/js/hub/pages/publish.js
new file mode 100644
index 0000000..859782e
--- /dev/null
+++ b/erpnext/public/js/hub/pages/publish.js
@@ -0,0 +1,228 @@
+import SubPage from './subpage';
+import { make_search_bar, get_item_card_container_html, get_local_item_card_html } from '../helpers';
+
+erpnext.hub.Publish = class Publish extends SubPage {
+ make_wrapper() {
+ super.make_wrapper();
+ this.items_to_publish = [];
+ this.unpublished_items = [];
+ this.fetched_items = [];
+
+ frappe.realtime.on("items-sync", (data) => {
+ this.$wrapper.find('.progress-bar').css('width', data.progress_percent+'%');
+
+ if(data.progress_percent === 100 || data.progress_percent === '100') {
+ setTimeout(() => {
+ hub.settings.sync_in_progress = 0;
+ frappe.db.get_doc('Hub Settings')
+ .then(doc => {
+ hub.settings = doc;
+ this.refresh();
+ });
+ }, 500);
+ }
+ });
+ }
+
+ refresh() {
+ if(!hub.settings.sync_in_progress) {
+ this.make_publish_ready_state();
+ } else {
+ this.make_publish_in_progress_state();
+ }
+ }
+
+ make_publish_ready_state() {
+ this.$wrapper.empty();
+ this.$wrapper.append(this.get_publishing_header());
+
+ make_search_bar({
+ wrapper: this.$wrapper,
+ on_search: keyword => {
+ this.search_value = keyword;
+ this.get_items_and_render();
+ },
+ placeholder: __('Search Items')
+ });
+
+ this.setup_publishing_events();
+
+ if(hub.settings.last_sync_datetime) {
+ this.show_message(`Last sync was <a href="#marketplace/profile">${comment_when(hub.settings.last_sync_datetime)}</a>.
+ <a href="#marketplace/my-products">See your Published Products</a>.`);
+ }
+
+ this.get_items_and_render();
+ }
+
+ get_publishing_header() {
+ const title_html = `<b>${__('Select Products to Publish')}</b>`;
+
+ const subtitle_html = `<p class="text-muted">
+ ${__(`Only products with an image, description and category can be published.
+ Please update them if an item in your inventory does not appear.`)}
+ </p>`;
+
+ const publish_button_html = `<button class="btn btn-primary btn-sm publish-items">
+ <i class="visible-xs octicon octicon-check"></i>
+ <span class="hidden-xs">${__('Publish')}</span>
+ </button>`;
+
+ return $(`
+ <div class='subpage-title flex'>
+ <div>
+ ${title_html}
+ ${subtitle_html}
+ </div>
+ ${publish_button_html}
+ </div>
+ `);
+ }
+
+ setup_publishing_events() {
+ this.$wrapper.find('.publish-items').on('click', () => {
+ this.publish_selected_items()
+ .then(this.refresh.bind(this))
+ });
+
+ this.$wrapper.on('click', '.hub-card', (e) => {
+ const $target = $(e.currentTarget);
+ $target.toggleClass('active');
+
+ // Get total items
+ const total_items = this.$wrapper.find('.hub-card.active').length;
+
+ let button_label;
+ if (total_items > 0) {
+ const more_than_one = total_items > 1;
+ button_label = __('Publish {0} item{1}', [total_items, more_than_one ? 's' : '']);
+ } else {
+ button_label = __('Publish');
+ }
+
+ this.$wrapper.find('.publish-items')
+ .text(button_label)
+ .prop('disabled', total_items === 0);
+ });
+ }
+
+ show_message(message) {
+ const $message = $(`<div class="subpage-message">
+ <p class="text-muted flex">
+ <span>
+ ${message}
+ </span>
+ <i class="octicon octicon-x text-extra-muted"></i>
+ </p>
+ </div>`);
+
+ $message.find('.octicon-x').on('click', () => {
+ $message.remove();
+ });
+
+ this.$wrapper.prepend($message);
+ }
+
+ make_publish_in_progress_state() {
+ this.$wrapper.empty();
+
+ this.$wrapper.append(this.show_publish_progress());
+
+ const subtitle_html = `<p class="text-muted">
+ ${__(`Only products with an image, description and category can be published.
+ Please update them if an item in your inventory does not appear.`)}
+ </p>`;
+
+ this.$wrapper.append(subtitle_html);
+
+ // Show search list with only desctiption, and don't set any events
+ make_search_bar({
+ wrapper: this.$wrapper,
+ on_search: keyword => {
+ this.search_value = keyword;
+ this.get_items_and_render();
+ },
+ placeholder: __('Search Items')
+ });
+
+ this.get_items_and_render();
+ }
+
+ show_publish_progress() {
+ const items_to_publish = this.items_to_publish.length
+ ? this.items_to_publish
+ : JSON.parse(hub.settings.custom_data);
+
+ const $publish_progress = $(`<div class="sync-progress">
+ <p><b>${__(`Syncing ${items_to_publish.length} Products`)}</b></p>
+ <div class="progress">
+ <div class="progress-bar" style="width: 1%"></div>
+ </div>
+
+ </div>`);
+
+ const items_to_publish_container = $(get_item_card_container_html(
+ items_to_publish, '', get_local_item_card_html));
+
+ items_to_publish_container.find('.hub-card').addClass('active');
+
+ $publish_progress.append(items_to_publish_container);
+
+ return $publish_progress;
+ }
+
+ get_items_and_render(wrapper = this.$wrapper) {
+ wrapper.find('.results').remove();
+ const items = this.get_valid_items();
+
+ if(!items.then) {
+ this.render(items, wrapper);
+ } else {
+ items.then(r => {
+ this.fetched_items = r.message;
+ this.render(r.message, wrapper);
+ });
+ }
+ }
+
+ render(items, wrapper) {
+ const items_container = $(get_item_card_container_html(items, '', get_local_item_card_html));
+ items_container.addClass('results');
+ wrapper.append(items_container);
+ }
+
+ get_valid_items() {
+ if(this.unpublished_items.length) {
+ return this.unpublished_items;
+ }
+ return frappe.call(
+ 'erpnext.hub_node.api.get_valid_items',
+ {
+ search_value: this.search_value
+ }
+ );
+ }
+
+ publish_selected_items() {
+ const item_codes_to_publish = [];
+ this.$wrapper.find('.hub-card.active').map(function () {
+ item_codes_to_publish.push($(this).attr("data-id"));
+ });
+
+ this.unpublished_items = this.fetched_items.filter(item => {
+ return !item_codes_to_publish.includes(item.item_code);
+ });
+
+ const items_to_publish = this.fetched_items.filter(item => {
+ return item_codes_to_publish.includes(item.item_code);
+ });
+ this.items_to_publish = items_to_publish;
+
+ return frappe.call(
+ 'erpnext.hub_node.api.publish_selected_items',
+ {
+ items_to_publish: item_codes_to_publish
+ }
+ )
+ }
+}
diff --git a/erpnext/public/js/hub/pages/published_products.js b/erpnext/public/js/hub/pages/published_products.js
new file mode 100644
index 0000000..1b19a51
--- /dev/null
+++ b/erpnext/public/js/hub/pages/published_products.js
@@ -0,0 +1,23 @@
+import SubPage from './subpage';
+import { get_item_card_container_html } from '../helpers';
+
+erpnext.hub.PublishedProducts = class PublishedProducts extends SubPage {
+ get_items_and_render() {
+ this.$wrapper.find('.hub-card-container').empty();
+ this.get_published_products()
+ .then(items => this.render(items));
+ }
+
+ refresh() {
+ this.get_items_and_render();
+ }
+
+ render(items) {
+ const items_container = $(get_item_card_container_html(items, __('Your Published Products')));
+ this.$wrapper.append(items_container);
+ }
+
+ get_published_products() {
+ return hub.call('get_items', { hub_seller: hub.settings.company_email });
+ }
+}
diff --git a/erpnext/public/js/hub/pages/register.js b/erpnext/public/js/hub/pages/register.js
new file mode 100644
index 0000000..b95ec04
--- /dev/null
+++ b/erpnext/public/js/hub/pages/register.js
@@ -0,0 +1,110 @@
+import SubPage from './subpage';
+
+erpnext.hub.Register = class Register extends SubPage {
+ make_wrapper() {
+ super.make_wrapper();
+ this.$register_container = $(`<div class="row register-container">`)
+ .appendTo(this.$wrapper);
+ this.$form_container = $('<div class="col-md-8 col-md-offset-1 form-container">')
+ .appendTo(this.$wrapper);
+ }
+
+ refresh() {
+ this.$register_container.empty();
+ this.$form_container.empty();
+ this.render();
+ }
+
+ render() {
+ this.make_field_group();
+ }
+
+ make_field_group() {
+ const fields = [
+ {
+ fieldtype: 'Link',
+ fieldname: 'company',
+ label: __('Company'),
+ options: 'Company',
+ onchange: () => {
+ const value = this.field_group.get_value('company');
+
+ if (value) {
+ frappe.db.get_doc('Company', value)
+ .then(company => {
+ this.field_group.set_values({
+ country: company.country,
+ company_email: company.email,
+ currency: company.default_currency
+ });
+ });
+ }
+ }
+ },
+ {
+ fieldname: 'company_email',
+ label: __('Email'),
+ fieldtype: 'Data'
+ },
+ {
+ fieldname: 'country',
+ label: __('Country'),
+ fieldtype: 'Read Only'
+ },
+ {
+ fieldname: 'currency',
+ label: __('Currency'),
+ fieldtype: 'Read Only'
+ },
+ {
+ fieldtype: 'Text',
+ label: __('About your Company'),
+ fieldname: 'company_description'
+ }
+ ];
+
+ this.field_group = new frappe.ui.FieldGroup({
+ parent: this.$form_container,
+ fields
+ });
+
+ this.field_group.make();
+
+ const default_company = frappe.defaults.get_default('company');
+ this.field_group.set_value('company', default_company);
+
+ this.$form_container.find('.form-column').append(`
+ <div class="text-right">
+ <button type="submit" class="btn btn-primary btn-register btn-sm">${__('Submit')}</button>
+ </div>
+ `);
+
+ this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller'))
+
+ this.$form_container.on('click', '.btn-register', (e) => {
+ const form_values = this.field_group.get_values();
+
+ let values_filled = true;
+ const mandatory_fields = ['company', 'company_email', 'company_description'];
+ mandatory_fields.forEach(field => {
+ const value = form_values[field];
+ if (!value) {
+ this.field_group.set_df_property(field, 'reqd', 1);
+ values_filled = false;
+ }
+ });
+ if (!values_filled) return;
+
+ frappe.call({
+ method: 'erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller',
+ args: form_values,
+ btn: $(e.currentTarget)
+ }).then(() => {
+ frappe.set_route('marketplace', 'publish');
+
+ // custom jquery event
+ this.$wrapper.trigger('seller-registered');
+ });
+ });
+ }
+}
diff --git a/erpnext/public/js/hub/pages/search.js b/erpnext/public/js/hub/pages/search.js
new file mode 100644
index 0000000..33c2b78
--- /dev/null
+++ b/erpnext/public/js/hub/pages/search.js
@@ -0,0 +1,34 @@
+import SubPage from './subpage';
+import { make_search_bar, get_item_card_container_html } from '../helpers';
+
+erpnext.hub.SearchPage = class SearchPage extends SubPage {
+ make_wrapper() {
+ super.make_wrapper();
+
+ make_search_bar({
+ wrapper: this.$wrapper,
+ on_search: keyword => {
+ frappe.set_route('marketplace', 'search', keyword);
+ }
+ });
+ }
+
+ refresh() {
+ this.keyword = frappe.get_route()[2] || '';
+ this.$wrapper.find('input').val(this.keyword);
+
+ this.get_items_by_keyword(this.keyword)
+ .then(items => this.render(items));
+ }
+
+ get_items_by_keyword(keyword) {
+ return hub.call('get_items', { keyword });
+ }
+
+ render(items) {
+ this.$wrapper.find('.hub-card-container').remove();
+ const title = this.keyword ? __('Search results for "{0}"', [this.keyword]) : '';
+ const html = get_item_card_container_html(items, title);
+ this.$wrapper.append(html);
+ }
+}
diff --git a/erpnext/public/js/hub/pages/seller.js b/erpnext/public/js/hub/pages/seller.js
new file mode 100644
index 0000000..94e8341
--- /dev/null
+++ b/erpnext/public/js/hub/pages/seller.js
@@ -0,0 +1,85 @@
+import SubPage from './subpage';
+
+erpnext.hub.Seller = class Seller extends SubPage {
+ make_wrapper() {
+ super.make_wrapper();
+ }
+
+ refresh() {
+ this.show_skeleton();
+ this.company = frappe.get_route()[2];
+ this.get_hub_seller_profile()
+ .then(this.render.bind(this));
+ }
+
+ get_hub_seller_profile() {
+ return hub.call('get_hub_seller_profile', { company: this.company });
+ }
+
+ // get_hub_seller_items(profile) {
+ // this.profile = profile;
+ // console.log(profile);
+ // return hub.call('get_items', { hub_seller: profile.user });
+ // }
+
+ show_skeleton() {
+ const skeleton = `<div class="hub-item-container">
+ <div class="row">
+ <div class="col-md-3">
+ <div class="hub-item-skeleton-image"></div>
+ </div>
+ <div class="col-md-6">
+ <h2 class="hub-skeleton" style="width: 75%;">Name</h2>
+ <div class="text-muted">
+ <p class="hub-skeleton" style="width: 35%;">Details</p>
+ <p class="hub-skeleton" style="width: 50%;">Ratings</p>
+ </div>
+ <hr>
+ <div class="hub-item-description">
+ <p class="hub-skeleton">Desc</p>
+ <p class="hub-skeleton" style="width: 85%;">Desc</p>
+ </div>
+ </div>
+ </div>
+ </div>`;
+
+ this.$wrapper.html(skeleton);
+ }
+
+ render(profile) {
+ const p = profile;
+
+ const profile_html = `<div class="hub-item-container">
+ <div class="row visible-xs">
+ <div class="col-xs-12 margin-bottom">
+ <button class="btn btn-xs btn-default" data-route="marketplace/home">Back to home</button>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-3">
+ <div class="hub-item-image">
+ <img src="${p.logo}">
+ </div>
+ </div>
+ <div class="col-md-6">
+ <h2>${p.company}</h2>
+ <div class="text-muted">
+ <p>${p.country}</p>
+ <p>${p.site_name}</p>
+ <p>${__(`Joined ${comment_when(p.creation)}`)}</p>
+ </div>
+ <hr>
+ <div class="hub-item-description">
+ ${'description'
+ ? `<p>${p.company_description}</p>`
+ : `<p>__('No description')</p`
+ }
+ </div>
+ </div>
+ </div>
+
+ </div>`;
+
+ this.$wrapper.html(profile_html);
+ }
+}
diff --git a/erpnext/public/js/hub/pages/subpage.js b/erpnext/public/js/hub/pages/subpage.js
new file mode 100644
index 0000000..a030e7e
--- /dev/null
+++ b/erpnext/public/js/hub/pages/subpage.js
@@ -0,0 +1,45 @@
+export default class SubPage {
+ constructor(parent, options) {
+ this.$parent = $(parent);
+ this.make_wrapper(options);
+
+ // generic action handler
+ this.$wrapper.on('click', '[data-action]', e => {
+ const $this = $(e.currentTarget);
+ const action = $this.data().action;
+
+ if (action && this[action]) {
+ this[action].apply(this);
+ }
+ })
+
+ // handle broken images after every render
+ if (this.render) {
+ this._render = this.render.bind(this);
+
+ this.render = (...args) => {
+ this._render(...args);
+ frappe.dom.handle_broken_images(this.$wrapper);
+ }
+ }
+ }
+
+ make_wrapper() {
+ const page_name = frappe.get_route()[1];
+ this.$wrapper = $(`<div class="marketplace-page" data-page-name="${page_name}">`).appendTo(this.$parent);
+ this.hide();
+ }
+
+ empty() {
+ this.$wrapper.empty();
+ }
+
+ show() {
+ this.refresh();
+ this.$wrapper.show();
+ }
+
+ hide() {
+ this.$wrapper.hide();
+ }
+}
\ No newline at end of file
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
index bdca28f..da51260 100644
--- a/erpnext/public/less/hub.less
+++ b/erpnext/public/less/hub.less
@@ -1,171 +1,264 @@
@import "../../../../frappe/frappe/public/less/variables.less";
-body[data-route^="Hub/"] {
- .hub-icon {
- width: 40px;
- height: 40px;
+body[data-route^="marketplace/"] {
+ .layout-side-section {
+ padding-top: 25px;
+ padding-right: 25px;
}
- .hub-page-title {
- margin-left: 10px;
- }
+ .layout-main-section {
+ border: none;
+ font-size: @text-medium;
+ padding-top: 25px;
- .img-wrapper {
- border: 1px solid #d1d8dd;
- border-radius: 3px;
- padding: 12px;
- overflow: hidden;
- text-align: center;
- white-space: nowrap;
-
- .helper {
- height: 100%;
- display: inline-block;
- vertical-align: middle;
+ @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;
+ }
+
+ .btn-primary {
+ background-color: #89da28;
+ border-color: #61ca23;
+ }
+
+ .btn-primary:hover {
+ background-color: #61ca23;
+ border-color: #59b81c;
+ }
+
+ .progress-bar {
+ background-color: #89da28;
+ }
+
+ .subpage-title.flex {
+ align-items: flex-start;
+ justify-content: space-between;
+ }
+
+ .subpage-message {
+ p {
+ padding: 10px 15px;
+ margin-top: 0px;
+ margin-bottom: 15px;
+ background-color: #f9fbf7;
+ justify-content: space-between;
+ }
+
+ .octicon-x {
+ cursor: pointer;
+ }
+ }
+
+ .hub-card {
+ 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;
+ cursor: pointer;
+
+ &: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;
+ }
+
+ .hub-card-image {
+ min-width: 100%;
+ width: 100%;
+ }
+
+ .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;
+
+ cursor: pointer;
+
+ &.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;
+ }
+
+ .empty-state {
+ height: 500px;
+ }
+
+ .form-container {
+ .frappe-control {
+ max-width: 100% !important;
}
}
- .star-icon.fa-star {
- color: @indicator-orange;
+ .form-message {
+ padding-top: 0;
+ padding-bottom: 0;
+ border-bottom: none;
}
- .octicon-heart.liked {
- color: @indicator-red;
+ .hub-item-container {
+ overflow: hidden;
}
- .margin-vertical-10 {
- margin: 10px 0px;
+ .hub-item-review-container {
+ margin-top: calc(30vh);
}
- .margin-vertical-15 {
- margin: 15px 0px;
- }
-
- .frappe-list .result {
- min-height: 100px;
- }
-
- .frappe-control[data-fieldtype="Attach Image"] {
- width: 140px;
- height: 180px;
+ .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;
+ padding: 8px 12px;
cursor: pointer;
- }
-}
-.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-list {
+ overflow: scroll;
}
- .image-view-body:hover .like-button {
- opacity: 0.7;
+ .message-area {
+ border-radius: 4px;
+ justify-content: space-between;
+ height: calc(100vh - 220px);
}
}
-
-.rating-area .star-icon {
- cursor: pointer;
- font-size: 15px;
-}
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index f9d0e71..1829b25 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -75,8 +75,8 @@
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'''