fix merge conflict
This commit is contained in:
commit
a5f6e54b70
@ -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,
|
||||
|
@ -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 @@ def enable_hub():
|
||||
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
|
||||
|
100
erpnext/hub_node/api.py
Normal file
100
erpnext/hub_node/api.py
Normal file
@ -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
|
@ -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
|
||||
|
@ -16,8 +16,8 @@
|
||||
},
|
||||
{
|
||||
"is_child_table": 0,
|
||||
"local_fieldname": "eval:frappe.db.get_default(\"company\")",
|
||||
"remote_fieldname": "company_name"
|
||||
"local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')",
|
||||
"remote_fieldname": "hub_seller"
|
||||
},
|
||||
{
|
||||
"is_child_table": 0,
|
||||
@ -28,16 +28,6 @@
|
||||
"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,
|
||||
@ -45,8 +35,8 @@
|
||||
"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",
|
||||
"modified": "2018-08-01 16:37:09.170546",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Item to Hub Item",
|
||||
"owner": "Administrator",
|
||||
"page_length": 10,
|
||||
|
@ -7,16 +7,13 @@
|
||||
{
|
||||
"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",
|
||||
"modified": "2018-08-01 16:37:09.027512",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Hub Node",
|
||||
"name": "Hub Sync",
|
||||
"owner": "Administrator",
|
||||
"plan_name": "Hub Sync"
|
||||
"plan_name": "Hub Sync",
|
||||
"postprocess_method": "erpnext.hub_node.api.item_sync_postprocess"
|
||||
}
|
@ -1,53 +1,23 @@
|
||||
frappe.ui.form.on("Hub Settings", {
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.add_custom_button(__('Logs'),
|
||||
() => frappe.set_route('List', 'Data Migration Run', {
|
||||
data_migration_plan: 'Hub Sync'
|
||||
}));
|
||||
|
||||
frm.trigger("enabled");
|
||||
|
||||
if (frm.doc.enabled) {
|
||||
frm.add_custom_button(__('Sync'),
|
||||
() => frm.call('sync'));
|
||||
}
|
||||
},
|
||||
onload: function(frm) {
|
||||
let token = frappe.urllib.get_arg("access_token");
|
||||
if(token) {
|
||||
let email = frm.get_field("user");
|
||||
console.log('token', frappe.urllib.get_arg("access_token"));
|
||||
|
||||
get_user_details(frm, token, email);
|
||||
let row = frappe.model.add_child(frm.doc, "Hub Users", "users");
|
||||
row.user = frappe.session.user;
|
||||
}
|
||||
|
||||
if(!frm.doc.country) {
|
||||
frm.set_value("country", frappe.defaults.get_default("Country"));
|
||||
}
|
||||
if(!frm.doc.company) {
|
||||
frm.set_value("company", frappe.defaults.get_default("Company"));
|
||||
}
|
||||
if(!frm.doc.user) {
|
||||
frm.set_value("user", frappe.session.user);
|
||||
}
|
||||
},
|
||||
onload: function(frm) { },
|
||||
onload_post_render: function(frm) {
|
||||
if(frm.get_field("unregister_from_hub").$input)
|
||||
frm.get_field("unregister_from_hub").$input.addClass("btn-danger");
|
||||
},
|
||||
on_update: function(frm) {
|
||||
},
|
||||
enabled: function(frm) {
|
||||
if(!frm.doc.enabled) {
|
||||
frm.trigger("set_enable_hub_primary_button");
|
||||
} else {
|
||||
frm.page.set_primary_action(__("Save Settings"), () => {
|
||||
frm.save();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
hub_user_email: function(frm) {
|
||||
if(frm.doc.hub_user_email){
|
||||
@ -55,39 +25,6 @@ frappe.ui.form.on("Hub Settings", {
|
||||
}
|
||||
},
|
||||
|
||||
set_enable_hub_primary_button: (frm) => {
|
||||
frm.page.set_primary_action(__("Enable Hub"), () => {
|
||||
if(frappe.session.user === "Administrator") {
|
||||
frappe.msgprint(__("Please login as another user."))
|
||||
} else {
|
||||
// frappe.verify_password(() => {
|
||||
|
||||
// } );
|
||||
|
||||
frm.trigger("call_pre_reg");
|
||||
// frm.trigger("call_register");
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
call_pre_reg: (frm) => {
|
||||
this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "pre_reg",
|
||||
args: {},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
console.log(r.message);
|
||||
authorize(frm, r.message.client_id, r.message.redirect_uri);
|
||||
},
|
||||
onerror: function() {
|
||||
frappe.msgprint(__("Wrong Password"));
|
||||
frm.set_value("enabled", 0);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
call_register: (frm) => {
|
||||
this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
@ -111,67 +48,3 @@ frappe.ui.form.on("Hub Settings", {
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// let hub_url = 'https://hubmarket.org'
|
||||
let hub_url = 'http://159.89.175.122'
|
||||
// let hub_url = 'http://erpnext.hub:8000'
|
||||
|
||||
function authorize(frm, client_id, redirect_uri) {
|
||||
|
||||
// queryStringData is details of OAuth Client (Implicit Grant) on Custom App
|
||||
var queryStringData = {
|
||||
response_type : "token",
|
||||
client_id : client_id,
|
||||
redirect_uri : redirect_uri
|
||||
}
|
||||
|
||||
// Get current raw route and build url
|
||||
const route = "/desk#" + frappe.get_raw_route_str();
|
||||
localStorage.removeItem("route"); // Clear previously set route if any
|
||||
localStorage.setItem("route", route);
|
||||
|
||||
// Go authorize!
|
||||
let api_route = "/api/method/frappe.integrations.oauth2.authorize?";
|
||||
let url = hub_url + api_route + $.param(queryStringData);
|
||||
window.location.replace(url, 'test');
|
||||
}
|
||||
|
||||
function get_user_details(frm, token, email) {
|
||||
console.log('user_details');
|
||||
var route = localStorage.getItem("route");
|
||||
if (token && route) {
|
||||
// Clean up access token from route
|
||||
frappe.set_route(frappe.get_route().join("/"))
|
||||
|
||||
// query protected resource e.g. Hub Items with token
|
||||
var call = {
|
||||
"async": true,
|
||||
"crossDomain": true,
|
||||
"url": hub_url + "/api/resource/User",
|
||||
"method": "GET",
|
||||
"data": {
|
||||
// "email": email,
|
||||
"fields": '["name", "first_name", "language"]',
|
||||
"limit_page_length": 1
|
||||
},
|
||||
"headers": {
|
||||
"authorization": "Bearer " + token,
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
$.ajax(call).done(function (response) {
|
||||
// display openid profile
|
||||
console.log('response', response);
|
||||
|
||||
let data = response.data[0];
|
||||
frm.set_value("enabled", 1);
|
||||
frm.set_value("hub_username", data.first_name);
|
||||
frm.set_value("hub_user_status", "Starter");
|
||||
frm.set_value("language", data.language);
|
||||
frm.save();
|
||||
|
||||
// clear route from localStorage
|
||||
localStorage.removeItem("route");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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,212 +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",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "hub_username",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Hub Username",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_0",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "hub_user_status",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "language",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Language",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:(!doc.enabled)",
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "seller_profile_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company and Seller Profile",
|
||||
"label": "Sync in Progress",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@ -268,6 +78,7 @@
|
||||
},
|
||||
{
|
||||
"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,
|
||||
|
@ -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 frappe import _
|
||||
from erpnext.utilities.product import get_price, get_qty_in_stock
|
||||
from six import string_types
|
||||
|
||||
hub_url = "https://hubmarket.org"
|
||||
# hub_url = "http://159.89.175.122"
|
||||
# hub_url = "http://erpnext.hub:8000"
|
||||
|
||||
class OAuth2Session():
|
||||
def __init__(self, headers):
|
||||
self.headers = headers
|
||||
def get(self, url, params, headers, verify):
|
||||
res = requests.get(url, params=params, headers=self.headers, verify=verify)
|
||||
return res
|
||||
def post(self, url, data, verify):
|
||||
res = requests.post(url, data=data, headers=self.headers, verify=verify)
|
||||
return res
|
||||
def put(self, url, data, verify):
|
||||
res = requests.put(url, data=data, headers=self.headers, verify=verify)
|
||||
return res
|
||||
|
||||
class HubSetupError(frappe.ValidationError): pass
|
||||
|
||||
class HubSettings(Document):
|
||||
|
||||
def validate(self):
|
||||
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 @@ class HubSettings(Document):
|
||||
doc = frappe.get_doc({
|
||||
'doctype': 'Data Migration Run',
|
||||
'data_migration_plan': 'Hub Sync',
|
||||
'data_migration_connector': 'Hub Connector'
|
||||
'data_migration_connector': 'Hub Connector',
|
||||
'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, headers = {'accept': 'application/json'})
|
||||
|
||||
response = requests.post(post_url, data=data)
|
||||
response.raise_for_status()
|
||||
|
||||
if response.ok:
|
||||
message = response.json().get('message')
|
||||
else:
|
||||
frappe.throw(json.loads(response.text))
|
||||
|
||||
if message and message.get('password'):
|
||||
self.user = frappe.session.user
|
||||
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 @@ class HubSettings(Document):
|
||||
'doctype': 'Data Migration Connector',
|
||||
'connector_type': 'Frappe',
|
||||
'connector_name': 'Hub Connector',
|
||||
'hostname': hub_url,
|
||||
'hostname': self.get_hub_url(),
|
||||
'username': message['email'],
|
||||
'password': message['password']
|
||||
}).insert()
|
||||
@ -143,3 +113,11 @@ def reset_hub_settings(last_sync_datetime = ""):
|
||||
def sync():
|
||||
hub_settings = frappe.get_doc('Hub Settings')
|
||||
hub_settings.sync()
|
||||
|
||||
@frappe.whitelist()
|
||||
def register_seller(**kwargs):
|
||||
settings = frappe.get_doc('Hub Settings')
|
||||
settings.update(kwargs)
|
||||
message = settings.register()
|
||||
|
||||
return message.get('email')
|
||||
|
178
erpnext/hub_node/legacy.py
Normal file
178
erpnext/hub_node/legacy.py
Normal file
@ -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
|
@ -559,3 +559,5 @@ erpnext.patches.v11_0.update_allow_transfer_for_manufacture
|
||||
erpnext.patches.v11_0.rename_healthcare_doctype_and_fields
|
||||
erpnext.patches.v11_0.add_item_group_defaults
|
||||
erpnext.patches.v10_0.update_address_template_for_india
|
||||
execute:frappe.delete_doc("Page", "hub")
|
||||
erpnext.patches.v11_0.reset_publish_in_hub_for_all_items
|
||||
|
@ -0,0 +1,5 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('stock', 'doctype', 'item')
|
||||
frappe.db.sql("""update `tabItem` set publish_in_hub = 0""")
|
@ -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",
|
||||
|
143
erpnext/public/js/hub/helpers.js
Normal file
143
erpnext/public/js/hub/helpers.js
Normal file
@ -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,
|
||||
}
|
44
erpnext/public/js/hub/hub_call.js
Normal file
44
erpnext/public/js/hub/hub_call.js
Normal file
@ -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)
|
||||
});
|
||||
}
|
@ -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];
|
||||
|
||||
const assets = {
|
||||
'List': [
|
||||
'/assets/erpnext/js/hub/hub_listing.js',
|
||||
],
|
||||
'Form': [
|
||||
'/assets/erpnext/js/hub/hub_form.js'
|
||||
]
|
||||
};
|
||||
frappe.model.with_doc('Hub Settings', 'Hub Settings', () => {
|
||||
this.hub_settings = frappe.get_doc('Hub Settings');
|
||||
|
||||
if (!erpnext.hub.pages[page_name]) {
|
||||
if(!frappe.is_online()) {
|
||||
this.render_offline_card();
|
||||
return;
|
||||
}
|
||||
if (!route[2]) {
|
||||
frappe.require(assets['List'], () => {
|
||||
if(page === 'Favourites') {
|
||||
erpnext.hub.pages[page_name] = new erpnext.hub['Favourites']({
|
||||
parent: this.make_page(true, page_name),
|
||||
hub_settings: this.hub_settings
|
||||
});
|
||||
frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views.Factory {
|
||||
show() {
|
||||
if (frappe.pages.marketplace) {
|
||||
frappe.container.change_to('marketplace');
|
||||
erpnext.hub.marketplace.refresh();
|
||||
} else {
|
||||
erpnext.hub.pages[page_name] = new erpnext.hub[page+'Listing']({
|
||||
parent: this.make_page(true, page_name),
|
||||
hub_settings: this.hub_settings
|
||||
});
|
||||
this.make('marketplace');
|
||||
}
|
||||
});
|
||||
} else if (!route[3]){
|
||||
frappe.require(assets['Form'], () => {
|
||||
erpnext.hub.pages[page_name] = new erpnext.hub[page+'Page']({
|
||||
unique_id: route[2],
|
||||
doctype: route[2],
|
||||
parent: this.make_page(true, page_name),
|
||||
hub_settings: this.hub_settings
|
||||
});
|
||||
});
|
||||
} else {
|
||||
frappe.require(assets['List'], () => {
|
||||
frappe.route_options = {};
|
||||
frappe.route_options["company_name"] = route[2]
|
||||
erpnext.hub.pages[page_name] = new erpnext.hub['ItemListing']({
|
||||
parent: this.make_page(true, page_name),
|
||||
hub_settings: this.hub_settings
|
||||
});
|
||||
});
|
||||
}
|
||||
window.hub_page = erpnext.hub.pages[page_name];
|
||||
} else {
|
||||
frappe.container.change_to(page_name);
|
||||
window.hub_page = erpnext.hub.pages[page_name];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render_offline_card() {
|
||||
let html = `<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>`;
|
||||
make(page_name) {
|
||||
const assets = [
|
||||
'/assets/js/marketplace.min.js'
|
||||
];
|
||||
|
||||
let page = $('#body_div');
|
||||
page.append(html);
|
||||
|
||||
return;
|
||||
frappe.require(assets, () => {
|
||||
erpnext.hub.marketplace = new erpnext.hub.Marketplace({
|
||||
parent: this.make_page(true, page_name)
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$(document).on('toolbar_setup', () => {
|
||||
$('#toolbar-user .navbar-reload').after(`
|
||||
<li>
|
||||
<a href="#marketplace/home">${__('Marketplace')}
|
||||
</li>
|
||||
`)
|
||||
})
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
219
erpnext/public/js/hub/marketplace.js
Normal file
219
erpnext/public/js/hub/marketplace.js
Normal file
@ -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();
|
||||
}
|
||||
}
|
27
erpnext/public/js/hub/pages/category.js
Normal file
27
erpnext/public/js/hub/pages/category.js
Normal file
@ -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)
|
||||
}
|
||||
}
|
21
erpnext/public/js/hub/pages/favourites.js
Normal file
21
erpnext/public/js/hub/pages/favourites.js
Normal file
@ -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)
|
||||
}
|
||||
}
|
41
erpnext/public/js/hub/pages/home.js
Normal file
41
erpnext/public/js/hub/pages/home.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
327
erpnext/public/js/hub/pages/item.js
Normal file
327
erpnext/public/js/hub/pages/item.js
Normal file
@ -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>`;
|
||||
}
|
||||
}
|
118
erpnext/public/js/hub/pages/messages.js
Normal file
118
erpnext/public/js/hub/pages/messages.js
Normal file
@ -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>
|
||||
`;
|
||||
}
|
10
erpnext/public/js/hub/pages/not_found.js
Normal file
10
erpnext/public/js/hub/pages/not_found.js
Normal file
@ -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>`
|
||||
));
|
||||
}
|
||||
}
|
98
erpnext/public/js/hub/pages/profile.js
Normal file
98
erpnext/public/js/hub/pages/profile.js
Normal file
@ -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`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
228
erpnext/public/js/hub/pages/publish.js
Normal file
228
erpnext/public/js/hub/pages/publish.js
Normal file
@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
23
erpnext/public/js/hub/pages/published_products.js
Normal file
23
erpnext/public/js/hub/pages/published_products.js
Normal file
@ -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 });
|
||||
}
|
||||
}
|
110
erpnext/public/js/hub/pages/register.js
Normal file
110
erpnext/public/js/hub/pages/register.js
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
34
erpnext/public/js/hub/pages/search.js
Normal file
34
erpnext/public/js/hub/pages/search.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
85
erpnext/public/js/hub/pages/seller.js
Normal file
85
erpnext/public/js/hub/pages/seller.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
45
erpnext/public/js/hub/pages/subpage.js
Normal file
45
erpnext/public/js/hub/pages/subpage.js
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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 {
|
||||
.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;
|
||||
|
||||
&:not(.active) {
|
||||
filter: grayscale(1);
|
||||
color: @text-muted;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
|
||||
.list-item-left {
|
||||
width: 30px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.list-item-body {
|
||||
font-weight: bold;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.message-list {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.message-area {
|
||||
border-radius: 4px;
|
||||
justify-content: space-between;
|
||||
height: calc(100vh - 220px);
|
||||
}
|
||||
}
|
||||
|
||||
.image-view-container {
|
||||
.image-view-body {
|
||||
&:hover .like-button {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.like-button {
|
||||
bottom: 10px !important;
|
||||
left: 10px !important;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
opacity: 0;
|
||||
font-size: 16px;
|
||||
color: @text-color;
|
||||
position: absolute;
|
||||
|
||||
// show zoom button on mobile devices
|
||||
@media (max-width: @screen-xs) {
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
|
||||
.image-view-body:hover .like-button {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.rating-area .star-icon {
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
@ -75,8 +75,8 @@ class Item(WebsiteGenerator):
|
||||
if not self.description:
|
||||
self.description = self.item_name
|
||||
|
||||
if self.is_sales_item and not self.get('is_item_from_hub'):
|
||||
self.publish_in_hub = 1
|
||||
# if self.is_sales_item and not self.get('is_item_from_hub'):
|
||||
# self.publish_in_hub = 1
|
||||
|
||||
def after_insert(self):
|
||||
'''set opening stock and item price'''
|
||||
|
Loading…
x
Reference in New Issue
Block a user