Hub (#10934)

* [WIP]Hub

* [listing] Show items, users and item_groups

* Show filters

* [start] cart, api for rfq and opp

* rfq working

* [wip] keys

* wip quotes

* [hub] register/unregister

* [hub] rename password to access_token, remove passed company field

* [hub] publishing cases, api call wrapper

* [hub] add and remove fields working

* [hub] fix flags, update on client save working

* [hub] new hub page, client item CUD at hub working

* listing, standard rate, local site hack

* item listing, item page, search, back to home

* [hub] implement hub company

* [hub] company filter

* [hub] basic rfq-ing, item page cleanup

* categories wip

* [hub] use get_doc_before_save()

* [hub] send opportunity message to hub, api to make locally

* [hub] enqueueing in hub api request wrapper

* cleanup

* [hub] refactor shopping cart's product.py to reuse

* sync dynamic item fields daily

* Scheduler heartbeat check

* [wip] hub categories

* [hub] wip enqueued callbacks

* [hub] outgoing messages, fixing callback loop

* [hub] bug: callback save after primary save

* [hub] pricing, stock, currency

* [hub] replace send_hub_request with make_and_enqueue

* add hub.less, refactor code

* Remove template html files, add styling for hub item cards

* fix paging

* add breadcrumb

* Add sidebar

* [hub] add company page, change country

* [hub] order_by filters

* [hub] make hub category a tree

* [hub] enqueue batched item enqueueing

* [hub] requested products page

* [minor]

* update hub url

* [fix] url

* [fix] more reform

* fix recursion

* [hub] data migration plans as jsons

* Hub register, create data connector, sync with run

* [add] user registration by session user

* Removed hub_message

* Remove sync code from hub_settings

* Remove hub methods from item.py

* Update Hub Sync plan

* Hub unregister

* Update Hub connector on reregister

* Dont delete Hub Connector on unregister

* Enable hub on success response

* Add new hub whitelisted methods

* [hub] list working

* Hub register from hub page

* [hub] Add hub logo in desk icon, link to page

* [hub] hide page head on empty state

* [hub] make rfq

* [hub] push opportunity doc, poll for opportunity docs

* add fields to item mapping

* update hub mappings

* Make RFQ

* [hub] item, home routing

* Make rfq and send opportunity refactor

* [hub][fix] remote lead data

* images passed as base64

* set default company on register

* Revert "images passed as base64"

This reverts commit 0b033a5fb7072b2d39a1b87a47dc41e7af707bb4.

* Add sync to hub page

* Prompt for publish items to hub

* add post process to hub document to lead

* Rename Hub document to Hub message, create opportunity in post process
diff --git a/erpnext/config/desktop.py b/erpnext/config/desktop.py
index 8fb66b1..6548591 100644
--- a/erpnext/config/desktop.py
+++ b/erpnext/config/desktop.py
@@ -127,7 +127,6 @@
 		{
 			"module_name": "POS",
 			"color": "#589494",
-			"icon": "fa fa-th",
 			"icon": "octicon octicon-credit-card",
 			"type": "page",
 			"link": "pos",
@@ -267,7 +266,15 @@
 			"color": "#FF888B",
 			"icon": "octicon octicon-plus",
 			"type": "module",
-			"label": _("Healthcare")
+			"label": _("Healthcare"),
+		},
+		{
+			"module_name": "Hub",
+			"color": "#009248",
+			"icon": "/assets/erpnext/images/hub_logo.svg",
+			"type": "page",
+			"link": "hub",
+			"label": _("Hub")
 		},
 		{
 			"module_name": "Data Import Tool",
diff --git a/erpnext/config/hub_node.py b/erpnext/config/hub_node.py
new file mode 100644
index 0000000..c9d5b97
--- /dev/null
+++ b/erpnext/config/hub_node.py
@@ -0,0 +1,24 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+	return [
+		{
+			"label": _("Setup"),
+			"items": [
+				{
+					"type": "doctype",
+					"name": "Hub Settings"
+				},
+			]
+		},
+		{
+			"label": _("Hub"),
+			"items": [
+				{
+					"type": "page",
+					"name": "hub"
+				},
+			]
+		},
+	]
\ No newline at end of file
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index a6dbd20..4e22757 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -209,6 +209,8 @@
 		"erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards",
 		"erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history",
 		"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
+		"erpnext.hub_node.doctype.hub_settings.hub_settings.sync_item_fields_at_hub",
+		"erpnext.hub_node.update_local_hub_categories"
 	]
 }
 
diff --git a/erpnext/hub/__init__.py b/erpnext/hub/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hub/__init__.py
diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py
index 1c97299..1756b76 100644
--- a/erpnext/hub_node/__init__.py
+++ b/erpnext/hub_node/__init__.py
@@ -2,16 +2,275 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-import frappe, requests
+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
 
 @frappe.whitelist()
