|
|
@@ -0,0 +1,119 @@
|
|
|
+"""OA 表单设计器:金额控件 form_data 校验"""
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+import math
|
|
|
+from typing import Any, Dict, List, Optional, Tuple
|
|
|
+
|
|
|
+MAX_MONEY = 999999999999.99
|
|
|
+
|
|
|
+
|
|
|
+def _walk_rules(rules: Any, out: List[dict]) -> None:
|
|
|
+ if not isinstance(rules, list):
|
|
|
+ return
|
|
|
+ for r in rules:
|
|
|
+ if not isinstance(r, dict):
|
|
|
+ continue
|
|
|
+ out.append(r)
|
|
|
+ children = r.get("children") or (r.get("props") or {}).get("rule") or []
|
|
|
+ if isinstance(children, list):
|
|
|
+ _walk_rules(children, out)
|
|
|
+
|
|
|
+
|
|
|
+def _schema_rules(schema: Any) -> List[dict]:
|
|
|
+ if isinstance(schema, list):
|
|
|
+ out: List[dict] = []
|
|
|
+ _walk_rules(schema, out)
|
|
|
+ return out
|
|
|
+ if not isinstance(schema, dict):
|
|
|
+ return []
|
|
|
+ out: List[dict] = []
|
|
|
+ _walk_rules(schema.get("rule") or schema.get("rules") or [], out)
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+def _rule_props(rule: dict) -> dict:
|
|
|
+ props = rule.get("props")
|
|
|
+ return props if isinstance(props, dict) else {}
|
|
|
+
|
|
|
+
|
|
|
+def _field_label(rule: dict) -> str:
|
|
|
+ return str(rule.get("title") or rule.get("label") or rule.get("field") or "金额")
|
|
|
+
|
|
|
+
|
|
|
+def _is_required(rule: dict) -> bool:
|
|
|
+ props = _rule_props(rule)
|
|
|
+ if rule.get("$required") is True:
|
|
|
+ return True
|
|
|
+ if rule.get("required") is True:
|
|
|
+ return True
|
|
|
+ if props.get("required") is True:
|
|
|
+ return True
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
+def _is_empty(value: Any) -> bool:
|
|
|
+ return value is None or value == ""
|
|
|
+
|
|
|
+
|
|
|
+def _parse_money(value: Any) -> Tuple[Optional[float], Optional[str]]:
|
|
|
+ """解析金额值。成功返回 (number, None);失败返回 (None, error_key)。"""
|
|
|
+ if _is_empty(value):
|
|
|
+ return None, None
|
|
|
+ if isinstance(value, bool):
|
|
|
+ return None, "invalid"
|
|
|
+ if isinstance(value, (int, float)):
|
|
|
+ n = float(value)
|
|
|
+ elif isinstance(value, str):
|
|
|
+ text = value.replace(",", "").strip()
|
|
|
+ if not text:
|
|
|
+ return None, None
|
|
|
+ try:
|
|
|
+ n = float(text)
|
|
|
+ except ValueError:
|
|
|
+ return None, "invalid"
|
|
|
+ else:
|
|
|
+ return None, "invalid"
|
|
|
+
|
|
|
+ if not math.isfinite(n):
|
|
|
+ return None, "invalid"
|
|
|
+ if n < 0:
|
|
|
+ return None, "negative"
|
|
|
+ if n > MAX_MONEY:
|
|
|
+ return None, "too_large"
|
|
|
+ return n, None
|
|
|
+
|
|
|
+
|
|
|
+def _validate_money_rule(rule: dict, data: Dict[str, Any]) -> List[str]:
|
|
|
+ field = str(rule.get("field") or "").strip()
|
|
|
+ if not field:
|
|
|
+ return []
|
|
|
+
|
|
|
+ label = _field_label(rule)
|
|
|
+ required = _is_required(rule)
|
|
|
+ value = data.get(field)
|
|
|
+ errors: List[str] = []
|
|
|
+
|
|
|
+ if _is_empty(value):
|
|
|
+ if required:
|
|
|
+ errors.append(f"「{label}」为必填金额字段")
|
|
|
+ return errors
|
|
|
+
|
|
|
+ _, err_key = _parse_money(value)
|
|
|
+ if err_key == "negative":
|
|
|
+ errors.append(f"「{label}」金额不能为负数")
|
|
|
+ elif err_key == "too_large":
|
|
|
+ errors.append("金额过大")
|
|
|
+ elif err_key == "invalid":
|
|
|
+ errors.append(f"「{label}」金额格式不正确")
|
|
|
+ return errors
|
|
|
+
|
|
|
+
|
|
|
+def validate_money_form_values(schema: Any, form_data: Optional[Dict[str, Any]]) -> List[str]:
|
|
|
+ """校验 form_data 中金额控件的值格式,返回中文错误列表(空表示通过)。"""
|
|
|
+ data = form_data or {}
|
|
|
+ errors: List[str] = []
|
|
|
+ for rule in _schema_rules(schema):
|
|
|
+ if str(rule.get("type") or "") != "money":
|
|
|
+ continue
|
|
|
+ errors.extend(_validate_money_rule(rule, data))
|
|
|
+ return errors
|