I am encountering an impossible bug.
I have created a Server Script
to handle Before Submit
of a Delivery Note
.
The required functionality is record a special Material Transfer
type Stock Entry
Today for the first time after more than 1500 executions, over the last two months, the script suddenly failed with the error …
"frappe.exceptions.UpdateAfterSubmitError:
____ Not allowed to change Basic Rate (as per Stock UOM) after submission"
… as you can see here below:
: : : : : :
File "apps/frappe/frappe/model/document.py", line 868, in validate_update_after_submit
d._validate_update_after_submit()
File "apps/frappe/frappe/model/base_document.py", line 857, in _validate_update_after_submit
frappe.throw(
File "apps/frappe/frappe/__init__.py", line 504, in throw
msgprint(
File "apps/frappe/frappe/__init__.py", line 479, in msgprint
_raise_exception()
File "apps/frappe/frappe/__init__.py", line 434, in _raise_exception
raise raise_exception(msg)
frappe.exceptions.UpdateAfterSubmitError:
Not allowed to change Basic Rate (as per Stock UOM) after submission
After poking around for hours I edited the Frappe source code to add print statement line…
print(f"df.label: {df.label} || self_value: ({self_value}) versus db_value: ({db_value})")
… just before the thrown exception, like this …
File : frappe/frappe/model/base_document.py
for key in self.as_dict():
df = self.meta.get_field(key)
db_value = db_values.get(key)
if df and not df.allow_on_submit and (self.get(key) or db_value):
if df.fieldtype in table_fields:
# just check if the table size has changed
# individual fields will be checked in the loop for children
self_value = len(self.get(key))
db_value = len(db_value)
else:
self_value = self.get_value(key)
print(f"df.label: {df.label} || self_value: ({self_value}) versus db_value: ({db_value})")
if self_value != db_value:
frappe.throw(
_("Not allowed to change {0} after submission").format(df.label),
frappe.UpdateAfterSubmitError,
)
When I try to submit the Delivery Note I see:
: : : : : :
: : : : : :
df.label: Qty || self_value: (8.0) versus db_value: (8.0)
df.label: Qty as per Stock UOM || self_value: (8.0) versus db_value: (8.0)
df.label: UOM || self_value: (Unidad(es)) versus db_value: (Unidad(es))
df.label: Stock UOM || self_value: (Unidad(es)) versus db_value: (Unidad(es))
df.label: Conversion Factor || self_value: (1.0) versus db_value: (1.0)
df.label: Basic Rate (as per Stock UOM) || self_value: (0.004792216125) versus db_value: (0.004792216)
Traceback (most recent call last):
File "apps/frappe/frappe/app.py", line 69, in application
response = frappe.api.handle()
: : : : : :
: : : : : :
The critical line is the last one before the stack trace:
df.label: Basic Rate (as per Stock UOM) ||
self_value: (0.004792216125) versus db_value: (0.004792216)
To try to understand that, I used this MariaDB query …
SELECT
parent
, LEFT(item_code, 5) as item
, idx
, basic_rate
FROM
`tabStock Entry Detail`
WHERE item_code like "Ficha%"
;
… to look at all past such Stock Entries created from my Server Script, with this result:
+--------------------+-------+-----+-------------+
| parent | item | idx | basic_rate |
+--------------------+-------+-----+-------------+
| MAT-STE-2022-00001 | FICHA | 2 | 0.000000000 |
: : : : : :
: : : : : :
| MAT-STE-2022-01521 | FICHA | 1 | 0.000000623 |
| MAT-STE-2022-01522 | FICHA | 1 | 0.000000000 |
| MAT-STE-2022-01523 | FICHA | 1 | 0.000000498 |
: : : : : :
: : : : : :
| MAT-STE-2022-01533 | FICHA | 1 | 0.000000416 |
| MAT-STE-2022-01534 | FICHA | 1 | 0.000000000 |
| MAT-STE-2022-01535 | FICHA | 1 | 0.000000623 |
| MAT-STE-2022-01536 | FICHA | 1 | 0.007667172 |
+--------------------+-------+-----+-------------+
1588 rows in set (0.017 sec)
How is this possible?
Here’s the relevant code in my Server Script:
se = { 'doctype': 'Stock Entry', 'docstatus': 1, 'stock_entry_type': 'Material Transfer', "items": [] }
for row in reversed_row_numbers:
dnItem = doc.items[row]
newRow = {
't_warehouse': whse.name,
's_warehouse': dnItem.warehouse,
'item_code': dnItem.item_code,
'qty': dnItem.qty,
# 'basic_rate': 0.00,
'allow_zero_valuation_rate': 1,
'serial_no': dnItem.serial_no
}
se['items'].append(newRow)
doc.items.remove(dnItem)
se["from_warehouse"] = dnItem.warehouse;
se["to_warehouse"] = whse.name;
material_transfer = frappe.get_doc(se).insert()
material_transfer.save()
My Date and Number Format
settings specify Float = 9
, but evidently the random number generated can be Float = 12
and causes this first “collision” after >1500 executions.
Even more annoying … it is not random!!! because I get the value 0.004792216125 every single time.
Questions:
- From where does
Basic Rate (as per Stock UOM)
get its value? - How can I force a value of zero?
- Why do I get an “after submission” fault, if I am inserting a new record?
Update 2022/10/01:
I did determine that the fault is from the Stock Entry, not the Delivery Note, by commenting out the last lines of my Sever Script:
# material_transfer = frappe.get_doc(se).insert()
# material_transfer.save()
I have also discovered a workaround; insert with 'docstatus': 0
instead of 'docstatus': 1
But, the workaround doesn’t address the underlying weirdness of random valuations.