fix!: UX of supplier linking with supplier users on portal pages (#35836)
* fix: create and add Portal Users child table in Supplier/Customer Issue #35772 * fix: modify the original permission check hook * fix: auto-add role for portal users * fix: added patch for auto-populating portal users * fix: modify patch to fetch users correctly * fix: remove unnecessary code for updating naming_series * fix(UX): show portal user in list view Also split columns to reduce whitespace. * refactor: simpler role checking * fix: consider parenttype while fetching portal user * refactor: simpler code, rename variable * test: supplier portal user can access their docs * refactor: only add role if not added * refactor: rename and move patch to supplier * refactor: dont add role if no perm or existing doc * fix: add role before save * refactor: run query directly * refactor: split patch and apply roles - if role isn't present dont add portal user - ignore failure as it's not critical * test: fix permission creation for webform test --------- Co-authored-by: Ankush Menat <ankush@frappe.io>
This commit is contained in:
parent
e832455790
commit
5113a417a1
0
erpnext/buying/doctype/supplier/patches/__init__.py
Normal file
0
erpnext/buying/doctype/supplier/patches/__init__.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
in_ci = os.environ.get("CI")
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
try:
|
||||||
|
contacts = get_portal_user_contacts()
|
||||||
|
add_portal_users(contacts)
|
||||||
|
except Exception:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error("Failed to migrate portal users")
|
||||||
|
|
||||||
|
if in_ci: # TODO: better way to handle this.
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def get_portal_user_contacts():
|
||||||
|
contact = frappe.qb.DocType("Contact")
|
||||||
|
dynamic_link = frappe.qb.DocType("Dynamic Link")
|
||||||
|
|
||||||
|
return (
|
||||||
|
frappe.qb.from_(contact)
|
||||||
|
.inner_join(dynamic_link)
|
||||||
|
.on(contact.name == dynamic_link.parent)
|
||||||
|
.select(
|
||||||
|
(dynamic_link.link_doctype).as_("doctype"),
|
||||||
|
(dynamic_link.link_name).as_("parent"),
|
||||||
|
(contact.email_id).as_("portal_user"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(dynamic_link.parenttype == "Contact")
|
||||||
|
& (dynamic_link.link_doctype.isin(["Supplier", "Customer"]))
|
||||||
|
)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
|
def add_portal_users(contacts):
|
||||||
|
for contact in contacts:
|
||||||
|
user = frappe.db.get_value("User", {"email": contact.portal_user}, "name")
|
||||||
|
if not user:
|
||||||
|
continue
|
||||||
|
|
||||||
|
roles = frappe.get_roles(user)
|
||||||
|
required_role = contact.doctype
|
||||||
|
if required_role not in roles:
|
||||||
|
continue
|
||||||
|
|
||||||
|
portal_user_doc = frappe.new_doc("Portal User")
|
||||||
|
portal_user_doc.parenttype = contact.doctype
|
||||||
|
portal_user_doc.parentfield = "portal_users"
|
||||||
|
portal_user_doc.parent = contact.parent
|
||||||
|
portal_user_doc.user = user
|
||||||
|
portal_user_doc.insert()
|
@ -42,6 +42,14 @@ frappe.ui.form.on("Supplier", {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("user", "portal_users", function(doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"ignore_user_type": true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
|
@ -68,7 +68,10 @@
|
|||||||
"on_hold",
|
"on_hold",
|
||||||
"hold_type",
|
"hold_type",
|
||||||
"column_break_59",
|
"column_break_59",
|
||||||
"release_date"
|
"release_date",
|
||||||
|
"portal_users_tab",
|
||||||
|
"portal_users",
|
||||||
|
"column_break_1mqv"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -445,6 +448,21 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_59",
|
"fieldname": "column_break_59",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "portal_users_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Portal Users"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "portal_users",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Supplier Portal Users",
|
||||||
|
"options": "Portal User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_1mqv",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
@ -457,7 +475,7 @@
|
|||||||
"link_fieldname": "party"
|
"link_fieldname": "party"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-05-09 15:34:13.408932",
|
"modified": "2023-06-26 14:20:00.961554",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Supplier",
|
"name": "Supplier",
|
||||||
@ -489,7 +507,6 @@
|
|||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Purchase Master Manager",
|
"role": "Purchase Master Manager",
|
||||||
"set_user_permissions": 1,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
|
@ -16,6 +16,7 @@ from erpnext.accounts.party import ( # noqa
|
|||||||
get_timeline_data,
|
get_timeline_data,
|
||||||
validate_party_accounts,
|
validate_party_accounts,
|
||||||
)
|
)
|
||||||
|
from erpnext.controllers.website_list_for_contact import add_role_for_portal_user
|
||||||
from erpnext.utilities.transaction_base import TransactionBase
|
from erpnext.utilities.transaction_base import TransactionBase
|
||||||
|
|
||||||
|
|
||||||
@ -46,12 +47,35 @@ class Supplier(TransactionBase):
|
|||||||
self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
if not self.naming_series:
|
|
||||||
self.naming_series = ""
|
|
||||||
|
|
||||||
self.create_primary_contact()
|
self.create_primary_contact()
|
||||||
self.create_primary_address()
|
self.create_primary_address()
|
||||||
|
|
||||||
|
def add_role_for_user(self):
|
||||||
|
for portal_user in self.portal_users:
|
||||||
|
add_role_for_portal_user(portal_user, "Supplier")
|
||||||
|
|
||||||
|
def _add_supplier_role(self, portal_user):
|
||||||
|
if not portal_user.is_new():
|
||||||
|
return
|
||||||
|
|
||||||
|
user_doc = frappe.get_doc("User", portal_user.user)
|
||||||
|
roles = {r.role for r in user_doc.roles}
|
||||||
|
|
||||||
|
if "Supplier" in roles:
|
||||||
|
return
|
||||||
|
|
||||||
|
if "System Manager" not in frappe.get_roles():
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Please add 'Supplier' role to user {0}.").format(portal_user.user),
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
user_doc.add_roles("Supplier")
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Added Supplier Role to User {0}.").format(frappe.bold(user_doc.name)), alert=True
|
||||||
|
)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.flags.is_new_doc = self.is_new()
|
self.flags.is_new_doc = self.is_new()
|
||||||
|
|
||||||
@ -62,6 +86,7 @@ class Supplier(TransactionBase):
|
|||||||
|
|
||||||
validate_party_accounts(self)
|
validate_party_accounts(self)
|
||||||
self.validate_internal_supplier()
|
self.validate_internal_supplier()
|
||||||
|
self.add_role_for_user()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_supplier_group_details(self):
|
def get_supplier_group_details(self):
|
||||||
|
@ -7,6 +7,7 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_
|
|||||||
from frappe.test_runner import make_test_records
|
from frappe.test_runner import make_test_records
|
||||||
|
|
||||||
from erpnext.accounts.party import get_due_date
|
from erpnext.accounts.party import get_due_date
|
||||||
|
from erpnext.controllers.website_list_for_contact import get_customers_suppliers
|
||||||
from erpnext.exceptions import PartyDisabled
|
from erpnext.exceptions import PartyDisabled
|
||||||
|
|
||||||
test_dependencies = ["Payment Term", "Payment Terms Template"]
|
test_dependencies = ["Payment Term", "Payment Terms Template"]
|
||||||
@ -195,6 +196,9 @@ class TestSupplier(FrappeTestCase):
|
|||||||
def create_supplier(**args):
|
def create_supplier(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
if not args.supplier_name:
|
||||||
|
args.supplier_name = frappe.generate_hash()
|
||||||
|
|
||||||
if frappe.db.exists("Supplier", args.supplier_name):
|
if frappe.db.exists("Supplier", args.supplier_name):
|
||||||
return frappe.get_doc("Supplier", args.supplier_name)
|
return frappe.get_doc("Supplier", args.supplier_name)
|
||||||
|
|
||||||
@ -209,3 +213,25 @@ def create_supplier(**args):
|
|||||||
).insert()
|
).insert()
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
class TestSupplierPortal(FrappeTestCase):
|
||||||
|
def test_portal_user_can_access_supplier_data(self):
|
||||||
|
|
||||||
|
supplier = create_supplier()
|
||||||
|
|
||||||
|
user = frappe.generate_hash() + "@example.com"
|
||||||
|
frappe.new_doc(
|
||||||
|
"User",
|
||||||
|
first_name="Supplier Portal User",
|
||||||
|
email=user,
|
||||||
|
send_welcome_email=False,
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
supplier.append("portal_users", {"user": user})
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
frappe.set_user(user)
|
||||||
|
_, suppliers = get_customers_suppliers("Purchase Order", user)
|
||||||
|
|
||||||
|
self.assertIn(supplier.name, suppliers)
|
||||||
|
@ -232,22 +232,8 @@ def get_customers_suppliers(doctype, user):
|
|||||||
has_supplier_field = meta.has_field("supplier")
|
has_supplier_field = meta.has_field("supplier")
|
||||||
|
|
||||||
if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
|
if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
|
||||||
contacts = frappe.db.sql(
|
suppliers = get_parents_for_user("Supplier")
|
||||||
"""
|
customers = get_parents_for_user("Customer")
|
||||||
select
|
|
||||||
`tabContact`.email_id,
|
|
||||||
`tabDynamic Link`.link_doctype,
|
|
||||||
`tabDynamic Link`.link_name
|
|
||||||
from
|
|
||||||
`tabContact`, `tabDynamic Link`
|
|
||||||
where
|
|
||||||
`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s
|
|
||||||
""",
|
|
||||||
user,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
customers = [c.link_name for c in contacts if c.link_doctype == "Customer"]
|
|
||||||
suppliers = [c.link_name for c in contacts if c.link_doctype == "Supplier"]
|
|
||||||
elif frappe.has_permission(doctype, "read", user=user):
|
elif frappe.has_permission(doctype, "read", user=user):
|
||||||
customer_list = frappe.get_list("Customer")
|
customer_list = frappe.get_list("Customer")
|
||||||
customers = suppliers = [customer.name for customer in customer_list]
|
customers = suppliers = [customer.name for customer in customer_list]
|
||||||
@ -255,6 +241,17 @@ def get_customers_suppliers(doctype, user):
|
|||||||
return customers if has_customer_field else None, suppliers if has_supplier_field else None
|
return customers if has_customer_field else None, suppliers if has_supplier_field else None
|
||||||
|
|
||||||
|
|
||||||
|
def get_parents_for_user(parenttype: str) -> list[str]:
|
||||||
|
portal_user = frappe.qb.DocType("Portal User")
|
||||||
|
|
||||||
|
return (
|
||||||
|
frappe.qb.from_(portal_user)
|
||||||
|
.select(portal_user.parent)
|
||||||
|
.where(portal_user.user == frappe.session.user)
|
||||||
|
.where(portal_user.parenttype == parenttype)
|
||||||
|
).run(pluck="name")
|
||||||
|
|
||||||
|
|
||||||
def has_website_permission(doc, ptype, user, verbose=False):
|
def has_website_permission(doc, ptype, user, verbose=False):
|
||||||
doctype = doc.doctype
|
doctype = doc.doctype
|
||||||
customers, suppliers = get_customers_suppliers(doctype, user)
|
customers, suppliers = get_customers_suppliers(doctype, user)
|
||||||
@ -282,3 +279,28 @@ def get_customer_field_name(doctype):
|
|||||||
return "party_name"
|
return "party_name"
|
||||||
else:
|
else:
|
||||||
return "customer"
|
return "customer"
|
||||||
|
|
||||||
|
|
||||||
|
def add_role_for_portal_user(portal_user, role):
|
||||||
|
"""When a new portal user is added, give appropriate roles to user if
|
||||||
|
posssible, else warn user to add roles."""
|
||||||
|
if not portal_user.is_new():
|
||||||
|
return
|
||||||
|
|
||||||
|
user_doc = frappe.get_doc("User", portal_user.user)
|
||||||
|
roles = {r.role for r in user_doc.roles}
|
||||||
|
|
||||||
|
if role in roles:
|
||||||
|
return
|
||||||
|
|
||||||
|
if "System Manager" not in frappe.get_roles():
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Please add {1} role to user {0}.").format(portal_user.user, role),
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
user_doc.add_roles(role)
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Added {1} Role to User {0}.").format(frappe.bold(user_doc.name), role), alert=True
|
||||||
|
)
|
||||||
|
@ -340,3 +340,4 @@ execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Template', ignore_missin
|
|||||||
execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Accounts', ignore_missing=True)
|
execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Accounts', ignore_missing=True)
|
||||||
erpnext.patches.v14_0.cleanup_workspaces
|
erpnext.patches.v14_0.cleanup_workspaces
|
||||||
erpnext.patches.v14_0.set_report_in_process_SOA
|
erpnext.patches.v14_0.set_report_in_process_SOA
|
||||||
|
erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users
|
||||||
|
@ -63,6 +63,14 @@ frappe.ui.form.on("Customer", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("user", "portal_users", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"ignore_user_type": true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
customer_primary_address: function(frm){
|
customer_primary_address: function(frm){
|
||||||
if(frm.doc.customer_primary_address){
|
if(frm.doc.customer_primary_address){
|
||||||
|
@ -81,7 +81,9 @@
|
|||||||
"dn_required",
|
"dn_required",
|
||||||
"column_break_53",
|
"column_break_53",
|
||||||
"is_frozen",
|
"is_frozen",
|
||||||
"disabled"
|
"disabled",
|
||||||
|
"portal_users_tab",
|
||||||
|
"portal_users"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -555,6 +557,17 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_54",
|
"fieldname": "column_break_54",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "portal_users_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Portal Users"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "portal_users",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Customer Portal Users",
|
||||||
|
"options": "Portal User"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
@ -568,7 +581,7 @@
|
|||||||
"link_fieldname": "party"
|
"link_fieldname": "party"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-05-09 15:38:40.255193",
|
"modified": "2023-06-22 13:21:10.678382",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Customer",
|
"name": "Customer",
|
||||||
@ -607,7 +620,6 @@
|
|||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Sales Master Manager",
|
"role": "Sales Master Manager",
|
||||||
"set_user_permissions": 1,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
|
@ -22,6 +22,7 @@ from erpnext.accounts.party import ( # noqa
|
|||||||
get_timeline_data,
|
get_timeline_data,
|
||||||
validate_party_accounts,
|
validate_party_accounts,
|
||||||
)
|
)
|
||||||
|
from erpnext.controllers.website_list_for_contact import add_role_for_portal_user
|
||||||
from erpnext.utilities.transaction_base import TransactionBase
|
from erpnext.utilities.transaction_base import TransactionBase
|
||||||
|
|
||||||
|
|
||||||
@ -82,6 +83,7 @@ class Customer(TransactionBase):
|
|||||||
self.check_customer_group_change()
|
self.check_customer_group_change()
|
||||||
self.validate_default_bank_account()
|
self.validate_default_bank_account()
|
||||||
self.validate_internal_customer()
|
self.validate_internal_customer()
|
||||||
|
self.add_role_for_user()
|
||||||
|
|
||||||
# set loyalty program tier
|
# set loyalty program tier
|
||||||
if frappe.db.exists("Customer", self.name):
|
if frappe.db.exists("Customer", self.name):
|
||||||
@ -170,6 +172,10 @@ class Customer(TransactionBase):
|
|||||||
|
|
||||||
self.update_customer_groups()
|
self.update_customer_groups()
|
||||||
|
|
||||||
|
def add_role_for_user(self):
|
||||||
|
for portal_user in self.portal_users:
|
||||||
|
add_role_for_portal_user(portal_user, "Customer")
|
||||||
|
|
||||||
def update_customer_groups(self):
|
def update_customer_groups(self):
|
||||||
ignore_doctypes = ["Lead", "Opportunity", "POS Profile", "Tax Rule", "Pricing Rule"]
|
ignore_doctypes = ["Lead", "Opportunity", "POS Profile", "Tax Rule", "Pricing Rule"]
|
||||||
if frappe.flags.customer_group_changed:
|
if frappe.flags.customer_group_changed:
|
||||||
|
@ -3,18 +3,21 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
|
|
||||||
|
|
||||||
class TestWebsite(unittest.TestCase):
|
class TestWebsite(unittest.TestCase):
|
||||||
def test_permission_for_custom_doctype(self):
|
def test_permission_for_custom_doctype(self):
|
||||||
create_user("Supplier 1", "supplier1@gmail.com")
|
create_user("Supplier 1", "supplier1@gmail.com")
|
||||||
create_user("Supplier 2", "supplier2@gmail.com")
|
create_user("Supplier 2", "supplier2@gmail.com")
|
||||||
create_supplier_with_contact(
|
|
||||||
"Supplier1", "All Supplier Groups", "Supplier 1", "supplier1@gmail.com"
|
supplier1 = create_supplier(supplier_name="Supplier1")
|
||||||
)
|
supplier2 = create_supplier(supplier_name="Supplier2")
|
||||||
create_supplier_with_contact(
|
supplier1.append("portal_users", {"user": "supplier1@gmail.com"})
|
||||||
"Supplier2", "All Supplier Groups", "Supplier 2", "supplier2@gmail.com"
|
supplier1.save()
|
||||||
)
|
supplier2.append("portal_users", {"user": "supplier2@gmail.com"})
|
||||||
|
supplier2.save()
|
||||||
|
|
||||||
po1 = create_purchase_order(supplier="Supplier1")
|
po1 = create_purchase_order(supplier="Supplier1")
|
||||||
po2 = create_purchase_order(supplier="Supplier2")
|
po2 = create_purchase_order(supplier="Supplier2")
|
||||||
|
|
||||||
@ -61,21 +64,6 @@ def create_user(name, email):
|
|||||||
).insert(ignore_if_duplicate=True)
|
).insert(ignore_if_duplicate=True)
|
||||||
|
|
||||||
|
|
||||||
def create_supplier_with_contact(name, group, contact_name, contact_email):
|
|
||||||
supplier = frappe.get_doc(
|
|
||||||
{"doctype": "Supplier", "supplier_name": name, "supplier_group": group}
|
|
||||||
).insert(ignore_if_duplicate=True)
|
|
||||||
|
|
||||||
if not frappe.db.exists("Contact", contact_name + "-1-" + name):
|
|
||||||
new_contact = frappe.new_doc("Contact")
|
|
||||||
new_contact.first_name = contact_name
|
|
||||||
new_contact.is_primary_contact = (True,)
|
|
||||||
new_contact.append("links", {"link_doctype": "Supplier", "link_name": supplier.name})
|
|
||||||
new_contact.append("email_ids", {"email_id": contact_email, "is_primary": 1})
|
|
||||||
|
|
||||||
new_contact.insert(ignore_mandatory=True)
|
|
||||||
|
|
||||||
|
|
||||||
def create_custom_doctype():
|
def create_custom_doctype():
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
{
|
{
|
||||||
|
0
erpnext/utilities/doctype/portal_user/__init__.py
Normal file
0
erpnext/utilities/doctype/portal_user/__init__.py
Normal file
34
erpnext/utilities/doctype/portal_user/portal_user.json
Normal file
34
erpnext/utilities/doctype/portal_user/portal_user.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2023-06-20 14:01:35.362233",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "user",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "User",
|
||||||
|
"options": "User",
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-06-26 14:15:34.695605",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Utilities",
|
||||||
|
"name": "Portal User",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
9
erpnext/utilities/doctype/portal_user/portal_user.py
Normal file
9
erpnext/utilities/doctype/portal_user/portal_user.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class PortalUser(Document):
|
||||||
|
pass
|
Loading…
x
Reference in New Issue
Block a user