mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-08 20:21:47 +00:00
fix(stock): read quality inspection readings in the user's number format
readings are Data fields, so they are parsed server side. parse_float only
swapped the separators for "#.###,##", so in the space grouped "# ###,##"
(polish) a reading of 1,15 was read as 115, fell outside the acceptance
range and silently rejected the inspection. strip whatever the group
separator is and normalise whatever the decimal separator is instead.
it also read the global number format, while the desk formats numbers with
the user's own. a user whose locale differs from the site therefore typed
readings in a format the server did not parse them with. read the user
default, which falls back to the global one.
a reading that is not a valid number in that format is now rejected with an
error instead of being read as a different number.
(cherry picked from commit e74c0a3cdb)
This commit is contained in:
@@ -82,6 +82,7 @@ class QualityInspection(Document):
|
||||
reading.status = "Accepted"
|
||||
|
||||
if self.readings:
|
||||
self.validate_reading_number_format()
|
||||
self.inspect_and_set_status()
|
||||
|
||||
self.validate_inspection_required()
|
||||
@@ -280,6 +281,37 @@ class QualityInspection(Document):
|
||||
)
|
||||
break
|
||||
|
||||
def validate_reading_number_format(self):
|
||||
"""Reject readings written in a different number format than the user's.
|
||||
|
||||
They would otherwise be misread rather than refused, silently rejecting an
|
||||
inspection whose readings are in fact within the acceptance range."""
|
||||
number_format = get_reading_number_format()
|
||||
decimal_str, comma_str, _precision = get_number_format_info(number_format)
|
||||
|
||||
for reading in self.readings:
|
||||
if not cint(reading.numeric):
|
||||
continue
|
||||
|
||||
for i in range(1, 11):
|
||||
value = reading.get("reading_" + str(i))
|
||||
if value is None or not value.strip():
|
||||
continue
|
||||
|
||||
if not is_valid_number(value, decimal_str, comma_str):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Reading {1} {2} is not a valid number in the {3} number format. Use {4} as the decimal separator."
|
||||
).format(
|
||||
reading.idx,
|
||||
i,
|
||||
frappe.bold(value),
|
||||
frappe.bold(number_format),
|
||||
frappe.bold(decimal_str),
|
||||
),
|
||||
title=_("Invalid Reading"),
|
||||
)
|
||||
|
||||
def set_status_based_on_acceptance_values(self, reading):
|
||||
if not cint(reading.numeric):
|
||||
reading_value = reading.get("reading_value") or ""
|
||||
@@ -507,17 +539,56 @@ def make_quality_inspection(source_name, target_doc=None):
|
||||
return doc
|
||||
|
||||
|
||||
def get_reading_number_format() -> str:
|
||||
"""Number format the user enters readings in.
|
||||
|
||||
User defaults fall back to the global default, so this is the same format the
|
||||
user's desk formats numbers with."""
|
||||
return frappe.defaults.get_user_default("number_format") or "#,###.##"
|
||||
|
||||
|
||||
def is_valid_number(num: str, decimal_str: str, comma_str: str) -> bool:
|
||||
num = num.strip().lstrip("+-")
|
||||
integer_part, fraction = num, ""
|
||||
|
||||
if decimal_str:
|
||||
if num.count(decimal_str) > 1:
|
||||
return False
|
||||
integer_part, _, fraction = num.partition(decimal_str)
|
||||
|
||||
if fraction and not fraction.isdigit():
|
||||
return False
|
||||
|
||||
if not integer_part:
|
||||
return bool(fraction)
|
||||
|
||||
if comma_str and comma_str in integer_part:
|
||||
groups = integer_part.split(comma_str)
|
||||
if any(not group.isdigit() for group in groups):
|
||||
return False
|
||||
|
||||
# the group just before the decimal separator is always 3 digits long
|
||||
if not 1 <= len(groups[0]) <= 3 or len(groups[-1]) != 3:
|
||||
return False
|
||||
|
||||
# 3 digits per group, or 2 in the indian format
|
||||
return all(len(group) in (2, 3) for group in groups[1:-1])
|
||||
|
||||
return integer_part.isdigit()
|
||||
|
||||
|
||||
def parse_float(num: str) -> float:
|
||||
"""Since reading_# fields are `Data` field they might contain number which
|
||||
is representation in user's prefered number format instead of machine
|
||||
readable format. This function converts them to machine readable format."""
|
||||
|
||||
number_format = frappe.db.get_default("number_format") or "#,###.##"
|
||||
number_format = get_reading_number_format()
|
||||
decimal_str, comma_str, _number_format_precision = get_number_format_info(number_format)
|
||||
|
||||
if decimal_str == "," and comma_str == ".":
|
||||
num = num.replace(",", "#$")
|
||||
num = num.replace(".", ",")
|
||||
num = num.replace("#$", ".")
|
||||
if comma_str:
|
||||
num = num.replace(comma_str, "")
|
||||
|
||||
if decimal_str and decimal_str != ".":
|
||||
num = num.replace(decimal_str, ".")
|
||||
|
||||
return flt(num)
|
||||
|
||||
Reference in New Issue
Block a user