V13 "Mandatory Depends On" documentation/guide wanted

Hello Community. Currently testing out the v13 and loving it! Awesome work, again!

However, I could make use if the “Mandatory Depends On” feature on certain fields. For example, I could make a “item part number” field become mandatory when “item has a part number” is checked or something. I’ve tried few methods, but couldn’t get it to work.

I’ve seen the Documentation PR, but it had no explanation.

Any help is appreciated :smiley:

Hi @iMoshi,

Mandatory Depends On works just like the Depends On property. Just setting
"mandatory_depends_on": "eval:doc.item_has_part_number"
for the “item part number” field should work!

4 Likes

per my experience and checking the code, the mandatory depends_on only works on regular doctype not the child doctype.

created a new issue with attached code change, hopefully someone from the core team can help to make a PR accordingly.
https://github.com/frappe/frappe/issues/10259

1 Like

Oh wow, thank you so much!

I’ve got your code in my Frappe, and the “Mandatory Depends On” now works in child DocTypes, but now I’m unable to Save the doc just so you know. Could be me though :stuck_out_tongue_winking_eye:

so what is the console error?

Hello there. It’s been a while and I forgot to reply to the thread.

I’m successfully using your code, and it’s working beautifully. Although, I figured you forgot to declare the reqd in the script, that’s all :slight_smile:

Thank you so much (so late), and stay safe!

So, I am trying to use your fix.

The client side works after the tweak noticed by @iMoshi .

Alas, the server side fails with:

