@@ -103,7 +103,7 @@ frappe.ui.form.on('Auto Repeat', { | |||||
frappe.auto_repeat.render_schedule = function(frm) { | frappe.auto_repeat.render_schedule = function(frm) { | ||||
if (!frm.is_dirty() && frm.doc.status !== 'Disabled') { | if (!frm.is_dirty() && frm.doc.status !== 'Disabled') { | ||||
frm.call("get_auto_repeat_schedule").then(r => { | frm.call("get_auto_repeat_schedule").then(r => { | ||||
frm.dashboard.wrapper.empty(); | |||||
frm.dashboard.reset(); | |||||
frm.dashboard.add_section( | frm.dashboard.add_section( | ||||
frappe.render_template("auto_repeat_schedule", { | frappe.render_template("auto_repeat_schedule", { | ||||
schedule_details: r.message || [] | schedule_details: r.message || [] | ||||
@@ -83,12 +83,61 @@ class DocType(Document): | |||||
if not self.is_new(): | if not self.is_new(): | ||||
self.before_update = frappe.get_doc('DocType', self.name) | self.before_update = frappe.get_doc('DocType', self.name) | ||||
self.setup_fields_to_fetch() | self.setup_fields_to_fetch() | ||||
self.validate_field_name_conflicts() | |||||
check_email_append_to(self) | check_email_append_to(self) | ||||
if self.default_print_format and not self.custom: | if self.default_print_format and not self.custom: | ||||
frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) | 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): | def after_insert(self): | ||||
# clear user cache so that on the next reload this doctype is included in boot | # clear user cache so that on the next reload this doctype is included in boot | ||||
clear_user_cache(frappe.session.user) | clear_user_cache(frappe.session.user) | ||||
@@ -1174,11 +1223,19 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): | |||||
else: | else: | ||||
raise | 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)) | frappe.throw(_("Fieldname {0} conflicting with meta object").format(fieldname)) | ||||
def clear_linked_doctype_cache(): | def clear_linked_doctype_cache(): | ||||
@@ -64,8 +64,8 @@ class CustomField(Document): | |||||
self.translatable = 0 | self.translatable = 0 | ||||
if not self.flags.ignore_validate: | 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): | def on_update(self): | ||||
if not frappe.flags.in_setup_wizard: | if not frappe.flags.in_setup_wizard: | ||||
@@ -34,8 +34,9 @@ def get_controller(doctype): | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe.utils.nestedset import NestedSet | 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 custom: | ||||
if frappe.db.field_exists("DocType", "is_tree"): | 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): | 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) | prefix, hashes = key.rsplit(".", 1) | ||||
if "#" not in hashes: | 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: | else: | ||||
prefix = key | prefix = key | ||||
@@ -474,14 +474,19 @@ frappe.Application = Class.extend({ | |||||
$('<link rel="icon" href="' + link + '" type="image/x-icon">').appendTo("head"); | $('<link rel="icon" href="' + link + '" type="image/x-icon">').appendTo("head"); | ||||
}, | }, | ||||
trigger_primary_action: function() { | 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() { | set_rtl: function() { | ||||
@@ -70,9 +70,9 @@ class TestNaming(unittest.TestCase): | |||||
name = 'TEST-{}-00001'.format(year) | name = 'TEST-{}-00001'.format(year) | ||||
frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 1)""", (series,)) | frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 1)""", (series,)) | ||||
revert_series_if_last(key, name) | 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) | frappe.db.sql("""delete from `tabSeries` where name = %s""", series) | ||||
series = 'TEST-{}-'.format(year) | series = 'TEST-{}-'.format(year) | ||||
@@ -80,9 +80,9 @@ class TestNaming(unittest.TestCase): | |||||
name = 'TEST-{}-00002'.format(year) | name = 'TEST-{}-00002'.format(year) | ||||
frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 2)""", (series,)) | frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 2)""", (series,)) | ||||
revert_series_if_last(key, name) | 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) | frappe.db.sql("""delete from `tabSeries` where name = %s""", series) | ||||
series = 'TEST-' | series = 'TEST-' | ||||
@@ -91,7 +91,29 @@ class TestNaming(unittest.TestCase): | |||||
frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) | frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) | ||||
frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) | frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) | ||||
revert_series_if_last(key, name) | 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) | frappe.db.sql("""delete from `tabSeries` where name = %s""", series) |