@@ -103,7 +103,7 @@ frappe.ui.form.on('Auto Repeat', { | |||
frappe.auto_repeat.render_schedule = function(frm) { | |||
if (!frm.is_dirty() && frm.doc.status !== 'Disabled') { | |||
frm.call("get_auto_repeat_schedule").then(r => { | |||
frm.dashboard.wrapper.empty(); | |||
frm.dashboard.reset(); | |||
frm.dashboard.add_section( | |||
frappe.render_template("auto_repeat_schedule", { | |||
schedule_details: r.message || [] | |||
@@ -83,12 +83,61 @@ class DocType(Document): | |||
if not self.is_new(): | |||
self.before_update = frappe.get_doc('DocType', self.name) | |||
self.setup_fields_to_fetch() | |||
self.validate_field_name_conflicts() | |||
check_email_append_to(self) | |||
if self.default_print_format and not self.custom: | |||
frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) | |||
if frappe.conf.get('developer_mode'): | |||
self.owner = 'Administrator' | |||
self.modified_by = 'Administrator' | |||
def validate_field_name_conflicts(self): | |||
"""Check if field names dont conflict with controller properties and methods""" | |||
core_doctypes = [ | |||
"Custom DocPerm", | |||
"DocPerm", | |||
"Custom Field", | |||
"Customize Form Field", | |||
"DocField", | |||
] | |||
if self.name in core_doctypes: | |||
return | |||
from frappe.model.base_document import get_controller | |||
try: | |||
controller = get_controller(self.name) | |||
except ImportError: | |||
controller = Document | |||
available_objects = {x for x in dir(controller) if isinstance(x, str)} | |||
property_set = { | |||
x for x in available_objects if isinstance(getattr(controller, x, None), property) | |||
} | |||
method_set = { | |||
x for x in available_objects if x not in property_set and callable(getattr(controller, x, None)) | |||
} | |||
for docfield in self.get("fields") or []: | |||
conflict_type = None | |||
field = docfield.fieldname | |||
field_label = docfield.label or docfield.fieldname | |||
if docfield.fieldname in method_set: | |||
conflict_type = "controller method" | |||
if docfield.fieldname in property_set: | |||
conflict_type = "class property" | |||
if conflict_type: | |||
frappe.throw( | |||
_("Fieldname '{0}' conflicting with a {1} of the name {2} in {3}") | |||
.format(field_label, conflict_type, field, self.name) | |||
) | |||
def after_insert(self): | |||
# clear user cache so that on the next reload this doctype is included in boot | |||
clear_user_cache(frappe.session.user) | |||
@@ -1174,11 +1223,19 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): | |||
else: | |||
raise | |||
def check_if_fieldname_conflicts_with_methods(doctype, fieldname): | |||
doc = frappe.get_doc({"doctype": doctype}) | |||
method_list = [method for method in dir(doc) if isinstance(method, str) and callable(getattr(doc, method))] | |||
def check_fieldname_conflicts(doctype, fieldname): | |||
"""Checks if fieldname conflicts with methods or properties""" | |||
if fieldname in method_list: | |||
doc = frappe.get_doc({"doctype": doctype}) | |||
available_objects = [x for x in dir(doc) if isinstance(x, str)] | |||
property_list = [ | |||
x for x in available_objects if isinstance(getattr(type(doc), x, None), property) | |||
] | |||
method_list = [ | |||
x for x in available_objects if x not in property_list and callable(getattr(doc, x)) | |||
] | |||
if fieldname in method_list + property_list: | |||
frappe.throw(_("Fieldname {0} conflicting with meta object").format(fieldname)) | |||
def clear_linked_doctype_cache(): | |||
@@ -64,8 +64,8 @@ class CustomField(Document): | |||
self.translatable = 0 | |||
if not self.flags.ignore_validate: | |||
from frappe.core.doctype.doctype.doctype import check_if_fieldname_conflicts_with_methods | |||
check_if_fieldname_conflicts_with_methods(self.dt, self.fieldname) | |||
from frappe.core.doctype.doctype.doctype import check_fieldname_conflicts | |||
check_fieldname_conflicts(self.dt, self.fieldname) | |||
def on_update(self): | |||
if not frappe.flags.in_setup_wizard: | |||
@@ -34,8 +34,9 @@ def get_controller(doctype): | |||
from frappe.model.document import Document | |||
from frappe.utils.nestedset import NestedSet | |||
module_name, custom = frappe.db.get_value("DocType", doctype, ("module", "custom"), cache=True) \ | |||
or ["Core", False] | |||
module_name, custom = frappe.db.get_value( | |||
"DocType", doctype, ("module", "custom"), cache=True | |||
) or ["Core", False] | |||
if custom: | |||
if frappe.db.field_exists("DocType", "is_tree"): | |||
@@ -199,10 +199,39 @@ def getseries(key, digits): | |||
def revert_series_if_last(key, name, doc=None): | |||
if ".#" in key: | |||
""" | |||
Reverts the series for particular naming series: | |||
* key is naming series - SINV-.YYYY-.#### | |||
* name is actual name - SINV-2021-0001 | |||
1. This function split the key into two parts prefix (SINV-YYYY) & hashes (####). | |||
2. Use prefix to get the current index of that naming series from Series table | |||
3. Then revert the current index. | |||
*For custom naming series:* | |||
1. hash can exist anywhere, if it exist in hashes then it take normal flow. | |||
2. If hash doesn't exit in hashes, we get the hash from prefix, then update name and prefix accordingly. | |||
*Example:* | |||
1. key = SINV-.YYYY.- | |||
* If key doesn't have hash it will add hash at the end | |||
* prefix will be SINV-YYYY based on this will get current index from Series table. | |||
2. key = SINV-.####.-2021 | |||
* now prefix = SINV-#### and hashes = 2021 (hash doesn't exist) | |||
* will search hash in key then accordingly get prefix = SINV- | |||
3. key = ####.-2021 | |||
* prefix = #### and hashes = 2021 (hash doesn't exist) | |||
* will search hash in key then accordingly get prefix = "" | |||
""" | |||
if ".#" in key: | |||
prefix, hashes = key.rsplit(".", 1) | |||
if "#" not in hashes: | |||
return | |||
# get the hash part from the key | |||
hash = re.search("#+", key) | |||
if not hash: | |||
return | |||
name = name.replace(hashes, "") | |||
prefix = prefix.replace(hash.group(), "") | |||
else: | |||
prefix = key | |||
@@ -474,14 +474,19 @@ frappe.Application = Class.extend({ | |||
$('<link rel="icon" href="' + link + '" type="image/x-icon">').appendTo("head"); | |||
}, | |||
trigger_primary_action: function() { | |||
if(window.cur_dialog && cur_dialog.display) { | |||
// trigger primary | |||
cur_dialog.get_primary_btn().trigger("click"); | |||
} else if(cur_frm && cur_frm.page.btn_primary.is(':visible')) { | |||
cur_frm.page.btn_primary.trigger('click'); | |||
} else if(frappe.container.page.save_action) { | |||
frappe.container.page.save_action(); | |||
} | |||
// to trigger change event on active input before triggering primary action | |||
$(document.activeElement).blur(); | |||
// wait for possible JS validations triggered after blur (it might change primary button) | |||
setTimeout(() => { | |||
if (window.cur_dialog && cur_dialog.display) { | |||
// trigger primary | |||
cur_dialog.get_primary_btn().trigger("click"); | |||
} else if (cur_frm && cur_frm.page.btn_primary.is(':visible')) { | |||
cur_frm.page.btn_primary.trigger('click'); | |||
} else if (frappe.container.page.save_action) { | |||
frappe.container.page.save_action(); | |||
} | |||
}, 100); | |||
}, | |||
set_rtl: function() { | |||
@@ -70,9 +70,9 @@ class TestNaming(unittest.TestCase): | |||
name = 'TEST-{}-00001'.format(year) | |||
frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 1)""", (series,)) | |||
revert_series_if_last(key, name) | |||
count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] | |||
current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] | |||
self.assertEqual(count.get('current'), 0) | |||
self.assertEqual(current_index.get('current'), 0) | |||
frappe.db.sql("""delete from `tabSeries` where name = %s""", series) | |||
series = 'TEST-{}-'.format(year) | |||
@@ -80,9 +80,9 @@ class TestNaming(unittest.TestCase): | |||
name = 'TEST-{}-00002'.format(year) | |||
frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 2)""", (series,)) | |||
revert_series_if_last(key, name) | |||
count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] | |||
current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] | |||
self.assertEqual(count.get('current'), 1) | |||
self.assertEqual(current_index.get('current'), 1) | |||
frappe.db.sql("""delete from `tabSeries` where name = %s""", series) | |||
series = 'TEST-' | |||
@@ -91,7 +91,29 @@ class TestNaming(unittest.TestCase): | |||
frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) | |||
frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) | |||
revert_series_if_last(key, name) | |||
count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] | |||
current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] | |||
self.assertEqual(count.get('current'), 2) | |||
self.assertEqual(current_index.get('current'), 2) | |||
frappe.db.sql("""delete from `tabSeries` where name = %s""", series) | |||
series = 'TEST1-' | |||
key = 'TEST1-.#####.-2021-22' | |||
name = 'TEST1-00003-2021-22' | |||
frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) | |||
frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) | |||
revert_series_if_last(key, name) | |||
current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] | |||
self.assertEqual(current_index.get('current'), 2) | |||
frappe.db.sql("""delete from `tabSeries` where name = %s""", series) | |||
series = '' | |||
key = '.#####.-2021-22' | |||
name = '00003-2021-22' | |||
frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) | |||
frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) | |||
revert_series_if_last(key, name) | |||
current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] | |||
self.assertEqual(current_index.get('current'), 2) | |||
frappe.db.sql("""delete from `tabSeries` where name = %s""", series) |