Merge pull request #15303 from frappe/hub-multiuser

Hub Multiuser
This commit is contained in:
Prateeksha Singh 2018-09-03 18:57:41 +05:30 committed by GitHub
commit 76f2430152
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 627 additions and 623 deletions

View File

@ -1,11 +1,61 @@
from __future__ import unicode_literals
import frappe, json
import io, base64, os, requests
import frappe
import json
import io
import base64
import os
import requests
from frappe import _
from frappe.frappeclient import FrappeClient
from frappe.desk.form.load import get_attachments
from frappe.utils.file_manager import get_file_path
from six import string_types
current_user = frappe.session.user
@frappe.whitelist()
def register_marketplace(company, company_description):
validate_registerer()
settings = frappe.get_single('Marketplace Settings')
message = settings.register_seller(company, company_description)
if message.get('hub_seller_name'):
settings.registered = 1
settings.hub_seller_name = message.get('hub_seller_name')
settings.save()
settings.add_hub_user(frappe.session.user)
return { 'ok': 1 }
@frappe.whitelist()
def register_users(user_list):
user_list = json.loads(user_list)
settings = frappe.get_single('Marketplace Settings')
for user in user_list:
settings.add_hub_user(user)
return user_list
def validate_registerer():
if current_user == 'Administrator':
frappe.throw(_('Please login as another user to register on Marketplace'))
valid_roles = ['System Manager', 'Item Manager']
if not frappe.utils.is_subset(valid_roles, frappe.get_roles()):
frappe.throw(_('Only users with {0} role can register on Marketplace').format(', '.join(valid_roles)),
frappe.PermissionError)
@frappe.whitelist()
def call_hub_method(method, params=None):
connection = get_hub_connection()
@ -20,11 +70,12 @@ def call_hub_method(method, params=None):
response = connection.post_request(params)
return response
def map_fields(items):
field_mappings = get_field_mappings()
table_fields = [d.fieldname for d in frappe.get_meta('Item').get_table_fields()]
hub_seller = frappe.db.get_value('Marketplace Settings' , 'Marketplace Settings', 'company_email')
hub_seller_name = frappe.db.get_value('Marketplace Settings' , 'Marketplace Settings', 'hub_seller_name')
for item in items:
for fieldname in table_fields:
@ -39,11 +90,12 @@ def map_fields(items):
item[remote_fieldname] = value
item['doctype'] = 'Hub Item'
item['hub_seller'] = hub_seller
item['hub_seller'] = hub_seller_name
item.pop('attachments', None)
return items
@frappe.whitelist()
def get_valid_items(search_value=''):
items = frappe.get_list(
@ -68,6 +120,7 @@ def get_valid_items(search_value=''):
return valid_items
@frappe.whitelist()
def publish_selected_items(items_to_publish):
items_to_publish = json.loads(items_to_publish)
@ -85,12 +138,11 @@ def publish_selected_items(items_to_publish):
'image_list': item.get('image_list')
}).insert(ignore_if_duplicate=True)
items = map_fields(items_to_publish)
try:
item_sync_preprocess(len(items))
load_base64_image_from_items(items)
convert_relative_image_urls_to_absolute(items)
# TODO: Publish Progress
connection = get_hub_connection()
@ -100,6 +152,15 @@ def publish_selected_items(items_to_publish):
except Exception as e:
frappe.log_error(message=e, title='Hub Sync Error')
@frappe.whitelist()
def get_unregistered_users():
settings = frappe.get_single('Marketplace Settings')
registered_users = [user.user for user in settings.users] + ['Administrator', 'Guest']
all_users = [user.name for user in frappe.db.get_all('User', filters={'enabled': 1})]
unregistered_users = [user for user in all_users if user not in registered_users]
return unregistered_users
def item_sync_preprocess(intended_item_publish_count):
response = call_hub_method('pre_items_publish', {
'intended_item_publish_count': intended_item_publish_count
@ -111,6 +172,7 @@ def item_sync_preprocess(intended_item_publish_count):
else:
frappe.throw('Unable to update remote activity')
def item_sync_postprocess():
response = call_hub_method('post_items_publish', {})
if response:
@ -121,50 +183,29 @@ def item_sync_postprocess():
frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'sync_in_progress', 0)
def load_base64_image_from_items(items):
def convert_relative_image_urls_to_absolute(items):
from urlparse import urljoin
for item in items:
file_path = item['image']
file_name = os.path.basename(file_path)
base64content = None
if file_path.startswith('http'):
# fetch content and then base64 it
url = file_path
response = requests.get(url)
base64content = base64.b64encode(response.content)
else:
# read file then base64 it
file_path = os.path.abspath(get_file_path(file_path))
with io.open(file_path, 'rb') as f:
base64content = base64.b64encode(f.read())
image_data = json.dumps({
'file_name': file_name,
'base64': base64content
})
item['image'] = image_data
if file_path.startswith('/files/'):
item['image'] = urljoin(frappe.utils.get_url(), file_path)
def get_hub_connection():
read_only = True
settings = frappe.get_single('Marketplace Settings')
marketplace_url = settings.marketplace_url
hub_user = settings.get_hub_user(frappe.session.user)
if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
# full rights to user who registered as hub_seller
if hub_connector.username == frappe.session.user:
read_only = False
if not read_only:
hub_connection = hub_connector.get_connection()
return hub_connection.connection
# read-only connection
if read_only:
hub_url = frappe.db.get_single_value('Marketplace Settings', 'hub_url')
hub_connection = FrappeClient(hub_url)
if hub_user:
password = hub_user.get_password()
hub_connection = FrappeClient(marketplace_url, hub_user.user, password)
return hub_connection
else:
read_only_hub_connection = FrappeClient(marketplace_url)
return read_only_hub_connection
def get_field_mappings():
return []

View File

@ -0,0 +1,140 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2018-08-31 12:36:45.627531",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"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": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "hub_user_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": "Hub User",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "password",
"fieldtype": "Password",
"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": "Hub Password",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-09-01 13:56:07.816894",
"modified_by": "netchamp@rawcoderz.com",
"module": "Hub Node",
"name": "Hub User",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class HubUser(Document):
pass

View File

@ -95,7 +95,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Marketplace URL",
"label": "Marketplace URL (to hide and update label)",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@ -215,16 +215,16 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company_email",
"fieldname": "hub_seller_name",
"fieldtype": "Data",
"hidden": 0,
"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": "Company Email",
"label": "Hub Seller Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@ -247,8 +247,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "site_name",
"fieldtype": "Data",
"fieldname": "users",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -256,138 +256,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Site Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"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,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "currency",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Currency",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "logo",
"fieldtype": "Attach Image",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company Logo",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company_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",
"label": "Users",
"length": 0,
"no_copy": 0,
"options": "Hub User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@ -479,8 +351,8 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-31 17:30:37.305704",
"modified_by": "netchamp@rawcoderz.com",
"modified": "2018-09-01 17:05:59.600583",
"modified_by": "cave@aperture.com",
"module": "Hub Node",
"name": "Marketplace Settings",
"name_case": "",

View File

@ -7,98 +7,80 @@ import frappe, requests, json, time
from frappe.model.document import Document
from frappe.utils import add_years, now, get_datetime, get_datetime_str
from frappe import _
from frappe.frappeclient import FrappeClient
from erpnext.utilities.product import get_price, get_qty_in_stock
from six import string_types
class MarketplaceSettings(Document):
def validate(self):
self.site_name = frappe.utils.get_url()
def register_seller(self, company, company_description):
def get_marketplace_url(self):
return self.marketplace_url
country, currency, company_logo = frappe.db.get_value('Company', company,
['country', 'default_currency', 'company_logo'])
def register(self):
""" Create a User on hub.erpnext.org and return username/password """
if frappe.session.user == 'Administrator':
frappe.throw(_('Please login as another user to register on Marketplace'))
if 'System Manager' not in frappe.get_roles():
frappe.throw(_('Only users with System Manager role can register on Marketplace'), frappe.PermissionError)
self.site_name = frappe.utils.get_url()
data = {
'profile': self.as_json()
company_details = {
'company': company,
'country': country,
'currency': currency,
'company_description': company_description,
'company_logo': company_logo,
'site_name': frappe.utils.get_url()
}
post_url = self.get_marketplace_url() + '/api/method/hub.hub.api.register'
response = requests.post(post_url, data=data, headers = {'accept': 'application/json'})
hub_connection = self.get_connection()
response.raise_for_status()
response = hub_connection.post_request({
'cmd': 'hub.hub.api.add_hub_seller',
'company_details': json.dumps(company_details)
})
if response.ok:
message = response.json().get('message')
else:
frappe.throw(json.loads(response.text))
return response
if message.get('email'):
self.create_hub_connector(message)
self.registered = 1
self.save()
return message or None
def add_hub_user(self, user_email):
'''Create a Hub User and User record on hub server
and if successfull append it to Hub User table
'''
# 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_doc = connection.update('User', frappe._dict({'enabled': 0}), hub_connector.username)
# if response_doc['enabled'] == 0:
# self.enabled = 0
# self.save()
def create_hub_connector(self, message):
if frappe.db.exists('Data Migration Connector', 'Hub Connector'):
hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector')
hub_connector.hostname = self.get_marketplace_url()
hub_connector.username = message['email']
hub_connector.password = message['password']
hub_connector.save()
if not self.registered:
return
frappe.get_doc({
'doctype': 'Data Migration Connector',
'connector_type': 'Frappe',
'connector_name': 'Hub Connector',
'hostname': self.get_marketplace_url(),
'username': message['email'],
'password': message['password']
}).insert()
hub_connection = self.get_connection()
def reset_hub_publishing_settings(last_sync_datetime = ""):
doc = frappe.get_doc("Marketplace Settings", "Marketplace Settings")
doc.reset_publishing_settings(last_sync_datetime)
doc.in_callback = 1
doc.save()
first_name, last_name = frappe.db.get_value('User', user_email, ['first_name', 'last_name'])
def reset_hub_settings(last_sync_datetime = ""):
doc = frappe.get_doc("Marketplace Settings", "Marketplace Settings")
doc.reset_publishing_settings(last_sync_datetime)
doc.reset_enable()
doc.in_callback = 1
doc.save()
frappe.msgprint(_("Successfully unregistered."))
hub_user = hub_connection.post_request({
'cmd': 'hub.hub.api.add_hub_user',
'user_email': user_email,
'first_name': first_name,
'last_name': last_name,
'hub_seller': self.hub_seller_name
})
@frappe.whitelist()
def register_seller(**kwargs):
settings = frappe.get_doc('Marketplace Settings')
settings.update(kwargs)
message = settings.register()
self.append('users', {
'user': hub_user.get('user_email'),
'hub_user_name': hub_user.get('hub_user_name'),
'password': hub_user.get('password')
})
return message.get('email')
self.save()
def get_hub_user(self, user):
'''Return the Hub User doc from the `users` table if password is set'''
filtered_users = filter(
lambda x: x.user == user and x.password,
self.users
)
if filtered_users:
return filtered_users[0]
def get_connection(self):
return FrappeClient(self.marketplace_url)
def unregister(self):
"""Disable the User on hubmarket.org"""
pass

View File

@ -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: Marketplace Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Marketplace Settings
() => frappe.tests.make('Marketplace Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestMarketplaceSettings(unittest.TestCase):
pass

View File

@ -560,6 +560,6 @@ erpnext.patches.v10_0.update_address_template_for_india
erpnext.patches.v11_0.add_expense_claim_default_account
execute:frappe.delete_doc("Page", "hub")
erpnext.patches.v11_0.reset_publish_in_hub_for_all_items
erpnext.patches.v11_0.update_hub_url # 2018-08-31
erpnext.patches.v11_0.update_hub_url # 2018-08-31 # 2018-09-03
erpnext.patches.v10_0.set_discount_amount
erpnext.patches.v10_0.recalculate_gross_margin_for_project

View File

@ -2,4 +2,4 @@ import frappe
def execute():
frappe.reload_doc('hub_node', 'doctype', 'Marketplace Settings')
frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'hub_url', 'https://hubmarket.org')
frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'marketplace_url', 'https://hubmarket.org')

View File

@ -20,23 +20,29 @@ import Messages from './pages/Messages.vue';
import Profile from './pages/Profile.vue';
import NotFound from './pages/NotFound.vue';
const route_map = {
'marketplace/home': Home,
'marketplace/search/:keyword': Search,
'marketplace/category/:category': Category,
'marketplace/item/:item': Item,
'marketplace/seller/:seller': Seller,
'marketplace/not-found': NotFound,
function get_route_map() {
const read_only_routes = {
'marketplace/home': Home,
'marketplace/search/:keyword': Search,
'marketplace/category/:category': Category,
'marketplace/item/:item': Item,
'marketplace/seller/:seller': Seller,
'marketplace/not-found': NotFound,
}
const registered_routes = {
'marketplace/profile': Profile,
'marketplace/saved-items': SavedItems,
'marketplace/publish': Publish,
'marketplace/published-items': PublishedItems,
'marketplace/buying': Buying,
'marketplace/buying/:item': Messages,
'marketplace/selling': Selling,
'marketplace/selling/:buyer/:item': Messages
}
// Registered seller routes
'marketplace/profile': Profile,
'marketplace/saved-items': SavedItems,
'marketplace/publish': Publish,
'marketplace/published-items': PublishedItems,
'marketplace/buying': Buying,
'marketplace/buying/:item': Messages,
'marketplace/selling': Selling,
'marketplace/selling/:buyer/:item': Messages
return hub.is_seller_registered()
? Object.assign({}, read_only_routes, registered_routes)
: read_only_routes;
}
export default {
@ -56,6 +62,7 @@ export default {
this.current_page = this.get_current_page();
},
get_current_page() {
const route_map = get_route_map();
const curr_route = frappe.get_route_str();
let route = Object.keys(route_map).filter(route => route == curr_route)[0];
if (!route) {

View File

@ -19,7 +19,7 @@
export default {
data() {
return {
hub_registered: hub.settings.registered && frappe.session.user === hub.settings.company_email,
hub_registered: hub.is_user_registered(),
items: [
{
label: __('Browse'),

View File

@ -38,7 +38,7 @@
</div>
</div>
<div v-if="menu_items" class="col-md-1">
<div v-if="menu_items && menu_items.length" class="col-md-1">
<div class="dropdown pull-right hub-item-dropdown">
<a class="dropdown-toggle btn btn-xs btn-default" data-toggle="dropdown">
<span class="caret"></span>

View File

@ -56,8 +56,6 @@ export default {
},
on_submit_review(values) {
values.user = hub.settings.company_email;
this.review_area.reset();
hub.call('add_item_review', {

View File

@ -1,138 +0,0 @@
import { get_rating_html } from './reviews';
function get_detail_view_html(item, allow_edit) {
const title = item.item_name || item.name;
const seller = item.company;
const who = __('Posted By {0}', [seller]);
const when = comment_when(item.creation);
const city = item.city ? item.city + ', ' : '';
const country = item.country ? item.country : '';
const where = `${city}${country}`;
const dot_spacer = '<span aria-hidden="true"> · </span>';
const description = item.description || '';
let stats = __('No views yet');
if(item.view_count) {
const views_message = __(`${item.view_count} Views`);
const rating_html = get_rating_html(item.average_rating);
const rating_count = item.no_of_ratings > 0 ? `${item.no_of_ratings} reviews` : __('No reviews yet');
stats = `${views_message}${dot_spacer}${rating_html} (${rating_count})`;
}
let favourite_button = ''
if (hub.settings.registered) {
favourite_button = !item.favourited
? `<button class="btn btn-default text-muted favourite-button" data-action="add_to_favourites">
${__('Save')} <i class="octicon octicon-heart text-extra-muted"></i>
</button>`
: `<button class="btn btn-default text-muted favourite-button disabled" data-action="add_to_favourites">
${__('Saved')}
</button>`;
}
const contact_seller_button = item.hub_seller !== hub.settings.company_email
? `<button class="btn btn-primary" data-action="contact_seller">
${__('Contact Seller')}
</button>`
: '';
let menu_items = '';
if(allow_edit) {
menu_items = `
<li><a data-action="edit_details">${__('Edit Details')}</a></li>
<li><a data-action="unpublish_item">${__('Unpublish')}</a></li>`;
} else {
menu_items = `
<li><a data-action="report_item">${__('Report this item')}</a></li>
`;
}
const html = `
<div class="hub-item-container">
<div class="row visible-xs">
<div class="col-xs-12 margin-bottom">
<button class="btn btn-xs btn-default" data-route="marketplace/home">${__('Back to home')}</button>
</div>
</div>
<div class="row detail-page-section margin-bottom">
<div class="col-md-3">
<div class="hub-item-image">
<img src="${item.image}">
</div>
</div>
<div class="col-md-8 flex flex-column">
<div class="detail-page-header">
<h2>${title}</h2>
<div class="text-muted">
<p>${where}${dot_spacer}${when}</p>
<p>${stats}</p>
</div>
</div>
<div class="page-actions detail-page-actions">
${favourite_button}
${contact_seller_button}
</div>
</div>
<div class="col-md-1">
<div class="dropdown pull-right hub-item-dropdown">
<a class="dropdown-toggle btn btn-xs btn-default" data-toggle="dropdown">
<span class="caret"></span>
</a>
<ul class="dropdown-menu dropdown-right" role="menu">
${menu_items}
</ul>
</div>
</div>
</div>
<div class="row hub-item-description">
<h6 class="col-md-12 margin-top">
<b class="text-muted">Item Description</b>
</h6>
<p class="col-md-12">
${description ? description : __('No details')}
</p>
</div>
<div class="row hub-item-seller">
<h6 class="col-md-12 margin-top margin-bottom">
<b class="text-muted">Seller Information</b>
</h6>
<div class="col-md-1">
<img src="https://picsum.photos/200">
</div>
<div class="col-md-8">
<div class="margin-bottom"><a href="#marketplace/seller/${seller}" class="bold">${seller}</a></div>
</div>
</div>
<!-- review area -->
<div class="row hub-item-review-container">
<div class="col-md-12 form-footer">
<div class="form-comments">
<div class="timeline">
<div class="timeline-head"></div>
<div class="timeline-items"></div>
</div>
</div>
<div class="pull-right scroll-to-top">
<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
</div>
</div>
</div>
</div>
`;
return html;
}
export {
get_detail_view_html,
get_profile_html
}

View File

@ -1,80 +0,0 @@
function get_buying_item_message_card_html(item) {
const item_name = item.item_name || item.name;
const title = strip_html(item_name);
const message = item.recent_message
const sender = message.sender === frappe.session.user ? 'You' : message.sender
const content = strip_html(message.content)
// route
item.route = `marketplace/buying/${item.name}`
const item_html = `
<div class="col-md-7">
<div class="hub-list-item" data-route="${item.route}">
<div class="hub-list-left">
<img class="hub-list-image" src="${item.image}">
<div class="hub-list-body ellipsis">
<div class="hub-list-title">${item_name}</div>
<div class="hub-list-subtitle ellipsis">
<span>${sender}: </span>
<span>${content}</span>
</div>
</div>
</div>
<div class="hub-list-right">
<span class="text-muted">${comment_when(message.creation, true)}</span>
</div>
</div>
</div>
`;
return item_html;
}
function get_selling_item_message_card_html(item) {
const item_name = item.item_name || item.name;
const title = strip_html(item_name);
// route
if (!item.route) {
item.route = `marketplace/item/${item.name}`
}
let received_messages = '';
item.received_messages.forEach(message => {
const sender = message.sender === frappe.session.user ? 'You' : message.sender
const content = strip_html(message.content)
received_messages += `
<div class="received-message">
<span class="text-muted">${comment_when(message.creation, true)}</span>
<div class="ellipsis">
<span class="bold">${sender}: </span>
<span>${content}</span>
</div>
</div>
`
});
const item_html = `
<div class="selling-item-message-card">
<div class="selling-item-detail" data-route="${item.route}">
<img class="item-image" src="${item.image}">
<h5 class="item-name">${item_name}</h5>
<div class="received-message-container">
${received_messages}
</div>
</div>
</div>
`;
return item_html;
}
export {
get_item_card_html,
get_local_item_card_html,
get_buying_item_message_card_html,
get_selling_item_message_card_html
}

View File

@ -4,40 +4,17 @@ const ProfileDialog = (title = __('Edit Profile'), action={}) => {
fieldtype: 'Link',
fieldname: 'company',
label: __('Company'),
options: 'Company',
onchange: () => {
const value = dialog.get_value('company');
if (value) {
frappe.db.get_doc('Company', value)
.then(company => {
dialog.set_values({
country: company.country,
currency: company.default_currency
});
});
}
}
options: 'Company'
},
{
fieldname: 'company_email',
label: __('Email'),
fieldtype: 'Read Only'
fieldtype: 'Read Only',
fieldname: 'email',
label: __('Email')
},
{
fieldname: 'country',
label: __('Country'),
fieldtype: 'Read Only'
},
{
fieldname: 'currency',
label: __('Currency'),
fieldtype: 'Read Only'
},
{
fieldtype: 'Text',
label: __('About your Company'),
fieldname: 'company_description'
label: __('About your company'),
fieldname: 'company_description',
fieldtype: 'Text'
}
];
@ -48,7 +25,11 @@ const ProfileDialog = (title = __('Edit Profile'), action={}) => {
primary_action: () => {
const form_values = dialog.get_values();
let values_filled = true;
const mandatory_fields = ['company', 'company_email', 'company_description'];
// TODO: Say "we notice that the company description and logo isn't set. Please set them in master."
// Only then allow to register
const mandatory_fields = ['company', 'company_description'];
mandatory_fields.forEach(field => {
const value = form_values[field];
if (!value) {
@ -65,7 +46,7 @@ const ProfileDialog = (title = __('Edit Profile'), action={}) => {
// Post create
const default_company = frappe.defaults.get_default('company');
dialog.set_value('company', default_company);
dialog.set_value('company_email', frappe.session.user);
dialog.set_value('email', frappe.session.user);
return dialog;
}

View File

@ -20,17 +20,18 @@ erpnext.hub.Marketplace = class Marketplace {
this.$parent = $(parent);
this.page = parent.page;
frappe.model.with_doc('Marketplace Settings').then(doc => {
hub.settings = doc;
const is_registered = hub.settings.registered;
const is_registered_seller = hub.settings.company_email === frappe.session.user;
this.update_hub_settings().then(() => {
this.setup_header();
this.make_sidebar();
this.make_body();
this.setup_events();
this.refresh();
if (!is_registered && !is_registered_seller && frappe.user_roles.includes('System Manager')) {
if (!hub.is_seller_registered()) {
this.page.set_primary_action('Become a Seller', this.show_register_dialog.bind(this))
} else {
this.page.set_secondary_action('Add Users', this.show_add_user_dialog.bind(this));
}
});
}
@ -76,10 +77,7 @@ erpnext.hub.Marketplace = class Marketplace {
});
erpnext.hub.on('seller-registered', () => {
this.page.clear_primary_action()
frappe.model.with_doc('Marketplace Settings').then((doc)=> {
hub.settings = doc;
});
this.page.clear_primary_action();
});
}
@ -88,27 +86,125 @@ erpnext.hub.Marketplace = class Marketplace {
}
show_register_dialog() {
if(frappe.session.user === 'Administrator') {
frappe.msgprint(__('You need to be a user other than Administrator with System Manager and Item Manager roles to register on Marketplace.'));
return;
}
if (!is_subset(['System Manager', 'Item Manager'], frappe.user_roles)) {
frappe.msgprint(__('You need to be a user with System Manager and Item Manager roles to register on Marketplace.'));
return;
}
this.register_dialog = ProfileDialog(
__('Become a Seller'),
{
label: __('Register'),
on_submit: this.register_seller.bind(this)
on_submit: this.register_marketplace.bind(this)
}
);
this.register_dialog.show();
}
register_seller(form_values) {
register_marketplace({company, company_description}) {
frappe.call({
method: 'erpnext.hub_node.doctype.marketplace_settings.marketplace_settings.register_seller',
args: form_values
}).then(() => {
this.register_dialog.hide();
frappe.set_route('marketplace', 'publish');
method: 'erpnext.hub_node.api.register_marketplace',
args: {
company,
company_description
}
}).then((r) => {
if (r.message && r.message.ok) {
this.register_dialog.hide();
erpnext.hub.trigger('seller-registered');
this.update_hub_settings()
.then(() => {
frappe.set_route('marketplace', 'publish');
erpnext.hub.trigger('seller-registered');
});
}
});
}
show_add_user_dialog() {
if (!is_subset(['System Manager', 'Item Manager'], frappe.user_roles)) {
frappe.msgprint(__('You need to be a user with System Manager and Item Manager roles to add users to Marketplace.'));
return;
}
this.get_unregistered_users()
.then(r => {
const user_list = r.message;
const d = new frappe.ui.Dialog({
title: __('Add Users to Marketplace'),
fields: [
{
label: __('Users'),
fieldname: 'users',
fieldtype: 'MultiSelect',
reqd: 1,
get_data() {
return user_list;
}
}
],
primary_action({ users }) {
const selected_users = users.split(',').map(d => d.trim()).filter(Boolean);
if (!selected_users.every(user => user_list.includes(user))) {
d.set_df_property('users', 'description', __('Some emails are invalid'));
return;
} else {
d.set_df_property('users', 'description', '');
}
frappe.call('erpnext.hub_node.api.register_users', {
user_list: selected_users
})
.then(r => {
d.hide();
if (r.message && r.message.length) {
frappe.show_alert(__('Added {0} users', [r.message.length]));
}
});
}
});
d.show();
});
}
get_unregistered_users() {
return frappe.call('erpnext.hub_node.api.get_unregistered_users')
}
update_hub_settings() {
return frappe.db.get_doc('Marketplace Settings').then(doc => {
hub.settings = doc;
});
}
}
Object.assign(hub, {
is_seller_registered() {
return hub.settings.registered;
},
is_user_registered() {
return this.is_seller_registered() && hub.settings.users
.filter(hub_user => hub_user.user === frappe.session.user)
.length === 1;
},
});
/**
* Returns true if list_a is subset of list_b
* @param {Array} list_a
* @param {Array} list_b
*/
function is_subset(list_a, list_b) {
return list_a.every(item => list_b.includes(item));
}

View File

@ -13,8 +13,8 @@
v-route="'marketplace/buying/' + item.name"
>
<div slot="subtitle">
<span>{{item.recent_message.sender}}: </span>
<span>{{item.recent_message.content | striphtml}}</span>
<span>{{ get_sender(item.recent_message) }}: </span>
<span>{{ item.recent_message.message | striphtml }}</span>
</div>
</item-list-card>
</div>
@ -47,6 +47,9 @@ export default {
methods: {
get_items_for_messages() {
return hub.call('get_buying_items_for_messages', {}, 'action:send_message');
},
get_sender(message) {
return message.sender === frappe.session.user ? __('You') : (message.sender_name || message.sender);
}
}
}

View File

@ -19,7 +19,7 @@
:value="item_views_and_ratings"
></detail-header-item>
<button slot="detail-header-item"
<button v-if="primary_action" slot="detail-header-item"
class="btn btn-primary btn-sm margin-top"
@click="primary_action.action"
>
@ -56,7 +56,7 @@ export default {
menu_items: [
{
label: __('Save Item'),
condition: !this.is_own_item,
condition: hub.is_user_registered() && !this.is_own_item,
action: this.add_to_saved_items
},
{
@ -66,12 +66,12 @@ export default {
},
{
label: __('Edit Details'),
condition: this.is_own_item,
condition: hub.is_user_registered() && this.is_own_item,
action: this.edit_details
},
{
label: __('Unpublish Item'),
condition: this.is_own_item,
condition: hub.is_user_registered() && this.is_own_item,
action: this.unpublish_item
}
]
@ -81,7 +81,7 @@ export default {
is_own_item() {
let is_own_item = false;
if(this.item) {
if(this.item.hub_seller === hub.setting.company_email) {
if(this.item.hub_seller === hub.settings.hub_seller_name) {
is_own_item = true;
}
}
@ -125,9 +125,13 @@ export default {
},
primary_action() {
return {
label: __('Contact Seller'),
action: this.contact_seller.bind(this)
if (hub.is_user_registered()) {
return {
label: __('Contact Seller'),
action: this.contact_seller.bind(this)
}
} else {
return undefined;
}
}
},
@ -190,9 +194,9 @@ export default {
},
add_to_saved_items() {
hub.call('add_item_to_seller_saved_items', {
hub.call('add_item_to_user_saved_items', {
hub_item_name: this.hub_item_name,
hub_seller: hub.settings.company_email
hub_user: frappe.session.user
})
.then(() => {
const saved_items_link = `<b><a href="#marketplace/saved-items">${__('Saved')}</a></b>`
@ -224,13 +228,11 @@ export default {
if (!message) return;
hub.call('send_message', {
from_seller: hub.settings.company_email,
to_seller: this.item.hub_seller,
hub_item: this.item.name,
message
})
.then(() => {
d.hide();
this.contact_seller_dialog.hide();
frappe.set_route('marketplace', 'buying', this.item.name);
erpnext.hub.trigger('action:send_message')
});

View File

@ -16,7 +16,7 @@
<div class="level margin-bottom" v-for="message in messages" :key="message.name">
<div class="level-left ellipsis" style="width: 80%;">
<div v-html="frappe.avatar(message.sender)" />
<div style="white-space: normal;" v-html="message.content" />
<div style="white-space: normal;" v-html="message.message" />
</div>
<div class="level-right text-muted" v-html="frappe.datetime.comment_when(message.creation, true)" />
</div>
@ -64,13 +64,12 @@ export default {
methods: {
send_message(message) {
this.messages.push({
sender: hub.settings.company_email,
content: message,
sender: frappe.session.user,
message: message,
creation: Date.now(),
name: frappe.utils.get_random(6)
});
hub.call('send_message', {
from_seller: hub.settings.company_email,
to_seller: this.get_against_seller(),
hub_item: this.item_details.name,
message

View File

@ -52,7 +52,7 @@ export default {
get_profile() {
hub.call(
'get_hub_seller_profile',
{ hub_seller: hub.settings.company_email }
{ hub_seller: hub.settings.hub_seller_name }
).then(profile => {
this.init = false;

View File

@ -5,10 +5,10 @@
>
<section-header>
<div>
<h5>{{ page_title }}</h5>
<h5>{{ __('Published Items') }}</h5>
<p v-if="items.length"
class="text-muted margin-bottom">
{{ published_items_message }}
{{ __('You can publish upto 200 items.') }}
</p>
</div>
@ -16,17 +16,17 @@
class="btn btn-default btn-xs publish-items"
v-route="'marketplace/publish'"
>
<span>{{ publish_button_text }}</span>
<span>{{ __('Publish More Items') }}</span>
</button>
</section-header>
<item-cards-container
:container_name="page_title"
:container_name="__('Published Items')"
:items="items"
:item_id_fieldname="item_id_fieldname"
:on_click="go_to_item_details_page"
:empty_state_message="empty_state_message"
:empty_state_message="__('You haven\'t published any items yet.')"
:empty_state_action="publish_page_action"
>
</item-cards-container>
@ -44,16 +44,9 @@ export default {
publish_page_action: {
label: __('Publish Your First Items'),
on_click: () => {
frappe.set_route(`marketplace/home`);
frappe.set_route(`marketplace/publish`);
}
},
// Constants
page_title: __('Published Items'),
publish_button_text: __('Publish More Items'),
published_items_message: __('You can publish upto 200 items.'),
// TODO: Add empty state action
empty_state_message: __('You haven\'t published any items yet.')
}
};
},
created() {
@ -63,7 +56,7 @@ export default {
get_items() {
hub.call('get_items', {
filters: {
hub_seller: hub.settings.company_email
hub_seller: hub.settings.hub_seller_name
}
})
.then((items) => {

View File

@ -38,7 +38,7 @@ export default {
methods: {
get_items() {
hub.call(
'get_saved_items_of_seller', {},
'get_saved_items_of_user', {},
'action:item_save'
)
.then((items) => {
@ -83,9 +83,9 @@ export default {
remove_item_from_saved_items(hub_item_name) {
erpnext.hub.trigger('action:item_save');
hub.call('remove_item_from_seller_saved_items', {
hub.call('remove_item_from_user_saved_items', {
hub_item_name,
hub_seller: hub.settings.company_email
hub_user: frappe.session.user
})
.then(() => {
this.get_items();

View File

@ -17,15 +17,15 @@
</div>
</item-list-card>
<div class="hub-list-item" v-for="(message, index) in item.received_messages" :key="index"
v-route="'marketplace/selling/' + message.buyer_email + '/' + item.name"
v-route="'marketplace/selling/' + message.buyer + '/' + item.name"
>
<div class="hub-list-left">
<div class="hub-list-body">
<div class="hub-list-title">
{{ message.buyer }}
{{ message.buyer_name }}
</div>
<div class="hub-list-subtitle">
{{ message.sender }}: {{ message.content }}
{{ message.sender }}: {{ message.message | striphtml }}
</div>
</div>
</div>

View File

@ -62,5 +62,5 @@ Vue.directive('img-src', {
});
Vue.filter('striphtml', function (text) {
return strip_html(text);
return strip_html(text || '');
});

View File

@ -305,6 +305,102 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb_about",
"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": "About the Company",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company_logo",
"fieldtype": "Attach Image",
"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 Logo",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company_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": "Company 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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -741,7 +837,7 @@
"label": "Create Chart Of Accounts Based On",
"length": 0,
"no_copy": 0,
"options": "\nStandard Template\nExisting Company",
"options": "\nStandard Template\nExisting Company\n\n\n",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@ -775,7 +871,7 @@
"label": "Chart Of Accounts Template",
"length": 0,
"no_copy": 1,
"options": "",
"options": "\n\n\n",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@ -2630,38 +2726,6 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company_logo",
"fieldtype": "Attach Image",
"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 Logo",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -2772,8 +2836,8 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-08-28 15:47:50.757131",
"modified_by": "Administrator",
"modified": "2018-09-01 16:03:30.716918",
"modified_by": "cave@aperture.com",
"module": "Setup",
"name": "Company",
"owner": "Administrator",
@ -2918,5 +2982,6 @@
"show_name_in_global_search": 1,
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}