-def get_items(text, start, limit):
-	hub = frappe.get_single("Hub Settings")
-	response = requests.get(hub.hub_url + "/api/method/hub.hub.api.get_items", params={
-		"access_token": hub.access_token,
-		"text": text,
-		"start": start,
-		"limit": limit
-	})
-	response.raise_for_status()
-	return response.json().get("message")
+def enable_hub():
+	hub_settings = frappe.get_doc('Hub Settings')
+	hub_settings.register()
+	frappe.db.commit()
+	return hub_settings
+
+@frappe.whitelist()
+def get_items(start=0, page_length=20):
+	connection = get_connection()
+	response = connection.connection.get_list('Hub Item', limit_start=start, limit_page_length=page_length)
+	return response
+
+@frappe.whitelist()
+def get_categories():
+	connection = get_connection()
+	response = connection.get_list('Hub Category')
+	return response.list
+
+@frappe.whitelist()
+def get_item_details(hub_sync_id):
+	connection = get_connection()
+	return connection.connection.get_doc('Hub Item', hub_sync_id)
+
+@frappe.whitelist()
+def get_company_details(hub_sync_id):
+	connection = get_connection()
+	return connection.connection.get_doc('Hub Company', hub_sync_id)
+
+def get_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 get_items(text='', by_item_codes=0, start=0, limit=20, order_by='', category=None, company_name=None, country=None):
+# 	item_codes = []
+# 	if cint(by_item_codes):
+# 		item_codes = [d["item_code"] for d in frappe.get_all("Item", fields=["item_code"], filters={"is_hub_item": "1"},
+# 			limit_start = start, limit_page_length = limit)]
+# 		if not item_codes:
+# 			return []
+
+# 	args = {
+# 		"text": text,
+# 		"item_codes": item_codes,
+# 		"category": category,
+# 		"company_name": company_name,
+# 		"country": country,
+# 		"order_by": order_by,
+# 		"start": start,
+# 		"limit": limit
+# 	}
+# 	return hub_request('get_items', data=json.dumps(args))
+
+# @frappe.whitelist()
+# def get_all_companies():
+# 	return hub_request('get_all_companies')
+
+# @frappe.whitelist()
+# def get_item_details(item_code):
+# 	args = {
+# 		"item_code": item_code,
+# 	}
+# 	return hub_request('get_item_details', data=json.dumps(args))
+
+# @frappe.whitelist()
+# def get_company_details(company_id):
+# 	args = {
+# 		"company_id": company_id,
+# 	}
+# 	return hub_request('get_company_details', data=json.dumps(args))
+
+# @frappe.whitelist()
+# def get_categories():
+# 	# update_local_hub_categories()
+# 	return hub_request('get_categories')
+
+# def update_local_hub_categories():
+# 	categories = get_categories()
+# 	categories_to_remove = []
+# 	categories_to_add = []
+# 	old_categories = frappe.db.sql_list("select category_name from from `tabHub Category`")
+# 	new_categories = [d.category_name for d in categories]
+# 	for old_category in old_categories:
+# 		if old_category not in new_categories:
+# 			categories_to_remove.append(old_category)
+
+# 	for new_category in new_categories:
+# 		if new_category not in old_categories:
+# 			categories_to_add.append(new_category)
+
+# 	for d in categories_to_remove:
+# 		docname = frappe.get_list('Hub Category', filters = {"category_name": d})[0]["name"]
+# 		frappe.delete_doc("Hub Category", docname)
+
+# 	for d in categories_to_add:
+# 		doc = frappe.new_doc("Hub Category")
+# 		doc.category_name = d
+# 		doc.save()
+
+
+# @frappe.whitelist()
+# def get_items_seen_states(items):
+# 	items = json.loads(items)
+# 	for d in items:
+# 		local_item_code = "HUB-" + d["item_code"]
+# 		if frappe.db.exists("Item", {"item_code": local_item_code}):
+# 			d["seen"] = 1
+# 		else:
+# 			d["seen"] = 0
+# 	return items
+
+# @frappe.whitelist()
+# def get_local_hub_item_codes():
+# 	item_codes = []
+# 	for d in frappe.get_all("Item", fields=["item_code"], filters={"is_hub_item": 1}):
+# 		item_codes.append(d["item_code"][4:])
+# 	return item_codes
+
+# @frappe.whitelist()
+# def hub_item_request_action(item):
+# 	item = json.loads(item)
+# 	rfq = make_rfq(item)
+# 	# , send click count and say requested
+# 	send_opportunity_details(supplier_name, supplier_email)
+# 	make_opportunities()
+# 	return rfq
+
+# def send_opportunity_details(supplier_name, supplier_email):
+# 	connection = get_connection()
+# 	params = {
+# 		"buyer_name": supplier_name,
+# 		"email_id": supplier_email
+# 	}
+# 	args = frappe._dict(dict(
+# 		doctype="Hub Document",
+# 		type="Opportunity",
+# 		document_data=json.dumps(params),
+# 		user=supplier_email
+# 	))
+# 	response = connection.insert("Hub Document", args)
+
+@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_type': supplier.supplier_type,
+			'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_hub_item': 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_connection()
+	response = connection.insert('Hub Message', args)
+
+	return response.ok
diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py
new file mode 100644
index 0000000..b32efd9
--- /dev/null
+++ b/erpnext/hub_node/api.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors and contributors
+# For license information, please see license.txt
+
+
+import frappe, json
+from frappe.utils import now, nowdate
+from erpnext.hub_node.doctype.hub_settings.hub_settings import get_hub_settings
+
+# API wrapper
+@frappe.whitelist(allow_guest=True)
+def call_method(access_token, method, message):
+	try:
+		args = json.loads(message)
+		if args:
+			return globals()[method](access_token, args)
+		else:
+			return globals()[method](access_token)
+	except:
+		print("Client Exception")
+		print(frappe.get_traceback())
+
+def disable_and_suspend_hub_user(access_token):
+	hub_settings = get_hub_settings()
+	hub_settings.publish = 0
+	hub_settings.publish_pricing = 0
+	hub_settings.publish_availability = 0
+	hub_settings.suspended = 1
+	hub_settings.enabled = 0
+	hub_settings.save(ignore_permissions=True)
diff --git a/erpnext/hub_node/data_migration_mapping/__init__.py b/erpnext/hub_node/data_migration_mapping/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hub_node/data_migration_mapping/__init__.py
diff --git a/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json b/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json
new file mode 100644
index 0000000..dac8fe6
--- /dev/null
+++ b/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json
@@ -0,0 +1,45 @@
+{
+ "condition": "{'name': ('=', frappe.db.get_single_value('Hub Settings', 'company'))}", 
+ "creation": "2017-09-07 11:38:43.169065", 
+ "docstatus": 0, 
+ "doctype": "Data Migration Mapping", 
+ "fields": [
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "name", 
+   "remote_fieldname": "company_name"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "country", 
+   "remote_fieldname": "country"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "\"city\"", 
+   "remote_fieldname": "seller_city"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "eval:frappe.local.site", 
+   "remote_fieldname": "site_name"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "eval:frappe.session.user", 
+   "remote_fieldname": "user"
+  }
+ ], 
+ "idx": 2, 
+ "local_doctype": "Company", 
+ "mapping_name": "Company to Hub Company", 
+ "mapping_type": "Push", 
+ "migration_id_field": "hub_sync_id", 
+ "modified": "2017-09-22 15:32:12.459172", 
+ "modified_by": "Administrator", 
+ "name": "Company to Hub Company", 
+ "owner": "Administrator", 
+ "page_length": 10, 
+ "remote_objectname": "Hub Company", 
+ "remote_primary_key": "name"
+}
\ No newline at end of file
diff --git a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/__init__.py b/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/__init__.py
new file mode 100644
index 0000000..79769ee
--- /dev/null
+++ b/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/__init__.py
@@ -0,0 +1,29 @@
+import frappe, json
+
+def pre_process(doc):
+	return json.loads(doc['data'])
+
+def post_process(remote_doc=None, local_doc=None, **kwargs):
+	if not local_doc:
+		return
+
+	hub_message = remote_doc
+	# update hub message on hub
+	hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
+	connection = hub_connector.get_connection()
+	connection.update('Hub Message', dict(
+		status='Synced'
+	), hub_message['name'])
+
+	# make opportunity after lead is created
+	lead = local_doc
+	opportunity = frappe.get_doc({
+		'doctype': 'Opportunity',
+		'naming_series': 'OPTY-',
+		'enquiry_type': 'Sales',
+		'enquiry_from': 'Lead',
+		'status': 'Open',
+		'lead': lead.name,
+		'company': lead.company,
+		'transaction_date': frappe.utils.today()
+	}).insert()
diff --git a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json b/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json
new file mode 100644
index 0000000..a0194ad
--- /dev/null
+++ b/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json
@@ -0,0 +1,31 @@
+{
+ "condition": "{'reference_doctype': 'Lead', 'user': frappe.db.get_single_value('Hub Settings', 'user'), 'status': 'Pending'}", 
+ "creation": "2017-09-20 15:06:40.279930", 
+ "docstatus": 0, 
+ "doctype": "Data Migration Mapping", 
+ "fields": [
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "email_id", 
+   "remote_fieldname": "email_id"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "lead_name", 
+   "remote_fieldname": "lead_name"
+  }
+ ], 
+ "idx": 0, 
+ "local_doctype": "Lead", 
+ "local_primary_key": "email_id", 
+ "mapping_name": "Hub Message to Lead", 
+ "mapping_type": "Pull", 
+ "migration_id_field": "hub_sync_id", 
+ "modified": "2017-09-28 13:21:41.575155", 
+ "modified_by": "faris@erpnext.com", 
+ "name": "Hub Message to Lead", 
+ "owner": "frappetest@gmail.com", 
+ "page_length": 10, 
+ "remote_objectname": "Hub Message", 
+ "remote_primary_key": "name"
+}
\ No newline at end of file
diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json
new file mode 100644
index 0000000..ef90918
--- /dev/null
+++ b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json
@@ -0,0 +1,54 @@
+{
+ "creation": "2017-09-07 13:27:52.726350", 
+ "docstatus": 0, 
+ "doctype": "Data Migration Mapping", 
+ "fields": [
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "item_code", 
+   "remote_fieldname": "item_code"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "item_name", 
+   "remote_fieldname": "item_name"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "eval:frappe.db.get_default(\"company\")", 
+   "remote_fieldname": "company_name"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "image", 
+   "remote_fieldname": "image"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "item_group", 
+   "remote_fieldname": "item_group"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "eval:frappe.session.user", 
+   "remote_fieldname": "seller"
+  }, 
+  {
+   "is_child_table": 0, 
+   "local_fieldname": "eval:frappe.db.get_default(\"country\")", 
+   "remote_fieldname": "country"
+  }
+ ], 
+ "idx": 1, 
+ "local_doctype": "Item", 
+ "mapping_name": "Item to Hub Item", 
+ "mapping_type": "Push", 
+ "migration_id_field": "hub_sync_id", 
+ "modified": "2017-09-22 15:32:12.674169", 
+ "modified_by": "Administrator", 
+ "name": "Item to Hub Item", 
+ "owner": "Administrator", 
+ "page_length": 10, 
+ "remote_objectname": "Hub Item", 
+ "remote_primary_key": "item_code"
+}
\ No newline at end of file
diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json
new file mode 100644
index 0000000..9edbac9
--- /dev/null
+++ b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json
@@ -0,0 +1,26 @@
+{
+ "creation": "2017-09-07 11:39:38.445902",
+ "docstatus": 0,
+ "doctype": "Data Migration Plan",
+ "idx": 1,
+ "mappings": [
+  {
+   "enabled": 1,
+   "mapping": "Company to Hub Company"
+  },
+  {
+   "enabled": 1,
+   "mapping": "Item to Hub Item"
+  },
+  {
+   "enabled": 1,
+   "mapping": "Hub Message to Lead"
+  }
+ ],
+ "modified": "2017-09-28 15:37:17.616828",
+ "modified_by": "faris@erpnext.com",
+ "module": "Hub Node",
+ "name": "Hub Sync",
+ "owner": "Administrator",
+ "plan_name": "Hub Sync"
+}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_category/__init__.py b/erpnext/hub_node/doctype/hub_category/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hub_node/doctype/hub_category/__init__.py
diff --git a/erpnext/hub_node/doctype/hub_category/hub_category.js b/erpnext/hub_node/doctype/hub_category/hub_category.js
new file mode 100644
index 0000000..9f54166
--- /dev/null
+++ b/erpnext/hub_node/doctype/hub_category/hub_category.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Hub Category', {
+	refresh: function(frm) {
+
+	}
+});
diff --git a/erpnext/hub_node/doctype/hub_category/hub_category.json b/erpnext/hub_node/doctype/hub_category/hub_category.json
new file mode 100644
index 0000000..4f8d66a
--- /dev/null
+++ b/erpnext/hub_node/doctype/hub_category/hub_category.json
@@ -0,0 +1,275 @@
+{
+ "allow_copy": 0, 
+ "allow_guest_to_view": 0, 
+ "allow_import": 0, 
+ "allow_rename": 0, 
+ "autoname": "field:hub_category_name", 
+ "beta": 0, 
+ "creation": "2017-08-22 11:31:10.410322", 
+ "custom": 0, 
+ "docstatus": 0, 
+ "doctype": "DocType", 
+ "document_type": "", 
+ "editable_grid": 1, 
+ "engine": "InnoDB", 
+ "fields": [
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "hub_category_name", 
+   "fieldtype": "Data", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 1, 
+   "in_standard_filter": 0, 
+   "label": "Category Name", 
+   "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": 1, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "parent_hub_category", 
+   "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": "Parent Category", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Hub Category", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "is_group", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Is Group", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "description", 
+   "fieldtype": "Text Editor", 
+   "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": "Description", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "lft", 
+   "fieldtype": "Int", 
+   "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": "Left", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "rgt", 
+   "fieldtype": "Int", 
+   "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": "Right", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "old_parent", 
+   "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": "Old Parent", 
+   "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, 
+   "unique": 0
+  }
+ ], 
+ "has_web_view": 0, 
+ "hide_heading": 0, 
+ "hide_toolbar": 0, 
+ "idx": 0, 
+ "image_view": 0, 
+ "in_create": 0, 
+ "is_submittable": 0, 
+ "issingle": 0, 
+ "istable": 0, 
+ "max_attachments": 0, 
+ "modified": "2017-09-03 22:04:22.958831", 
+ "modified_by": "Administrator", 
+ "module": "Hub Node", 
+ "name": "Hub Category", 
+ "name_case": "", 
+ "owner": "Administrator", 
+ "permissions": [
+  {
+   "amend": 0, 
+   "apply_user_permissions": 0, 
+   "cancel": 0, 
+   "create": 1, 
+   "delete": 1, 
+   "email": 1, 
+   "export": 1, 
+   "if_owner": 0, 
+   "import": 0, 
+   "permlevel": 0, 
+   "print": 1, 
+   "read": 1, 
+   "report": 1, 
+   "role": "System Manager", 
+   "set_user_permissions": 0, 
+   "share": 1, 
+   "submit": 0, 
+   "write": 1
+  }
+ ], 
+ "quick_entry": 1, 
+ "read_only": 0, 
+ "read_only_onload": 0, 
+ "show_name_in_global_search": 0, 
+ "sort_field": "modified", 
+ "sort_order": "DESC", 
+ "title_field": "hub_category_name", 
+ "track_changes": 1, 
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_category/hub_category.py b/erpnext/hub_node/doctype/hub_category/hub_category.py
new file mode 100644
index 0000000..5d19082
--- /dev/null
+++ b/erpnext/hub_node/doctype/hub_category/hub_category.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils.nestedset import NestedSet
+from frappe.model.document import Document
+
+class HubCategory(NestedSet):
+	pass
diff --git a/erpnext/hub_node/doctype/hub_category/hub_category_tree.js b/erpnext/hub_node/doctype/hub_category/hub_category_tree.js
new file mode 100644
index 0000000..d0309e3
--- /dev/null
+++ b/erpnext/hub_node/doctype/hub_category/hub_category_tree.js
@@ -0,0 +1,4 @@
+frappe.treeview_settings["Hub Category"] = {
+	title: __("Hub Category"),
+	breadcrumb: "Hub"
+}
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_category/test_hub_category.js b/erpnext/hub_node/doctype/hub_category/test_hub_category.js
new file mode 100644
index 0000000..73c06d5
--- /dev/null
+++ b/erpnext/hub_node/doctype/hub_category/test_hub_category.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Hub Category", function (assert) {
+	let done = assert.async();
+
+	// number of asserts
+	assert.expect(1);
+
+	frappe.run_serially([
+		// insert a new Hub Category
+		() => frappe.tests.make('Hub Category', [
+			// values to be set
+			{key: 'value'}
+		]),
+		() => {
+			assert.equal(cur_frm.doc.key, 'value');
+		},
+		() => done()
+	]);
+
+});
diff --git a/erpnext/hub_node/doctype/hub_category/test_hub_category.py b/erpnext/hub_node/doctype/hub_category/test_hub_category.py
new file mode 100644
index 0000000..7df2088
--- /dev/null
+++ b/erpnext/hub_node/doctype/hub_category/test_hub_category.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestHubCategory(unittest.TestCase):
+	pass
diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.js b/erpnext/hub_node/doctype/hub_settings/hub_settings.js
index 95b6d62..ce0f8bc 100644
--- a/erpnext/hub_node/doctype/hub_settings/hub_settings.js
+++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.js
@@ -1,17 +1,87 @@
-frappe.ui.form.on("Hub Settings", "onload", function(frm) {
-	if(!frm.doc.seller_country) {
-		frm.set_value("seller_country", frappe.defaults.get_default("Country"));
-	}
-	if(!frm.doc.seller_name) {
-		frm.set_value("seller_name", frappe.defaults.get_default("Company"));
-	}
-});
+frappe.ui.form.on("Hub Settings", {
+	refresh: function(frm) {
+		frm.add_custom_button(__('Logs'),
+			() => frappe.set_route('List', 'Data Migration Run', {
+				data_migration_plan: 'Hub Sync'
+			}));
 
-frappe.ui.form.on("Hub Settings", "refresh", function(frm) {
-	// make mandatory if published
-	frm.toggle_reqd(["seller_name", "seller_email", "seller_country"], frm.doc.publish);
-});
+		frm.trigger("enabled");
+		if (frm.doc.enabled) {
+			frm.add_custom_button(__('View Hub'),
+				() => frappe.set_route('hub'));
+			frm.add_custom_button(__('Sync'),
+				() => frm.call('sync'));
+		}
+	},
+	onload: function(frm) {
+		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"));
+		}
+	},
+	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();
+			});
+		}
+	},
 
