V13 "Mandatory Depends On" documentation/guide wanted

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:~$