04:57:40 web.1            | Traceback (most recent call last):
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/desk/form/save.py", line 21, in savedocs
04:57:40 web.1            |     doc.save()
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/model/document.py", line 281, in save
04:57:40 web.1            |     return self._save(*args, **kwargs)
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/model/document.py", line 319, in _save
04:57:40 web.1            |     self._validate()
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/model/document.py", line 489, in _validate
04:57:40 web.1            |     self._validate_mandatory()
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/model/document.py", line 778, in _validate_mandatory
04:57:40 web.1            |     raise frappe.MandatoryError('[{doctype}, {name}]: {fields}'.format(
04:57:40 web.1            | frappe.exceptions.MandatoryError: [Returnable, RTN-ITM000003]: from_customer
04:57:40 web.1            | 
04:57:40 web.1            | Traceback (most recent call last):
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/app.py", line 64, in application
04:57:40 web.1            |     response = frappe.api.handle()
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/api.py", line 58, in handle
04:57:40 web.1            |     return frappe.handler.handle()
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/handler.py", line 30, in handle
04:57:40 web.1            |     data = execute_cmd(cmd)
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/handler.py", line 69, in execute_cmd
04:57:40 web.1            |     return frappe.call(method, **frappe.form_dict)
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/__init__.py", line 1086, in call
04:57:40 web.1            |     return fn(*args, **newargs)
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/desk/form/save.py", line 21, in savedocs
04:57:40 web.1            |     doc.save()
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/model/document.py", line 281, in save
04:57:40 web.1            |     return self._save(*args, **kwargs)
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/model/document.py", line 319, in _save
04:57:40 web.1            |     self._validate()
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/model/document.py", line 489, in _validate
04:57:40 web.1            |     self._validate_mandatory()
04:57:40 web.1            |   File "/home/erpdev/frappe-bench-DYPW/apps/frappe/frappe/model/document.py", line 778, in _validate_mandatory
04:57:40 web.1            |     raise frappe.MandatoryError('[{doctype}, {name}]: {fields}'.format(
04:57:40 web.1            | frappe.exceptions.MandatoryError: [Returnable, RTN-ITM000003]: from_customer

Here’s the code for _validate_mandatory:

	def _validate_mandatory(self):
		if self.flags.ignore_mandatory:
			return

		missing = self._get_missing_mandatory_fields()
		for d in self.get_all_children():
			missing.extend(d._get_missing_mandatory_fields())

		if not missing:
			return

		for fieldname, msg in missing:
			msgprint(msg)

		if frappe.flags.print_messages:
			print(self.as_json().encode("utf-8"))

		raise frappe.MandatoryError('[{doctype}, {name}]: {fields}'.format(
			fields=", ".join((each[0] for each in missing)),
			doctype=self.doctype,
			name=self.name))

As you can see the Doc collects any missing mandatory fields for itself and then for each child Doc, by calling _get_missing_mandatory_fields on each of them. Here’s the code for _get_missing_mandatory_fields:

	def _get_missing_mandatory_fields(self):
		"""Get mandatory fields that do not have any values"""
		def get_msg(df):
			if df.fieldtype in table_fields:
				return "{}: {}: {}".format(_("Error"), _("Data missing in table"), _(df.label))

			elif self.parentfield:
				return "{}: {} {} #{}: {}: {}".format(_("Error"), frappe.bold(_(self.doctype)),
					_("Row"), self.idx, _("Value missing for"), _(df.label))

			else:
				return _("Error: Value missing for {0}: {1}").format(_(df.parent), _(df.label))

		missing = []

		for df in self.meta.get("fields", {"reqd": ('=', 1)}):
			if self.get(df.fieldname) in (None, []) or not strip_html(cstr(self.get(df.fieldname))).strip():
				missing.append((df.fieldname, get_msg(df)))

		# check for missing parent and parenttype
		if self.meta.istable:
			for fieldname in ("parent", "parenttype"):
				if not self.get(fieldname):
					missing.append((fieldname, get_msg(frappe._dict(label=fieldname))))

		return missing

Specifically, any field, where reqd is set, must have data, or it is thrown on the missing pile:

		for df in self.meta.get("fields", {"reqd": ('=', 1)}):
			if self.get(df.fieldname) in (None, []) or not strip_html(cstr(self.get(df.fieldname))).strip():
				missing.append((df.fieldname, get_msg(df)))

I have yet to dig deeper into the code but I can see that the model never even tries to work with mandatory_depends_on:

erpdev@erpserver:~/frappe-bench/apps/frappe/frappe/model$ grep -R mandatory_depends_on
erpdev@erpserver:~/frappe-bench/apps/frappe/frappe/model$ 

I find this hard to understand, since, while there are literally dozens of places that set the value of mandatory_depends_on, there doesn’t seem to be any code anywhere that actually handles it:

erpdev@erpserver:~/frappe-bench/apps$ grep --include="*.py" -R mandatory_depends_on;
frappe/frappe/custom/doctype/customize_form/customize_form.py:	'mandatory_depends_on': 'Data',
frappe/frappe/core/doctype/doctype/test_doctype.py:			"ifnull(mandatory_depends_on, '')": ("!=", ''),
frappe/frappe/core/doctype/doctype/test_doctype.py:			fields=["parent", "depends_on", "collapsible_depends_on", "mandatory_depends_on",\
frappe/frappe/core/doctype/doctype/test_doctype.py:			for depends_on in ["depends_on", "collapsible_depends_on", "mandatory_depends_on", "read_only_depends_on"]:
frappe/frappe/core/doctype/doctype/doctype.py:		depends_on_fields = ["depends_on", "collapsible_depends_on", "mandatory_depends_on", "read_only_depends_on"]
erpdev@erpserver:~/frappe-bench/apps$

My questions:

  • Am I using the wrong code version?
  • Is this how the code is expected to be from now on?
  • if so: will mandatory_depends_on be deprecated?

Platform:

erpdev@erpserver:~/frappe-bench$ lsb_release -d
Description:	Ubuntu 20.04.1 LTS
erpdev@erpserver:~/frappe-bench$ bench --version
5.2.1
erpdev@erpserver:~/frappe-bench$ bench version
erpnext 13.0.0-beta.4
frappe 13.0.0-beta.5
erpdev@erpserver:~$ 

Assuming you have modded your save.js and layout.js according to Szufisher’s post, just add var reqd = false; into the var check_mandatory = function () { and run the bench migrate in your bench :slight_smile:

Good luck.