-frappe.ui.form.on("Hub Settings", "publish", function(frm) {
-	frm.trigger("refresh");
+	hub_user_email: function(frm) {
+		if(frm.doc.hub_user_email){
+			frm.set_value("hub_user_name", frappe.user.full_name(frm.doc.hub_user_email));
+		}
+	},
+
+	set_enable_hub_primary_button: (frm) => {
+		frm.page.set_primary_action(__("Enable Hub"), () => {
+			if(frappe.session.user === "Administrator") {
+				frappe.msgprint("Please login as another user.")
+			} else {
+				frappe.verify_password(() => {
+					this.frm.call({
+						doc: this.frm.doc,
+						method: "register",
+						args: {},
+						freeze: true,
+						callback: function(r) {},
+						onerror: function() {
+							frappe.msgprint(__("Wrong Password"));
+							frm.set_value("enabled", 0);
+						}
+					});
+				} );
+			}
+		});
+	},
+
+	// update_hub: (frm) => {
+	// 	this.frm.call({
+	// 		doc: this.frm.doc,
+	// 		method: "update_hub",
+	// 		args: {},
+	// 		freeze: true,
+	// 		callback: function(r) { },
+	// 		onerror: function() { }
+	// 	});
+	// },
+
+	unregister_from_hub: (frm) => {
+		frappe.verify_password(() => {
+			var d = frappe.confirm(__('Are you sure you want to unregister?'), () => {
+				frm.call('unregister');
+			}, () => {}, __('Confirm Action'));
+			d.get_primary_btn().addClass("btn-danger");
+		});
+	},
 });
diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.json b/erpnext/hub_node/doctype/hub_settings/hub_settings.json
index 30cb816..b96d6b3 100644
--- a/erpnext/hub_node/doctype/hub_settings/hub_settings.json
+++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.json
@@ -1,29 +1,41 @@
 {
  "allow_copy": 0, 
+ "allow_guest_to_view": 0, 
  "allow_import": 0, 
  "allow_rename": 0, 
+ "beta": 1, 
  "creation": "2015-02-18 00:59:34.560476", 
  "custom": 0, 
+ "description": "", 
  "docstatus": 0, 
  "doctype": "DocType", 
- "document_type": "System", 
+ "document_type": "", 
+ "editable_grid": 0, 
  "fields": [
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
-   "fieldname": "publish", 
+   "columns": 0, 
+   "fieldname": "enabled", 
    "fieldtype": "Check", 
-   "hidden": 0, 
+   "hidden": 1, 
    "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 0, 
-   "label": "Publish Items to Hub", 
+   "in_standard_filter": 0, 
+   "label": "Enabled", 
+   "length": 0, 
    "no_copy": 0, 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
    "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
    "search_index": 0, 
@@ -31,43 +43,29 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
-   "depends_on": "publish", 
-   "fieldname": "section_break_2", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "publish_pricing", 
+   "columns": 0, 
+   "fieldname": "suspended", 
    "fieldtype": "Check", 
-   "hidden": 0, 
+   "hidden": 1, 
    "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 0, 
-   "label": "Publish Pricing", 
+   "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, 
@@ -75,132 +73,124 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
-   "fieldname": "publish_availability", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Publish Availability", 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "column_break_5", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "sync_now", 
-   "fieldtype": "Button", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Sync Now", 
-   "no_copy": 0, 
-   "options": "sync", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "depends_on": "publish", 
-   "fieldname": "section_break_6", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "seller_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Seller Name", 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "seller_country", 
+   "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, 
-   "label": "Seller Country", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 1, 
+   "collapsible_depends_on": "eval:(!doc.enabled)", 
+   "columns": 0, 
+   "depends_on": "", 
+   "fieldname": "seller_profile_section", 
+   "fieldtype": "Section Break", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Company and Seller Profile", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "company", 
+   "fieldtype": "Link", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 1, 
+   "in_standard_filter": 0, 
+   "label": "Company", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Company", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "country", 
+   "fieldtype": "Link", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 1, 
+   "in_standard_filter": 0, 
+   "label": "Country", 
+   "length": 0, 
    "no_copy": 0, 
    "options": "Country", 
    "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, 
@@ -208,131 +198,29 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
-   "fieldname": "seller_email", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Seller Email", 
-   "no_copy": 0, 
-   "options": "Email", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "column_break_10", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "seller_city", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Seller City", 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "fieldname": "seller_website", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Seller Website", 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "depends_on": "publish", 
-   "fieldname": "section_break_13", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "seller_description", 
    "fieldtype": "Text Editor", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 0, 
-   "label": "Seller Description", 
+   "in_standard_filter": 0, 
+   "label": "Description", 
+   "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, 
@@ -340,21 +228,30 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
-   "fieldname": "name_token", 
-   "fieldtype": "Data", 
-   "hidden": 1, 
+   "columns": 0, 
+   "depends_on": "enabled", 
+   "fieldname": "publish_section", 
+   "fieldtype": "Section Break", 
+   "hidden": 0, 
    "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 0, 
-   "label": "Name Token", 
+   "in_standard_filter": 0, 
+   "label": "Publish", 
+   "length": 0, 
    "no_copy": 0, 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
-   "read_only": 1, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
    "search_index": 0, 
@@ -362,21 +259,216 @@
    "unique": 0
   }, 
   {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
-   "fieldname": "access_token", 
-   "fieldtype": "Data", 
-   "hidden": 1, 
+   "columns": 0, 
+   "fieldname": "publish", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
    "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
    "in_filter": 0, 
+   "in_global_search": 0, 
    "in_list_view": 0, 
-   "label": "Access Token", 
+   "in_standard_filter": 0, 
+   "label": "Publish Items to Hub", 
+   "length": 0, 
    "no_copy": 0, 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "depends_on": "publish", 
+   "fieldname": "publish_pricing", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Publish Pricing", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "depends_on": "eval:(doc.publish && doc.publish_pricing)", 
+   "fieldname": "selling_price_list", 
+   "fieldtype": "Link", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Selling Price List", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Price List", 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "depends_on": "publish", 
+   "fieldname": "publish_availability", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Publish Availability", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "depends_on": "publish", 
+   "fieldname": "last_sync_datetime", 
+   "fieldtype": "Datetime", 
+   "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": "Last Sync On", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "collapsible_depends_on": "", 
+   "columns": 0, 
+   "depends_on": "enabled", 
+   "fieldname": "unregister_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": "Unregister", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "unregister_from_hub", 
+   "fieldtype": "Button", 
+   "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": "Unregister from Hub", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
    "search_index": 0, 
@@ -384,15 +476,18 @@
    "unique": 0
   }
  ], 
+ "has_web_view": 0, 
  "hide_heading": 0, 
  "hide_toolbar": 0, 
+ "idx": 0, 
+ "image_view": 0, 
  "in_create": 0, 
- "in_dialog": 0, 
  "is_submittable": 0, 
  "issingle": 1, 
  "istable": 0, 
- "modified": "2015-02-18 08:14:46.140473", 
- "modified_by": "Administrator", 
+ "max_attachments": 0, 
+ "modified": "2017-09-21 12:13:50.841646", 
+ "modified_by": "manas@erpnext.com", 
  "module": "Hub Node", 
  "name": "Hub Settings", 
  "name_case": "", 
@@ -419,8 +514,12 @@
    "write": 1
   }
  ], 
+ "quick_entry": 0, 
  "read_only": 0, 
  "read_only_onload": 0, 
+ "show_name_in_global_search": 0, 
  "sort_field": "modified", 
- "sort_order": "DESC"
+ "sort_order": "DESC", 
+ "track_changes": 1, 
+ "track_seen": 0
 }
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_settings/hub_settings.py b/erpnext/hub_node/doctype/hub_settings/hub_settings.py
index 35f1f67..7e54b65 100644
--- a/erpnext/hub_node/doctype/hub_settings/hub_settings.py
+++ b/erpnext/hub_node/doctype/hub_settings/hub_settings.py
@@ -3,94 +3,103 @@
 
 from __future__ import unicode_literals
 import frappe, requests, json
-from frappe.model.document import Document
-from frappe.utils import cint, expand_relative_urls
-from frappe import _
 
+from frappe.model.document import Document
+from frappe.utils import add_years, now, get_datetime, get_datetime_str
+from frappe import _
+from erpnext.utilities.product import get_price, get_qty_in_stock
+from six import string_types
+
+hub_url = "http://erpnext.hub:8000"
+# hub_url = "https://hub.erpnext.org"
+# hub_url = "http://192.168.29.145:3000"
+
+class HubSetupError(frappe.ValidationError): pass
 
 class HubSettings(Document):
-	hub_url = "http://localhost:8001"
-	def validate(self):
-		if cint(self.publish):
-			if not self.name_token:
-				self.register()
-			else:
-				self.update_seller_details()
-			self.publish_selling_items()
-		else:
-			if self.name_token:
-				self.unpublish()
 
-	def publish_selling_items(self):
-		"""Set `publish_in_hub`=1 for all Sales Items"""
-		for item in frappe.get_all("Item", fields=["name"],
-			filters={ "publish_in_hub": "0"}):
-			frappe.db.set_value("Item", item.name, "publish_in_hub", 1)
+	def validate(self):
+		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
+
+	def sync(self):
+		"""Create and execute Data Migration Run for Hub Sync plan"""
+		frappe.has_permission('Hub Settings', throw=True)
+
+		doc = frappe.get_doc({
+			'doctype': 'Data Migration Run',
+			'data_migration_plan': 'Hub Sync',
+			'data_migration_connector': 'Hub Connector'
+		}).insert()
+
+		doc.run()
 
 	def register(self):
