Random numbers in Basic Rate (as per Stock UOM)

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:

  1. From where does Basic Rate (as per Stock UOM) get its value?
  2. How can I force a value of zero?
  3. 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.


Are you sure the error is from Stock Entry and not from the Delivery Note itself?

Hi!

Thanks for taking a look, and … yeah, valid question!

I should have mentioned that I’d already confirmed that. Please see my update.

I finally understand my mistake with this.

I looked at it for so long without noticing the obvious – the created Stock Entry “item” actually consists of serial numbered items! The randomness creeps in when creating Serial Numbers through the REST API without specifying a purchase price for each one.

I have the answer to my first two questions. I assume the third answer would have to do with internal validations of ERPNext.

1 Like

The “after submission” error is likely tied to how ERPNext validates its documents. Even though you’re creating a new record, it seems the system is treating it as an update due to how the docstatus is being handled. Using docstatus zero as you’ve done is a good way to sidestep that issue, but it doesn’t explain the underlying problem with validation.
When things get this weird, I like to take a step back and try something unrelated to clear my head. Tools like yes no tarot or other simple decision-making apps have been surprisingly helpful for moments like this—just something light to break the cycle and refocus.