|
|
@@ -0,0 +1,114 @@
|
|
|
+"""关联审批单:字段扁平化与 form_data 抽链"""
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+from typing import Any, Dict, List, Optional, Sequence, Tuple
|
|
|
+
|
|
|
+LAYOUT_SKIP = frozenset(
|
|
|
+ {"fcRow", "fcTable", "col", "row", "elCard", "space", "divider", "html", "title", "text", "tag"}
|
|
|
+)
|
|
|
+UPLOAD_TYPES = frozenset({"upload", "fcUpload", "image", "oaAttachment"})
|
|
|
+LONG_TYPES = frozenset({"textarea", "input"})
|
|
|
+
|
|
|
+
|
|
|
+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 = []
|
|
|
+ _walk_rules(schema.get("rule") or schema.get("rules") or [], out)
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+def _format_value(typ: str, raw: Any) -> Tuple[str, bool]:
|
|
|
+ """returns (text, fullWidth)"""
|
|
|
+ if typ in UPLOAD_TYPES:
|
|
|
+ if raw is None or raw == "" or raw == []:
|
|
|
+ n = 0
|
|
|
+ elif isinstance(raw, list):
|
|
|
+ n = len(raw)
|
|
|
+ else:
|
|
|
+ n = len([x for x in str(raw).split(",") if x.strip()])
|
|
|
+ return f"{n} 个", False
|
|
|
+ if typ == "relatedApproval":
|
|
|
+ if isinstance(raw, list):
|
|
|
+ n = len([x for x in raw if str(x).strip()])
|
|
|
+ elif raw:
|
|
|
+ n = 1
|
|
|
+ else:
|
|
|
+ n = 0
|
|
|
+ return f"已关联 {n} 条", False
|
|
|
+ if isinstance(raw, list):
|
|
|
+ text = "、".join(str(x) for x in raw if x is not None and str(x) != "")
|
|
|
+ elif isinstance(raw, dict):
|
|
|
+ text = str(raw.get("label") or raw.get("value") or raw)
|
|
|
+ else:
|
|
|
+ text = "" if raw is None else str(raw)
|
|
|
+ full = typ in LONG_TYPES and len(text) > 40
|
|
|
+ return text, full
|
|
|
+
|
|
|
+
|
|
|
+def flatten_form_fields_for_summary(schema: Any, form_data: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
|
+ data = form_data or {}
|
|
|
+ out: List[Dict[str, Any]] = []
|
|
|
+ for rule in _schema_rules(schema):
|
|
|
+ typ = str(rule.get("type") or "")
|
|
|
+ if not typ or typ in LAYOUT_SKIP:
|
|
|
+ continue
|
|
|
+ field = str(rule.get("field") or "").strip()
|
|
|
+ if not field:
|
|
|
+ continue
|
|
|
+ label = str(rule.get("title") or rule.get("label") or field)
|
|
|
+ text, full = _format_value(typ, data.get(field))
|
|
|
+ item: Dict[str, Any] = {"label": label, "value": text}
|
|
|
+ if full:
|
|
|
+ item["fullWidth"] = True
|
|
|
+ out.append(item)
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+def extract_related_approval_links(
|
|
|
+ schema: Any, form_data: Optional[Dict[str, Any]]
|
|
|
+) -> List[Tuple[str, str]]:
|
|
|
+ """Ordered unique (field_key, to_instance_id)."""
|
|
|
+ data = form_data or {}
|
|
|
+ seen = set()
|
|
|
+ links: List[Tuple[str, str]] = []
|
|
|
+ for rule in _schema_rules(schema):
|
|
|
+ if str(rule.get("type") or "") != "relatedApproval":
|
|
|
+ continue
|
|
|
+ field = str(rule.get("field") or "").strip()
|
|
|
+ if not field:
|
|
|
+ continue
|
|
|
+ raw = data.get(field)
|
|
|
+ ids: Sequence[Any]
|
|
|
+ if isinstance(raw, list):
|
|
|
+ ids = raw
|
|
|
+ elif raw is None or raw == "":
|
|
|
+ ids = []
|
|
|
+ else:
|
|
|
+ ids = [raw]
|
|
|
+ for x in ids:
|
|
|
+ tid = str(x or "").strip()
|
|
|
+ if not tid:
|
|
|
+ continue
|
|
|
+ key = (field, tid)
|
|
|
+ if key in seen:
|
|
|
+ continue
|
|
|
+ seen.add(key)
|
|
|
+ links.append((field, tid))
|
|
|
+ return links
|