-		"""Register at hub.erpnext.com, save `name_token` and `access_token`"""
-		response = requests.post(self.hub_url + "/api/method/hub.hub.api.register", data=self.get_args())
-		response.raise_for_status()
-		response = response.json()
-		self.name_token = response.get("message").get("name")
-		self.access_token = response.get("message").get("access_token")
-
-	def unpublish(self):
-		"""Unpublish from hub.erpnext.com"""
-		response = requests.post(self.hub_url + "/api/method/hub.hub.api.unpublish", data={
-			"access_token": self.access_token
-		})
-		response.raise_for_status()
-
-	def update_seller_details(self):
-		"""Update details at hub.erpnext.com"""
-		args = self.get_args()
-		args["published"] = 1
-		response = requests.post(self.hub_url + "/api/method/hub.hub.api.update_seller", data={
-			"access_token": self.access_token,
-			"args": json.dumps(args)
-		})
-		response.raise_for_status()
-
-	def get_args(self):
-		return {
-			"seller_name": self.seller_name,
-			"seller_country": self.seller_country,
-			"seller_city": self.seller_city,
-			"seller_email": self.seller_email,
-			"seller_website": self.seller_website,
-			"seller_description": self.seller_description
+		""" Create a User on hub.erpnext.org and return username/password """
+		data = {
+			'email': frappe.session.user
 		}
+		post_url = hub_url + '/api/method/hub.hub.api.register'
 
-	def sync(self, verbose=True):
-		"""Sync items with hub.erpnext.com"""
-		if not self.publish:
-			if verbose:
-				frappe.msgprint(_("Publish to sync items"))
+		response = requests.post(post_url, data=data)
+		response.raise_for_status()
+		message = response.json().get('message')
+
+		if message and message.get('password'):
+			self.user = frappe.session.user
+			self.create_hub_connector(message)
+			self.company = frappe.defaults.get_user_default('company')
+			self.enabled = 1
+			self.save()
+
+	def unregister(self):
+		""" Disable the User on hub.erpnext.org"""
+
+		hub_connector = frappe.get_doc(
+			'Data Migration Connector', 'Hub Connector')
+
+		connection = hub_connector.get_connection()
+		response = connection.update('User', frappe._dict({'enabled': 0}), hub_connector.username)
+
+		if response.ok:
+			self.enabled = 0
+			self.save()
+
+	def create_hub_connector(self, message):
+		if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
+			hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
+			hub_connector.username = message['email']
+			hub_connector.password = message['password']
+			hub_connector.save()
 			return
 
-		items = frappe.db.get_all("Item",
-			fields=["name", "item_name", "description", "image", "item_group"],
-			filters={"publish_in_hub": 1, "synced_with_hub": 0})
+		frappe.get_doc({
+			'doctype': 'Data Migration Connector',
+			'connector_type': 'Frappe',
+			'connector_name': 'Hub Connector',
+			'hostname': hub_url,
+			'username': message['email'],
+			'password': message['password']
+		}).insert()
 
-		for item in items:
-			item.item_code = item.name
-			if item.image:
-				item.image = expand_relative_urls(item.image)
+def reset_hub_publishing_settings(last_sync_datetime = ""):
+	doc = frappe.get_doc("Hub Settings", "Hub Settings")
+	doc.reset_publishing_settings(last_sync_datetime)
+	doc.in_callback = 1
+	doc.save()
 
-		item_list = frappe.db.sql_list("select name from tabItem where publish_in_hub=1")
+def reset_hub_settings(last_sync_datetime = ""):
+	doc = frappe.get_doc("Hub Settings", "Hub Settings")
+	doc.reset_publishing_settings(last_sync_datetime)
+	doc.reset_enable()
+	doc.in_callback = 1
+	doc.save()
+	frappe.msgprint(_("Successfully unregistered."))
 
-		if items:
-			response = requests.post(self.hub_url + "/api/method/hub.hub.api.sync", data={
-				"access_token": self.access_token,
-				"items": json.dumps(items),
-				"item_list": json.dumps(item_list)
-			})
-			response.raise_for_status()
-			for item in items:
-				frappe.db.set_value("Item", item.name, "synced_with_hub", 1)
-			if verbose:
-				frappe.msgprint(_("{0} Items synced".format(len(items))))
-		else:
-			if verbose:
-				frappe.msgprint(_("Items already synced"))
+@frappe.whitelist()
+def sync():
+	hub_settings = frappe.get_doc('Hub Settings')
+	hub_settings.sync()
\ No newline at end of file
diff --git a/erpnext/hub_node/doctype/hub_settings/test_hub_settings.js b/erpnext/hub_node/doctype/hub_settings/test_hub_settings.js
new file mode 100644
index 0000000..546ce15
--- /dev/null
+++ b/erpnext/hub_node/doctype/hub_settings/test_hub_settings.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Hub Settings", function (assert) {
+	let done = assert.async();
+
+	// number of asserts
+	assert.expect(1);
+
+	frappe.run_serially('Hub Settings', [
+		// insert a new Hub Settings
+		() => frappe.tests.make([
+			// values to be set
+			{key: 'value'}
+		]),
+		() => {
+			assert.equal(cur_frm.doc.key, 'value');
+		},
+		() => done()
+	]);
+
+});
diff --git a/erpnext/hub_node/doctype/hub_settings/test_hub_settings.py b/erpnext/hub_node/doctype/hub_settings/test_hub_settings.py
new file mode 100644
index 0000000..1299adc
--- /dev/null
+++ b/erpnext/hub_node/doctype/hub_settings/test_hub_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestHubSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/hub_node/page/hub/hub.js b/erpnext/hub_node/page/hub/hub.js
index ad09b9d..7f34a5a 100644
--- a/erpnext/hub_node/page/hub/hub.js
+++ b/erpnext/hub_node/page/hub/hub.js
@@ -1,90 +1,870 @@
+/* globals Hub, HubList */
+
+frappe.provide('erpnext.hub');
+
 frappe.pages['hub'].on_page_load = function(wrapper) {
-	var page = frappe.ui.make_app_page({
+	const page = frappe.ui.make_app_page({
 		parent: wrapper,
 		title: 'Hub',
-		single_column: true
+		single_col: false
 	});
 
-	frappe.hub = new frappe.Hub({page:page});
+	erpnext.hub.Hub = new Hub({ page });
 
-}
+};
 
-frappe.pages['hub'].on_page_show = function() {
-	frappe.hub.refresh();
-}
-
-frappe.Hub = Class.extend({
-	init: function(args) {
-		$.extend(this, args);
-		this.render();
-	},
-	refresh: function() {
-		if(this.hub && this.hub.publish && !this.hub_list) {
-			this.setup_list();
+frappe.pages['hub'].on_page_show = function(wrapper) {
+	const current_route = frappe.get_route();
+	if(current_route[1] === "Products") {
+		const item_code = current_route[2];
+		if(item_code) {
+			erpnext.hub.Hub.go_to_item_page(item_code);
 		}
-	},
-	render: function() {
-		this.page.main.empty();
-		var me = this;
-		frappe.model.with_doc("Hub Settings", "Hub Settings", function() {
-			me.hub = locals["Hub Settings"]["Hub Settings"];
-			if(!me.hub.publish) {
-				$(frappe.render_template("register_in_hub", {})).appendTo(me.page.main);
+	}
+
+	if(current_route[1] === "Company") {
+		const company_name = current_route[2];
+		if(company_name) {
+			erpnext.hub.Hub.go_to_company_page(company_name);
+		}
+	}
+}
+
+window.Hub = class Hub {
+	constructor({ page }) {
+		this.page = page;
+		frappe.require('/assets/erpnext/css/hub.css', () => {
+			this.setup();
+		});
+	}
+
+	setup() {
+		this.setup_header();
+		this.company_cache = {};
+		this.item_cache = {};
+		this.filters = {};
+		this.order_by = '';
+
+		this.$hub_main_section =
+			$(`<div class='hub-main-section'>`).appendTo(this.page.body);
+		this.bind_events();
+		this.refresh();
+	}
+
+	refresh() {
+		this.$hub_main_section.empty();
+		this.page.page_form.hide();
+
+		const $layout_main = this.page.wrapper.find('.layout-main');
+		const $page_head = this.page.wrapper.find('.page-head');
+
+		frappe.model.with_doc('Hub Settings', 'Hub Settings', () => {
+			this.hub_settings = frappe.get_doc('Hub Settings');
+
+			if(this.hub_settings.enabled == 0) {
+				let $empty_state = this.page.get_empty_state(
+					__("Register for Hub"),
+					__(`Let other ERPNext users discover your products
+						and automate workflow with Supplier from within ERPNext.`),
+					__("Register")
+				);
+
+				$page_head.hide();
+				$layout_main
+					.find('.layout-side-section, .layout-main-section-wrapper')
+					.hide();
+				$layout_main.append($empty_state);
+
+				$empty_state.find('.btn-primary').on('click', () => {
+					this.register_for_hub();
+				});
 			} else {
-				me.setup_list();
+				$page_head.show();
+				$layout_main.find('.page-card-container').remove();
+				$layout_main.find('.layout-side-section, .layout-main-section-wrapper').show();
+				this.setup_live_state();
 			}
 		});
-	},
-	setup_list: function() {
-		var me = this;
-		$(frappe.render_template("hub_body", {})).appendTo(this.page.main);
-		this.hub_list = this.page.main.find(".hub-list");
-		this.search = this.page.main.find("input").on("keypress", function(e) {
-			if(e.which===13) {
-				me.reset();
-			}
+	}
+
+	register_for_hub() {
+		frappe.verify_password(() => {
+			frappe.call({
+				method: 'erpnext.hub_node.enable_hub',
+				callback: (r) => {
+					if(r.message.enabled == 1) {
+						Object.assign(this.hub_settings, r.message);
+						this.refresh();
+						this.prompt_for_item_sync();
+					}
+				}
+			});
 		});
-		this.loading = this.page.main.find(".loading");
-		this.done = this.page.main.find(".done");
-		this.more = this.page.main.find(".more")
-		this.more.find(".btn").on("click", function() { me.next_page() });
-		this.reset();
-	},
-	reset: function() {
-		this.hub_list.empty();
-		this.start = 0;
-		this.page_length = 20;
-		this.next_page();
-	},
-	next_page: function() {
-		var me = this;
-		this.loading.toggleClass("hide", false);
+	}
+
+	prompt_for_item_sync() {
 		frappe.call({
-			method: "erpnext.hub_node.get_items",
+			method: 'frappe.client.get_list',
 			args: {
-				text: this.get_text(),
-				start: this.start,
-				limit: this.page_length
+				doctype: 'Data Migration Run',
+				filters: {
+					'data_migration_plan': 'Hub Sync'
+				},
+				limit_page_length: 1
 			},
 			callback: function(r) {
-				me.loading.toggleClass("hide", true);
-				if(!r.message)
-					r.message = [];
-				me.start += r.message.length;
-				$(frappe.render_template("hub_list", {items: r.message})).appendTo(me.hub_list);
-				if(r.message.length && r.message.length===me.page_length) {
-					// more
-					me.more.removeClass("hide");
-					me.done.addClass("hide");
-				} else {
-					// done
-					me.more.addClass("hide");
-					me.done.removeClass("hide");
+				if (!r) {
+					frappe.confirm(__('Do you want to publish your Items to Hub ?'), () => {
+						this.sync_items_to_hub();
+					});
 				}
 			}
+		})
+	}
+
+	setup_header() {
+		this.page.page_title = this.page.wrapper.find('.page-title');
+		this.tag_line = $(`
+			<div class='tag-line-container'>
+				<span class='tag-line text-muted small'>
+					${__('Product listing and discovery for ERPNext users')}
+				</span>
+			</div>`)
+			.appendTo(this.page.page_title);
+
+		this.bind_title();
+	}
+
+	setup_live_state() {
+		if(!this.$search) {
+			this.setup_filters();
+		}
+		this.page.page_form.show();
+		this.setup_menu();
+		this.setup_sidebar();
+		this.render_body();
+		this.setup_lists();
+	}
+
+	setup_filters() {
+
+		// frappe.call({
+		// 	method: 'erpnext.hub_node.get_categories'
+		// }).then((r) => {
+		// 	if (r.message) {
+		// 		const categories = r.message;
+		// 		console.log("categories", categories);
+		// 		categories
+		// 			.map(c => c.hub_category_name)
+		// 			.map(c => this.sidebar.add_item({
+		// 				label: c,
+		// 				on_click: () => {
+		// 					this.home_item_list &&
+		// 					this.home_item_list.refresh({
+		// 						text: '',
+		// 						start: 0,
+		// 						limit: 20,
+		// 						category: c && c !== 'All Categories' ? c : undefined
+		// 					});
+		// 				}
+		// 			}, __('Hub Category')));
+
+
+		// 	}
+		// });
+
+		// this.category_select = this.page.add_select(__('Category'),
+		// 	[
+		// 		{label: __('Sort by Price ...'), value: '' },
+		// 		{label: __('High to Low'), value: 'price desc' },
+		// 		{label: __('Low to High'), value: 'price' },
+		// 	]
+		// );
+
+		this.price_sort_select = this.page.add_select(__('Sort by Price'),
+			[
+				{label: __('Sort by Price ...'), value: '' },
+				{label: __('High to Low'), value: 'price desc' },
+				{label: __('Low to High'), value: 'price' },
+			]
+		);
+
+		this.criteria_select = this.page.add_select(__('Sort by Criteria'),
+			[
+				{label: __('Most Popular'), value: 'request_count' },
+				{label: __('Newest'), value: 'creation' },
+			]
+		);
+
+		this.price_sort_select.on('change', () => {
+			this.refresh_item_only_page();
 		});
-	},
-	get_text: function() {
-		return this.search.val();
-	},
-})
+
+		this.criteria_select.on('change', () => {
+			this.refresh_item_only_page();
+		});
+
+		this.$search = this.page.add_data(__('Search'));
+		this.setup_search();
+	}
+
+	bind_events() {
+		const me = this;
+		this.$hub_main_section
+			.on('click', '.company-link a', function(e) {
+				e.preventDefault();
+				const company_name = $(this).attr('data-company-name');
+				me.get_company_details(company_name);
+			})
+			.on('click', '.breadcrumb li', function(e) {
+				e.preventDefault();
+				const $li = $(this);
+				if ($li.attr('data-route') === 'Home') {
+					me.go_to_home_page();
+				}
+			});
+	}
+
+	update_filters() {
+		let price_sort = $(this.price_sort_select).val() || '';
+		let criteria = $(this.criteria_select).val() || '';
+
+		let order_by_params = [];
+		let query_string = '';
+		if(criteria) {
+			order_by_params.push(criteria);
+			// query_string += 'sort_by=' + criteria
+		}
+		if(price_sort) order_by_params.push(price_sort);
+		this.order_by = order_by_params.join(",");
+		// return query_string;
+	}
+
+	reset_filters() {
+		this.order_by = '';
+		$(this.category_select).val('');
+		$(this.price_sort_select).val('');
+		$(this.criteria_select).val('Most Popular');
+	}
+
+	refresh_item_only_page() {
+		this.reset_search();
+		this.update_filters();
+		this.go_to_items_only_page(
+			['hub', 'Products'],
+			'', 'product-list'
+		);
+	}
+
+	bind_title() {
+		this.page.page_title.find('.title-text').on('click', () => {
+			this.go_to_home_page();
+		});
+	}
+
+	render_body() {
+		this.$home_page = $(`
+			<div class = 'hub-home-page'>
+				<div class='banner'></div>
+				<div class='listing-body row'>
+					<div class='main-list-section'></div>
+				</div>
+			</div>
+		`).appendTo(this.$hub_main_section);
+
+		this.$banner = this.$hub_main_section.find('.banner');
+		this.$listing_body = this.$hub_main_section.find('.listing-body');
+		this.$main_list_section = this.$hub_main_section.find('.main-list-section');
+		this.$side_list_section = this.$hub_main_section.find('.side-list-section');
+	}
+
+	setup_lists() {
+		this.home_item_list = new HubList({
+			parent: this.$main_list_section,
+			title: 'New',
+			page_length: 20,
+			list_css_class: 'home-product-list',
+			method: 'erpnext.hub_node.get_items',
+			// order_by: 'request_count',
+			filters: {text: '', country: this.country}, // filters at the time of creation
+			on_item_click: (item_code) => {
+				frappe.set_route('hub', 'Products', item_code);
+			}
+		});
+
+		this.home_item_list.setup();
+	}
+
+	setup_search() {
+		this.$search.on('keypress', (e) => {
+			if(e.which === 13) {
+				var search_term = ($(this.$search).val() || '').toLowerCase();
+				this.go_to_items_only_page(
+					['hub', 'search', search_term],
+					'Search results for \''  + search_term + '\'',
+					'search-product-list',
+					{text: search_term}
+				);
+			}
+		});
+	}
+
+	go_to_items_only_page(route, title, class_name, filters = {text: ''}, by_item_codes=0) {
+		frappe.set_route(route);
+		this.$hub_main_section.empty();
+		this.filtered_item_list = new HubList({
+			parent: this.$hub_main_section,
+			title: title,
+			page_length: 20,
+			list_css_class: class_name,
+			method: 'erpnext.hub_node.get_items',
+			order_by: this.order_by,
+			filters: filters,
+			by_item_codes: by_item_codes
+		});
+		this.filtered_item_list.on_item_click = (item_code) => {
+			frappe.set_route('hub', 'Products', item_code);
+		}
+		this.filtered_item_list.setup();
+	}
+
+	go_to_item_page(item_code) {
+		if(this.item_cache) {
+			let item = this.item_cache[item_code];
+			if(item) {
+				this.render_item_page(item);
+				return;
+			}
+		} else {
+			this.item_cache = {};
+		}
+		frappe.call({
+			args:{
+				hub_sync_id: item_code
+			},
+			method: "erpnext.hub_node.get_item_details",
+			callback: (r) => {
+				let item = r.message;
+				this.item_cache[item_code] = item;
+				this.render_item_page(item);
+			}
+		});
+	}
+
+	render_item_page(item) {
+		this.$hub_main_section.empty();
+
+
+		let $item_page =
+			$(this.get_item_page(item))
+				.appendTo(this.$hub_main_section);
+
+		let $company_items = $item_page.find('.company-items');
+
+		let company_item_list = new HubList({
+			parent: $company_items,
+			title: 'More by ' + item.company_name,
+			page_length: 5,
+			list_css_class: 'company-item-list',
+			method: 'erpnext.hub_node.get_items',
+			// order_by: 'request_count',
+			filters: {text: '', company_name: item.company_name, country: this.country},
+			paginated: 0,
+			img_size: 150
+		});
+
+		company_item_list.on_item_click = (item_code) => {
+			frappe.set_route('hub', 'Products', item_code);
+		}
+		company_item_list.setup();
+
+		$item_page.find('.rfq-btn')
+			.click((e) => {
+				const $btn = $(e.target);
+
+				this.show_rfq_modal(item)
+					.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, $btn);
+					})
+					.then(r => {
+						console.log(r);
+						if (r.message && r.message.rfq) {
+							$btn.addClass('disabled').html(`<span><i class='fa fa-check'></i> ${__('Quote Requested')}</span>`);
+						} else {
+							throw r;
+						}
+					})
+					.catch((e) => {
+						console.log(e); //eslint-disable-line
+					});
+			});
+	}
+
+	show_rfq_modal(item) {
+		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 Type'), fieldname: 'supplier_type',
+					fieldtype: 'Link', options: 'Supplier Type' }
+			];
+			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();
+		});
+	}
+
+	get_company_details(company_id) {
+		// get from cache if exists
+		let company_details = this.company_cache[company_id];
+		if(this.company_cache[company_id]) {
+			this.go_to_company_page(company_details);
+			return;
+		}
+		frappe.call({
+			method: 'erpnext.hub_node.get_company_details',
+			args: {company_id: company_id}
+		}).then((r) => {
+			if (r.message) {
+				const company_details = r.message.company_details;
+				this.company_cache[company_id] = company_details;
+				this.go_to_company_page(company_details)
+			}
+		});
+	}
+
+	go_to_company_page(company_details) {
+		frappe.set_route('hub', 'Company', company_details.company_name);
+		this.$hub_main_section.empty();
+
+		let $company_page =
+			$(this.get_company_page(company_details))
+				.appendTo(this.$hub_main_section);
+
+		let $company_items = $company_page.find('.company-items');
+
+		let company_item_list = new HubList({
+			parent: $company_items,
+			title: 'More by ' + company_details.company_name,
+			page_length: 5,
+			list_css_class: 'company-item-list',
+			method: 'erpnext.hub_node.get_items',
+			// order_by: 'request_count',
+			filters: {text: '', company: company_details.company_name, country: this.country},
+			paginated: 0,
+			img_size: 150
+		});
+
+		company_item_list.on_item_click = (item_code) => {
+			frappe.set_route('hub', 'Products', item_code);
+		}
+		company_item_list.setup();
+	}
+
+	get_item_page(item) {
+		return `
+			<div class="hub-item-page">
+				<div class="item-header">
+					<div class="item-page-image">
+						${ this.home_item_list.get_item_image(item) }
+					</div>
+					<div class="title-content">
+						<div class="breadcrumbs">
+							${this.get_breadcrumb(item.item_name, "Products") }
+						</div>
+						<div class="title">
+							<h2>${ item.item_name }</h2>
+						</div>
+						<div class="company">
+							<span class="">${ item.company_name }</span>
+						</div>
+						<div class="category">
+							<span class="text-muted">Products</span>
+						</div>
+						<div class="description">
+							<span class="small">${ item.description ? item.description : "" }</span>
+						</div>
+						<div class="price">
+							${ item.formatted_price ? item.formatted_price : '' }
+						</div>
+						<div class="actions">
+							<a class="btn btn-primary rfq-btn">Request A Quote</a>
+						</div>
+					</div>
+
+				</div>
+				<div class="item-more-info"></div>
+				<div class="company-items">
+
+				</div>
+			</div>
+		`;
+	}
+
+	get_company_page(company_details) {
+		return `
+			<div class="hub-item-page">
+				<div class="item-header">
+					<div class="title-content">
+						<div class="breadcrumbs">
+							${this.get_breadcrumb(company_details.company_name, "Company") }
+						</div>
+						<div class="title">
+							<h2>${ company_details.company_name }</h2>
+						</div>
+						<div class="company">
+							<span class="">${ company_details.seller_city }</span>
+						</div>
+						<div class="description">
+							<span class="small">${ company_details.seller_description }</span>
+						</div>
+					</div>
+
+				</div>
+				<div class="item-more-info"></div>
+				<div class="company-items">
+
+				</div>
+			</div>
+		`;
+	}
+
+	get_breadcrumb(name, type) {
+		return `
+			<ul class="breadcrumb">
+				<li data-route="Home">
+					<a href><span>Home</span></a>
+				</li>
+				<li data-route="List">
+					<a href><span>${type}</span></a>
+				</li>
+				<li class="active">
+					<span>${name}</span>
+				</li>
+			</ul>
+		`;
+	}
+
+	go_to_home_page() {
+		frappe.set_route('hub');
+		this.reset_filters();
+		this.refresh();
+	}
+
+	setup_menu() {
+		this.page.add_menu_item(__('Hub Settings'),
+			() => frappe.set_route('Form', 'Hub Settings'));
+
+		this.page.add_menu_item(__('Refresh'), () => this.refresh());
+
+		this.page.add_menu_item(__('Sync'), () => this.sync_items_to_hub());
+	}
+
+	sync_items_to_hub() {
+		frappe.call('erpnext.hub_node.doctype.hub_settings.hub_settings.sync')
+	}
+
+	setup_sidebar() {
+		var me = this;
+		this.sidebar = new HubSidebar({
+			wrapper: this.page.wrapper.find('.layout-side-section')
+		});
+
+		this.add_account_to_sidebar();
+
+	}
+
+	add_account_to_sidebar() {
+		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: __("Requested Products"),
+			on_click: () => this.go_to_seen_items()
+		}, __("Account"));
+	}
+
+	get_search_term() {
+		return this.$search.val();
+	}
+
+	reset_search() {
+		this.$search.val('');
+	}
+
+	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);
+		});
+	}
+
+	go_to_seen_items() {
+		this.go_to_items_only_page(
+			['hub', 'Requested Products'],
+			__('Requested Products'),
+			'requested-product-list',
+			{}, 1
+		);
+	}
+}
+
+class HubList {
+	constructor({
+		parent = null,
+		title = 'Products',
+		page_length = 20,
+		list_css_class = '',
+		method = 'erpnext.hub_node.get_items',
+		filters = {text: ''},
+		order_by = '',
+		by_item_codes = 0,
+		paginated = 1,
+		on_item_click = null,
+		img_size = 200
+	}) {
+		this.parent = parent;
+		this.title = title;
+		this.page_length = page_length;
+		this.list_css_class = list_css_class;
+		this.method = method;
+		this.filters = filters;
+		this.order_by = order_by;
+		this.by_item_codes = by_item_codes;
+		this.paginated = paginated;
+
+		this.on_item_click = on_item_click;
+		this.img_size = img_size;
+	}
+
+	// to be called on demand
+	setup() {
+		this.container = $(`
+			<div class='item-list-container ${this.list_css_class}' data-page-length='${this.page_length}'>
+				<div class='item-list-header'>
+					<h3>${this.title}</h3>
+				</div>
+				<div class='item-list'></div>
+				<div class='list-state'>
+					<div class='loading'>
+						<p class='text-muted text-center'>${__('Loading...')}</p>
+					</div>
+					<div class='done hide'>
+						<p class='text-muted text-center'>${__('No more results')}</p>
+					</div>
+					<div class='more text-right'>
+						<button class='btn btn-default btn-sm'>${__('More')}</div>
+					</div>
+				</div>
+			</div>`)
+			.appendTo(this.parent);
+
+		this.$item_list_title = this.container.find('.item-list-header h3');
+		this.$list = this.container.find('.item-list');
+		this.$loading = this.container.find('.loading').hide();
+		this.$more = this.container.find('.more').hide();
+		this.$done = this.container.find('.done');
+
+		this.$more.on('click', () => {
+			this.next_page();
+		});
+
+		this.next_page();
+	}
+
+	refresh(filters = this.filters) {
+		this.reset();
+		this.set_filters(filters);
+		this.next_page();
+	}
+
+	reset() {
+		this.$list.empty();
+	}
+
+	set_filters(filters) {
+		this.filters = filters;
+	}
+
+	next_page() {
+		this.$item_list_title.html(this.title);
+		const start = this.$list.find('.hub-item-wrapper').length;
+		this.$loading.show();
+
+		// build args
+		let args = {
+			start: start,
+			// query one extra
+			limit: this.page_length + 1
+		};
+		Object.assign(args, this.filters);
+		console.log("filters: ", args);
+		args.order_by = this.order_by;
+		args.by_item_codes = this.by_item_codes;
+
+		frappe.call({
+			method: this.method,
+			args: args,
+			callback: (r) => {
+				let items = r.message;
+				console.log("items: ", items);
+				this.render_items(items);
+			}
+		});
+	}
+
+	render_items(items) {
+		if(items) {
+			let done = 0;
+			console.log("items length", items.length);
+			if(items.length && items.length > this.page_length) {
+				// remove the extra queried
+				items.pop();
+			} else {
+				done = 1;
+			}
+			items.forEach((item) => {
+				this.make_item_card(item).appendTo(this.$list);
+			});
+			console.log(done);
+			this.update_list_state(done);
+		} else {
+			this.$item_list_title.html('No results found');
+		}
+	}
+
+	update_list_state(done=0) {
+		this.$loading.hide();
+		if(done) {
+			this.$done.removeClass('hide');
+			this.$more.hide();
+		} else {
+			this.$more.show();
+			this.$done.addClass('hide');
+		}
+	}
+
+	make_item_card(item) {
+		let $item_card = $(`
+			<div class="hub-item-wrapper" style="max-width: ${this.img_size}px;">
+				<a class="item-link" href>
+					<div class="hub-item-image">
+						${ this.get_item_image(item) }
+					</div>
+					<div class="hub-item-title">
+						<h5 class="bold">
+							${!item.seen ? item.item_name : `<span class="indicator blue">${item.item_name}</span>`}
+						<h5>
+					</div>
+				</a>
+				<div class="company-link">
+					<a data-company-name="${ item.company_id }" class="">${ item.company_name }</a>
+				</div>
+				<div>${ item.formatted_price ? item.formatted_price : ''}</div>
+			</div>
+		`);
+
+		$item_card.find(".item-link").click((e) => {
+			e.preventDefault();
+			this.on_item_click && this.on_item_click(item.name);
+		});
+
+		return $item_card;
+	}
+
+	get_item_image(item, size=this.img_size) {
+		const _size = size + 'px';
+		const item_image = item.image ?
+			`<img src="${item.image}"><span class="helper"></span>` :
+			`<div class="standard-image">${item.item_name[0]}</div>`;
+
+		return `
+			<div class="img-wrapper"
+				style="max-width: ${_size}; width: ${_size}; height: ${_size};">
+				${item_image}
+			</div>`;
+	}
+}
+
+class HubSidebar {
+	constructor({ wrapper }) {
+		this.wrapper = wrapper;
+		this.make_dom();
+	}
+
+	make_dom() {
+		this.wrapper.html(`
+			<div class="hub-sidebar overlay-sidebar hidden-xs hidden-sm">
+			</div>
+		`);
+
+		this.$sidebar = this.wrapper.find('.hub-sidebar');
+	}
+
+	add_item(item, section) {
+		let $section;
+		if(!section && this.wrapper.find('.sidebar-menu').length === 0) {
+			// if no section, add section with no heading
+			$section = this.get_section();
+		} else {
+			$section = this.get_section(section);
+		}
+
+		const $li_item = $(`
+			<li><a ${item.href ? `href="${item.href}"` : ''}>${item.label}</a></li>
+		`).click(
+			() => item.on_click && item.on_click()
+		);
+
+		$section.append($li_item);
+	}
+
+	get_section(section_heading="") {
+		let $section = $(this.wrapper.find(
+			`[data-section-heading="${section_heading}"]`));
+		if($section.length) {
+			return $section;
+		}
+
+		const $section_heading = section_heading ?
+			`<li class="h6">${section_heading}</li>` : '';
+
+		$section = $(`
+			<ul class="list-unstyled sidebar-menu" data-section-heading="${section_heading || 'default'}">
+				${$section_heading}
+			</ul>
+		`);
+
+		this.$sidebar.append($section);
+		return $section;
+	}
+}
\ No newline at end of file
diff --git a/erpnext/hub_node/page/hub/hub_body.html b/erpnext/hub_node/page/hub/hub_body.html
deleted file mode 100644
index e415f7e..0000000
--- a/erpnext/hub_node/page/hub/hub_body.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<div class="padding">
-    <div class="row">
-        <div class="col-md-4 col-md-offset-4">
-            <input class="form-control" name="search" placeholder="{%= __("Search") %}">
-        </div>
-    </div>
-    <hr style="margin: 15px -15px">
-    <div class="hub-list">
-
-    </div>
-    <div class="loading">
-        <p class="text-muted text-center">{%= __("Loading...") %}</p>
-    </div>
-    <div class="more text-center hide">
-        <button class="btn btn-default btn-sm">{%= __("More") %}</div>
-    </div>
-    <div class="done text-center text-extra-muted hide">
-        <p>{%= __("No more results.") %}</p>
-    </div>
-</div>
diff --git a/erpnext/hub_node/page/hub/hub_list.html b/erpnext/hub_node/page/hub/hub_list.html
deleted file mode 100644
index 036ef2b..0000000
--- a/erpnext/hub_node/page/hub/hub_list.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% for(var i=0, l=items.length; i < l; i++) { %}
-<div class="list-item">
-    <div class="row">
-        <div class="col-sm-6">
-            <h6>{%= items[i].item_name %}<h6>
-        </div>
-        <div class="col-sm-3">
-            <h6>{%= items[i].seller_name %}<h6>
-        </div>
-        <div class="col-sm-3 text-right">
-            <h6 class="text-muted">{%= items[i].seller_country %}<h6>
-        </div>
-    </div>
-    <p class="text-muted small">{%= items[i].description %}</p>
-</div>
-{% } %}
diff --git a/erpnext/hub_node/page/hub/register_in_hub.html b/erpnext/hub_node/page/hub/register_in_hub.html
deleted file mode 100644
index 96b1fb3..0000000
--- a/erpnext/hub_node/page/hub/register_in_hub.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<div style="padding: 70px 0px;">
-    <h2 class="text-center">{%= __("Register For ERPNext Hub") %}</h2>
-    <br>
-    <div class="row">
-        <div class="col-md-6 col-md-offset-3">
-            <ul>
-                <li>Free listing of your products</li>
-                <li>Let other ERPNext users discover your products</li>
-                <li>Discover products quickly</li>
-                <li>Automate workflow with Supplier from within ERPNext (later)</li>
-            </ul>
-        </div>
-    </div>
-    <br>
-    <div class="text-center">
-        <a class="btn btn-primary" href="#Form/Hub Settings">
-            Hub Settings</a>
-    </div>
-</div>
diff --git a/erpnext/public/css/hub.css b/erpnext/public/css/hub.css
new file mode 100644
index 0000000..559c203
--- /dev/null
+++ b/erpnext/public/css/hub.css
@@ -0,0 +1,104 @@
+/* hub */
+div[data-page-route="hub"] .page-head {
+  height: 80px;
+}
+div[data-page-route="hub"] .page-head .title-text {
+  cursor: pointer;
+}
+div[data-page-route="hub"] .page-content {
+  margin-top: 80px;
+}
+div[data-page-route="hub"] .page-title h1 {
+  margin-bottom: 0px;
+}
+div[data-page-route="hub"] .account-details {
+  margin-top: 20px;
+}
+div[data-page-route="hub"] [data-original-title="Search"] {
+  float: right;
+  width: 220px;
+}
+div[data-page-route="hub"] .hub-main-section {
+  padding: 30px;
+}
+div[data-page-route="hub"] .listing-body {
+  margin: 0;
+}
+div[data-page-route="hub"] .main-list-section {
+  padding: 0;
+}
+div[data-page-route="hub"] .side-list-section {
+  padding: 0;
+}
+div[data-page-route="hub"] .item-list-header h3 {
+  font-weight: normal;
+}
+div[data-page-route="hub"] .hub-item-page h2 {
+  margin-top: 10px;
+}
+div[data-page-route="hub"] .hub-item-page .item-header {
+  display: flex;
+}
+div[data-page-route="hub"] .hub-item-page .item-page-image {
+  flex: 1;
+}
+div[data-page-route="hub"] .hub-item-page .title-content {
+  flex: 3;
+}
+div[data-page-route="hub"] .hub-item-page .title-content .description {
+  margin: 30px 0px;
+}
+div[data-page-route="hub"] .hub-item-page .title-content .actions {
+  margin-top: 30px;
+}
+div[data-page-route="hub"] .hub-item-page .title-content .actions .rfq-btn.disabled {
+  background-color: #b1bdca;
+  color: #fff;
+  border-color: #b1bdca;
+}
+div[data-page-route="hub"] .hub-item-page .company-items {
+  margin-top: 40px;
+}
+div[data-page-route="hub"] .company-header {
+  display: flex;
+}
+div[data-page-route="hub"] .item-list {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+div[data-page-route="hub"] .hub-item-wrapper {
+  margin-bottom: 20px;
+}
+div[data-page-route="hub"] .img-wrapper {
+  border: 1px solid #d1d8dd;
+  border-radius: 3px;
+  padding: 12px;
+  overflow: hidden;
+  text-align: center;
+  white-space: nowrap;
+}
+div[data-page-route="hub"] .img-wrapper img {
+  max-width: 100%;
+  max-height: 100%;
+  display: inline-block;
+  vertical-align: middle;
+}
+div[data-page-route="hub"] .img-wrapper .helper {
+  height: 100%;
+  display: inline-block;
+  vertical-align: middle;
+}
+div[data-page-route="hub"] .img-wrapper .standard-image {
+  font-size: 72px;
+  border: none;
+  background-color: #fafbfc;
+}
+div[data-page-route="hub"] .hub-item-title {
+  width: 100%;
+}
+div[data-page-route="hub"] .breadcrumb {
+  padding-left: 0;
+  padding-top: 0;
+  margin-bottom: 10px;
+}
diff --git a/erpnext/public/images/hub_logo.svg b/erpnext/public/images/hub_logo.svg
new file mode 100644
index 0000000..2363090
--- /dev/null
+++ b/erpnext/public/images/hub_logo.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="svg3718"
+   xml:space="preserve"
+   width="162.12097"
+   height="162.3004"
+   viewBox="0 0 162.12098 162.3004"
+   sodipodi:docname="hub_logo.svg"
+   inkscape:version="0.92.2 5c3e80d, 2017-08-06"><metadata
+     id="metadata3724"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+     id="defs3722"><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath3734"><path
+         d="M 0,600 H 1000 V 0 H 0 Z"
+         id="path3732"
+         inkscape:connector-curvature="0" /></clipPath></defs><sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="740"
+     inkscape:window-height="449"
+     id="namedview3720"
+     showgrid="false"
+     inkscape:zoom="1.1278353"
+     inkscape:cx="108.86803"
+     inkscape:cy="50.640564"
+     inkscape:window-x="605"
+     inkscape:window-y="98"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="g3726"
+     fit-margin-top="10"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="10" /><g
+     id="g3726"
+     inkscape:groupmode="layer"
+     inkscape:label="hub logo"
+     transform="matrix(1.3333333,0,0,-1.3333333,-579.98759,522.65266)"><g
+       id="g3728"><g
+         id="g3730"
+         clip-path="url(#clipPath3734)"><g
+           id="g3740"
+           transform="translate(469.3467,296.2959)"><path
+             d="m 0,0 c -16.597,0 -31.587,12.581 -32.298,13.186 l -2.058,1.751 1.131,2.452 c 0.44,0.956 11.012,23.476 30.001,27.662 l 1.527,0.336 C 10.532,48.083 22.675,40.327 25.37,28.098 28.065,15.871 20.307,3.728 8.081,1.033 L 6.551,0.698 C 4.36,0.214 2.167,0 0,0"
+             style="fill:#6be273;fill-opacity:1;fill-rule:nonzero;stroke:none"
+             id="path3742"
+             inkscape:connector-curvature="0" /></g><g
+           id="g3744"
+           transform="translate(519.9316,324.9663)"><path
+             d="m 0,0 c -8.052,0 -16.033,3.513 -21.457,10.266 -4.599,5.724 -6.689,12.896 -5.892,20.196 0.797,7.297 4.39,13.85 10.111,18.446 l 1.524,1.225 c 7.78,6.245 18.871,9.487 32.081,9.388 9.773,-0.076 17.263,-1.928 17.578,-2.006 l 2.621,-0.657 0.074,-2.699 C 36.675,52.862 37.313,22.21 18.708,7.269 L 17.184,6.044 C 12.118,1.977 6.044,0 0,0"
+             style="fill:#49c44c;fill-opacity:1;fill-rule:nonzero;stroke:none"
+             id="path3746"
+             inkscape:connector-curvature="0" /></g><g
+           id="g3748"
+           transform="translate(533.21,367.6562)"><path
+             d="m 0,0 c -5.312,-1.335 -13.328,-3.353 -20.459,-7.646 -8.907,-5.364 -13.925,-12.827 -14.923,-22.183 l -0.02,-0.188 v -14.901 c -3.873,3.824 -8.159,6.202 -12.809,7.101 -12.143,2.341 -23.14,-5.967 -29.049,-10.433 l 4.296,-5.686 c 5.024,3.797 14.373,10.864 23.402,9.122 5.207,-1.005 9.965,-4.887 14.16,-11.54 v -33.538 h 7.128 v 59.49 c 1.878,16.413 20.17,21.016 30.01,23.492 z"
+             style="fill:#cef6d1;fill-opacity:1;fill-rule:nonzero;stroke:none"
+             id="path3750"
+             inkscape:connector-curvature="0" /></g></g></g></g></svg>
\ No newline at end of file
diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less
index d0c4841..262b0c3 100644
--- a/erpnext/public/less/erpnext.less
+++ b/erpnext/public/less/erpnext.less
@@ -438,4 +438,4 @@
 	.list-item_content {
 		padding-right: 45px;
 	}
-}
\ No newline at end of file
+}
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
new file mode 100644
index 0000000..1cae692
--- /dev/null
+++ b/erpnext/public/less/hub.less
@@ -0,0 +1,140 @@
+@import "../../../../frappe/frappe/public/less/variables.less";
+
+/* hub */
+div[data-page-route="hub"] {
+	.page-head {
+		height: 80px;
+
+		.title-text {
+			cursor: pointer;
+		}
+	}
+
+	.page-content {
+		margin-top: 80px;
+	}
+
+	.page-title h1 {
+		margin-bottom: 0px;
+	}
+
+	.account-details {
+		margin-top: 20px;
+	}
+
+	[data-original-title="Search"] {
+		float: right;
+		width: 220px;
+	}
+
+	.hub-main-section {
+		padding: 30px;
+	}
+
+	.listing-body {
+		margin: 0;
+	}
+
+	.main-list-section {
+		padding: 0;
+		// border-right: 1px solid #d1d8dd;
+	}
+
+	.side-list-section {
+		padding: 0;
+	}
+
+	.item-list-header h3 {
+		font-weight: normal;
+	}
+
+	.hub-item-page {
+
+		h2 {
+			margin-top: 10px;
+		}
+
+		.item-header {
+			display: flex;
+		}
+
+		.item-page-image {
+			flex: 1;
+		}
+
+		.title-content {
+			flex: 3;
+
+			.description {
+				margin: 30px 0px;
+			}
+
+			.actions {
+				margin-top: 30px;
+
+				.rfq-btn.disabled {
+					background-color: #b1bdca;
+					color: #fff;
+					border-color: #b1bdca;
+				}
+			}
+		}
+
+		.company-items {
+			margin-top: 40px;
+		}
+	}
+
+	.company-header {
+		display: flex;
+	}
+
+	.item-list {
+		display: flex;
+		flex-wrap: wrap;
+		justify-content: space-between;
+	}
+
+	.hub-item-wrapper {
+		margin-bottom: 20px;
+	}
+
+	.img-wrapper {
+		border: 1px solid @border-color;
+		border-radius: 3px;
+		padding: 12px;
+		overflow: hidden;
+		text-align: center;
+		white-space: nowrap;
+
+		img {
+			max-width: 100%;
+			max-height: 100%;
+			display: inline-block;
+			vertical-align: middle;
+		}
+
+		.helper {
+			height: 100%;
+			display: inline-block;
+			vertical-align: middle;
+		}
+
+		.standard-image {
+			font-size: 72px;
+			border: none;
+			background-color: @light-bg;
+		}
+	}
+
+	.hub-item-title {
+		width: 100%;
+	}
+
+	.breadcrumb {
+		padding-left: 0;
+		padding-top: 0;
+		margin-bottom: 10px;
+	}
+
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index ec83705..f1b10a9 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -194,461 +194,461 @@
    "search_index": 0,
    "set_only_once": 0,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "sales_settings", 
-   "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": "Sales", 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "sales_settings",
+   "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": "Sales",
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "sales_monthly_history", 
-   "fieldtype": "Small Text", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Sales Monthly History", 
-   "length": 0, 
-   "no_copy": 1, 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "sales_monthly_history",
+   "fieldtype": "Small Text",
+   "hidden": 1,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Sales Monthly History",
+   "length": 0,
+   "no_copy": 1,
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "monthly_sales_target", 
-   "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": "Monthly Sales Target", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "default_currency", 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "monthly_sales_target",
+   "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": "Monthly Sales Target",
+   "length": 0,
+   "no_copy": 0,
+   "options": "default_currency",
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_goals", 
-   "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, 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "column_break_goals",
+   "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,
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "total_monthly_sales", 
-   "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": "Total Monthly Sales", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "default_currency", 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "total_monthly_sales",
+   "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": "Total Monthly Sales",
+   "length": 0,
+   "no_copy": 1,
+   "options": "default_currency",
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "charts_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": "Default Values", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "charts_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": "Default Values",
+   "length": 0,
+   "no_copy": 0,
+   "permlevel": 0,
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "default_letter_head", 
-   "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": "Default Letter Head", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Letter Head", 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "default_letter_head",
+   "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": "Default Letter Head",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Letter Head",
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "default_holiday_list", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Default Holiday List", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Holiday List", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "default_holiday_list",
+   "fieldtype": "Link",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Default Holiday List",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Holiday List",
+   "permlevel": 0,
+   "precision": "",
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 0,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 0,
+   "search_index": 0,
+   "set_only_once": 0,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "default_terms", 
-   "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": "Default Terms", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Terms and Conditions", 
-   "permlevel": 0, 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "default_terms",
+   "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": "Default Terms",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Terms and Conditions",
+   "permlevel": 0,
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "default_currency", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 1, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Default Currency", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Currency", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "default_currency",
+   "fieldtype": "Link",
+   "hidden": 0,
+   "ignore_user_permissions": 1,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 0,
+   "in_standard_filter": 0,
+   "label": "Default Currency",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Currency",
+   "permlevel": 0,
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 0,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 1,
+   "search_index": 0,
+   "set_only_once": 0,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_10", 
-   "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, 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "column_break_10",
+   "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,
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "country", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Country", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Country", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "country",
+   "fieldtype": "Link",
+   "hidden": 0,
+   "ignore_user_permissions": 0,
+   "ignore_xss_filter": 0,
+   "in_filter": 0,
+   "in_global_search": 0,
+   "in_list_view": 1,
+   "in_standard_filter": 0,
+   "label": "Country",
+   "length": 0,
+   "no_copy": 0,
+   "options": "Country",
+   "permlevel": 0,
+   "print_hide": 0,
+   "print_hide_if_no_value": 0,
+   "read_only": 0,
+   "remember_last_selected_value": 0,
+   "report_hide": 0,
+   "reqd": 1,
+   "search_index": 0,
+   "set_only_once": 0,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "create_chart_of_accounts_based_on", 
-   "fieldtype": "Select", 
-   "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": "Create Chart Of Accounts Based On", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "\nStandard Template\nExisting Company", 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "fieldname": "create_chart_of_accounts_based_on",
+   "fieldtype": "Select",
+   "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": "Create Chart Of Accounts Based On",
+   "length": 0,
+   "no_copy": 0,
+   "options": "\nStandard Template\nExisting Company",
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", 
-   "fieldname": "chart_of_accounts", 
-   "fieldtype": "Select", 
-   "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": "Chart Of Accounts Template", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "", 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"",
+   "fieldname": "chart_of_accounts",
+   "fieldtype": "Select",
+   "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": "Chart Of Accounts Template",
+   "length": 0,
+   "no_copy": 1,
+   "options": "",
+   "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,
    "unique": 0
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"", 
-   "fieldname": "existing_company", 
-   "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": "Existing Company ", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "Company", 
-   "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, 
+   "allow_bulk_edit": 0,
+   "allow_on_submit": 0,
+   "bold": 0,
+   "collapsible": 0,
+   "columns": 0,
+   "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"",
+   "fieldname": "existing_company",
+   "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": "Existing Company ",
+   "length": 0,
+   "no_copy": 1,
+   "options": "Company",
+   "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,
    "unique": 0
   },
   {
@@ -1978,24 +1978,24 @@
    "set_only_once": 0,
    "unique": 0
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "icon": "fa fa-building", 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "menu_index": 0, 
- "modified": "2017-08-31 11:48:56.278568", 
- "modified_by": "Administrator", 
- "module": "Setup", 
- "name": "Company", 
- "owner": "Administrator", 
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "icon": "fa fa-building",
+ "idx": 1,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "menu_index": 0,
+ "modified": "2017-09-06 15:08:44.360880",
+ "modified_by": "mohan@annapurna.com",
+ "module": "Setup",
+ "name": "Company",
+ "owner": "Administrator",
  "permissions": [
   {
    "amend": 0,
diff --git a/erpnext/shopping_cart/product.py b/erpnext/shopping_cart/product.py
deleted file mode 100644
index 0d6eccd..0000000
--- a/erpnext/shopping_cart/product.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-
-import frappe
-from frappe.utils import cint, fmt_money, flt
-from erpnext.shopping_cart.cart import _get_cart_quotation
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings \
-	import is_cart_enabled, get_shopping_cart_settings, show_quantity_in_website
-from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item
-
-@frappe.whitelist(allow_guest=True)
-def get_product_info(item_code):
-	"""get product price / stock info"""
-	if not is_cart_enabled():
-		return {}
-
-	qty = 0
-	cart_quotation = _get_cart_quotation()
-	template_item_code = frappe.db.get_value("Item", item_code, "variant_of")
-	stock_status = get_qty_in_stock(item_code, template_item_code)
-	in_stock = stock_status.in_stock
-	stock_qty = stock_status.stock_qty
-	price = get_price(item_code, template_item_code, cart_quotation.selling_price_list)
-
-	if price:
-		price["formatted_price"] = fmt_money(price["price_list_rate"], currency=price["currency"])
-
-		price["currency"] = not cint(frappe.db.get_default("hide_currency_symbol")) \
-			and (frappe.db.get_value("Currency", price.currency, "symbol") or price.currency) \
-			or ""
-
-		if frappe.session.user != "Guest":
-			item = cart_quotation.get({"item_code": item_code})
-			if item:
-				qty = item[0].qty
-
-	return {
-		"price": price,
-		"stock_qty": stock_qty,
-		"in_stock": in_stock,
-		"uom": frappe.db.get_value("Item", item_code, "stock_uom"),
-		"qty": qty,
-		"show_stock_qty": show_quantity_in_website()
-	}
-
-def get_qty_in_stock(item_code, template_item_code):
-	in_stock, stock_qty = 0, ''
-	warehouse = frappe.db.get_value("Item", item_code, "website_warehouse")
-	if not warehouse and template_item_code and template_item_code != item_code:
-		warehouse = frappe.db.get_value("Item", template_item_code, "website_warehouse")
-
-	if warehouse:
-		stock_qty = frappe.db.sql("""select actual_qty from tabBin where
-			item_code=%s and warehouse=%s""", (item_code, warehouse))
-		if stock_qty:
-			in_stock = stock_qty[0][0] > 0 and 1 or 0
-
-	return frappe._dict({"in_stock": in_stock, "stock_qty": stock_qty})
-
-def get_price(item_code, template_item_code, price_list, qty=1):
-	if price_list:
-		cart_settings = get_shopping_cart_settings()
-
-		price = frappe.get_all("Item Price", fields=["price_list_rate", "currency"],
-			filters={"price_list": price_list, "item_code": item_code})
-
-		if template_item_code and not price:
-			price = frappe.get_all("Item Price", fields=["price_list_rate", "currency"],
-				filters={"price_list": price_list, "item_code": template_item_code})
-
-		if price:
-			pricing_rule = get_pricing_rule_for_item(frappe._dict({
-				"item_code": item_code,
-				"qty": qty,
-				"transaction_type": "selling",
-				"price_list": price_list,
-				"customer_group": cart_settings.default_customer_group,
-				"company": cart_settings.company,
-				"conversion_rate": 1,
-				"for_shopping_cart": True
-			}))
-
-			if pricing_rule:
-				if pricing_rule.pricing_rule_for == "Discount Percentage":
-					price[0].price_list_rate = flt(price[0].price_list_rate * (1.0 - (flt(pricing_rule.discount_percentage) / 100.0)))
-
-				if pricing_rule.pricing_rule_for == "Price":
-					price[0].price_list_rate = pricing_rule.price_list_rate
-
-			return price[0]
diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py
new file mode 100644
index 0000000..8015c48
--- /dev/null
+++ b/erpnext/shopping_cart/product_info.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from erpnext.shopping_cart.cart import _get_cart_quotation
+from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings \
+	import is_cart_enabled, get_shopping_cart_settings, show_quantity_in_website
+from erpnext.utilities.product import get_price, get_qty_in_stock
+
+@frappe.whitelist(allow_guest=True)
+def get_product_info_for_website(item_code):
+	"""get product price / stock info for website"""
+	if not is_cart_enabled():
+		return {}
+
+	cart_quotation = _get_cart_quotation()
+	cart_settings = get_shopping_cart_settings()
+
+	price = get_price(
+		item_code,
+		cart_quotation.selling_price_list,
+		cart_settings.default_customer_group,
+		cart_settings.company
+	)
+
+	stock_status = get_qty_in_stock(item_code, "website_warehouse")
+
+	product_info = {
+		"price": price,
+		"stock_qty": stock_status.stock_qty,
+		"in_stock": stock_status.in_stock,
+		"qty": 0,
+		"uom": frappe.db.get_value("Item", item_code, "stock_uom"),
+		"show_stock_qty": show_quantity_in_website()
+	}
+
+	if product_info["price"]:
+		if frappe.session.user != "Guest":
+			item = cart_quotation.get({"item_code": item_code})
+			if item:
+				product_info["qty"] = item[0].qty
+
+	return product_info
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 03b93c0..40b80c7 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -63,7 +63,7 @@
 			frm.page.set_inner_btn_group_as_primary(__("Make"));
 		}
 		if (frm.doc.variant_of) {
-			frm.set_intro(__('This Item is a Variant of {0} (Template).', 
+			frm.set_intro(__('This Item is a Variant of {0} (Template).',
 				[`<a href="#Form/Item/${frm.doc.variant_of}">${frm.doc.variant_of}</a>`]), true);
 		}
 
@@ -127,7 +127,7 @@
 		if(!frm.doc.description)
 			frm.set_value("description", frm.doc.item_code);
 	},
-	
+
 	is_stock_item: function(frm) {
 		if(!frm.doc.is_stock_item) {
 			frm.set_value("has_batch_no", 0);
@@ -135,7 +135,7 @@
 			frm.set_value("has_serial_no", 0);
 		}
 	},
-	
+
 	copy_from_item_group: function(frm) {
 		return frm.call({
 			doc: frm.doc,
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 525321c..7b46b0a 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -240,6 +240,68 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "is_hub_item", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Is Hub Item", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "depends_on": "is_hub_item", 
+   "fieldname": "hub_category", 
+   "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": "Hub Category", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Hub Category", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
    "description": "", 
    "fieldname": "stock_uom", 
    "fieldtype": "Link", 
@@ -3191,6 +3253,163 @@
    "search_index": 0, 
    "set_only_once": 0, 
    "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "depends_on": "eval:(!doc.is_hub_item)", 
+   "fieldname": "hub_publishing_sb", 
+   "fieldtype": "Section Break", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Hub Publishing Details", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "default": "0", 
+   "description": "Publish Item to hub.erpnext.com", 
+   "fieldname": "publish_in_hub", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Publish in Hub", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "hub_category_to_publish", 
+   "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": "Hub Category to Publish", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Hub Category", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "description": "Publish \"In Stock\" or \"Not in Stock\" on Hub based on stock available in this warehouse.", 
+   "fieldname": "hub_warehouse", 
+   "fieldtype": "Link", 
+   "hidden": 0, 
+   "ignore_user_permissions": 1, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Hub Warehouse", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Warehouse", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "default": "0", 
+   "fieldname": "synced_with_hub", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Synced With Hub", 
+   "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, 
+   "unique": 0
   }
  ], 
  "has_web_view": 0, 
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index a810665..3f71db8 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -52,7 +52,8 @@
 		if not self.description:
 			self.description = self.item_name
 
-		self.publish_in_hub = 1
+		if self.is_sales_item and not self.is_hub_item:
+			self.publish_in_hub = 1
 
 	def after_insert(self):
 		'''set opening stock and item price'''
@@ -63,6 +64,10 @@
 			self.set_opening_stock()
 
 	def validate(self):
+		self.before_update = None
+		if frappe.db.exists('Item', self.name):
+			self.before_update = frappe.get_doc('Item', self.name)
+
 		super(Item, self).validate()
 
 		if not self.item_name:
@@ -815,4 +820,3 @@
 
 	if not matched:
 		frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item))
-
diff --git a/erpnext/stock/doctype/item/test_item.js b/erpnext/stock/doctype/item/test_item.js
new file mode 100644
index 0000000..af44278
--- /dev/null
+++ b/erpnext/stock/doctype/item/test_item.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Item", function (assert) {
+	let done = assert.async();
+
+	// number of asserts
+	assert.expect(1);
+
+	frappe.run_serially([
+		// insert a new Item
+		() => frappe.tests.make('Item', [
+			// values to be set
+			{key: 'value'}
+		]),
+		() => {
+			assert.equal(cur_frm.doc.key, 'value');
+		},
+		() => done()
+	]);
+
+});
diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js
index b2d5ad9..93dadaa 100644
--- a/erpnext/templates/includes/product_page.js
+++ b/erpnext/templates/includes/product_page.js
@@ -7,7 +7,7 @@
 
 	frappe.call({
 		type: "POST",
-		method: "erpnext.shopping_cart.product.get_product_info",
+		method: "erpnext.shopping_cart.product_info.get_product_info_for_website",
 		args: {
 			item_code: get_item_code()
 		},
diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py
new file mode 100644
index 0000000..1ad8b6e
--- /dev/null
+++ b/erpnext/utilities/product.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from frappe.utils import cint, fmt_money, flt
+from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item
+
+def get_qty_in_stock(item_code, item_warehouse_field):
+	in_stock, stock_qty = 0, ''
+	template_item_code = frappe.db.get_value("Item", item_code, "variant_of")
+
+	warehouse = frappe.db.get_value("Item", item_code, item_warehouse_field)
+	if not warehouse and template_item_code and template_item_code != item_code:
+		warehouse = frappe.db.get_value("Item", template_item_code, item_warehouse_field)
+
+	if warehouse:
+		stock_qty = frappe.db.sql("""select actual_qty from tabBin where
+			item_code=%s and warehouse=%s""", (item_code, warehouse))
+		if stock_qty:
+			in_stock = stock_qty[0][0] > 0 and 1 or 0
+
+	return frappe._dict({"in_stock": in_stock, "stock_qty": stock_qty})
+
+def get_price(item_code, price_list, customer_group, company, qty=1):
+	template_item_code = frappe.db.get_value("Item", item_code, "variant_of")
+
+	if price_list:
+		price = frappe.get_all("Item Price", fields=["price_list_rate", "currency"],
+			filters={"price_list": price_list, "item_code": item_code})
+
+		if template_item_code and not price:
+			price = frappe.get_all("Item Price", fields=["price_list_rate", "currency"],
+				filters={"price_list": price_list, "item_code": template_item_code})
+
+		if price:
+			pricing_rule = get_pricing_rule_for_item(frappe._dict({
+				"item_code": item_code,
+				"qty": qty,
+				"transaction_type": "selling",
+				"price_list": price_list,
+				"customer_group": customer_group,
+				"company": company,
+				"conversion_rate": 1,
+				"for_shopping_cart": True
+			}))
+
+			if pricing_rule:
+				if pricing_rule.pricing_rule_for == "Discount Percentage":
+					price[0].price_list_rate = flt(price[0].price_list_rate * (1.0 - (flt(pricing_rule.discount_percentage) / 100.0)))
+
+				if pricing_rule.pricing_rule_for == "Price":
+					price[0].price_list_rate = pricing_rule.price_list_rate
+
+			price_obj = price[0]
+			if price_obj:
+				price_obj["formatted_price"] = fmt_money(price_obj["price_list_rate"], currency=price_obj["currency"])
+
+				price_obj["currency_symbol"] = not cint(frappe.db.get_default("hide_currency_symbol")) \
+					and (frappe.db.get_value("Currency", price_obj.currency, "symbol") or price_obj.currency) \
+					or ""
+
+				if not price_obj["price_list_rate"]:
+					price_obj["price_list_rate"] = 0
+
+				if not price_obj["currency"]:
+					price_obj["currency"] = ""
+
+				if not price_obj["formatted_price"]:
+					price_obj["formatted_price"] = ""
+
+			return price_obj