提交
This commit is contained in:
1 parent
3d98104455
commit
0d52442902
76 files changed
+21994
-12642
No files matched your search
@@ -903,6 +903,19 @@ STATIC_MENU_TREE = [
|
||||
"component": "/oa-approval/start/index",
|
||||
"meta": {"title": "发起审批", "keepAlive": True, "menuCode": "oa:start"},
|
||||
},
|
||||
{
|
||||
"id": "OA000000000000000000000000000203",
|
||||
"name": "OaApprovalHistory",
|
||||
"path": "history/:appId",
|
||||
"component": "/oa-approval/history/index",
|
||||
"meta": {
|
||||
"title": "历史记录",
|
||||
"keepAlive": True,
|
||||
"isHide": True,
|
||||
"worktabMulti": True,
|
||||
"menuCode": "oa:start",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "OA000000000000000000000000000104",
|
||||
"name": "OaApprovalPending",
|
||||
|
||||
@@ -937,6 +937,47 @@ async def list_oa_instance_history(
|
||||
return error_response(400, str(e))
|
||||
|
||||
|
||||
@router.get("/instances/history/export")
|
||||
async def export_oa_instance_history(
|
||||
appId: str = Query(..., description="流程应用 ID"),
|
||||
instanceId: str = Query(""),
|
||||
instanceIds: str = Query("", description="逗号分隔的实例 ID,用于导出勾选"),
|
||||
instanceStatus: str = Query(""),
|
||||
initiatorName: str = Query(""),
|
||||
applyTimeFrom: str = Query(""),
|
||||
applyTimeTo: str = Query(""),
|
||||
finishTimeFrom: str = Query(""),
|
||||
finishTimeTo: str = Query(""),
|
||||
user: dict = Depends(require_admin_or_super_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
from app.core.menu_guard import user_has_menu_perm
|
||||
from app.services.oa_engine import OaEngineError, export_instance_history
|
||||
|
||||
try:
|
||||
tenant_no = _get_tenant_no(db, user)
|
||||
can_view_all = user_has_menu_perm(db, user, OA_START_HISTORY_ALL_PERM)
|
||||
id_list = [x.strip() for x in (instanceIds or "").split(",") if x.strip()]
|
||||
data = export_instance_history(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user.get("user_id") or "",
|
||||
can_view_all=can_view_all,
|
||||
app_id=appId,
|
||||
instance_id=instanceId or None,
|
||||
instance_ids=id_list or None,
|
||||
initiator_name=initiatorName or None,
|
||||
apply_time_from=applyTimeFrom or None,
|
||||
apply_time_to=applyTimeTo or None,
|
||||
finish_time_from=finishTimeFrom or None,
|
||||
finish_time_to=finishTimeTo or None,
|
||||
instance_status=instanceStatus or None,
|
||||
)
|
||||
return success_response(data)
|
||||
except OaEngineError as e:
|
||||
return error_response(400, str(e))
|
||||
|
||||
|
||||
@router.get("/instances/{instance_id}")
|
||||
async def get_oa_instance(
|
||||
instance_id: str,
|
||||
|
||||
@@ -62,6 +62,7 @@ class ProductionTaskStatusUpdate(BaseModel):
|
||||
|
||||
|
||||
class TaskStepAdd(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
process_id: Optional[str] = None
|
||||
process_code: Optional[str] = None
|
||||
category_id: Optional[str] = None
|
||||
@@ -74,6 +75,7 @@ class TaskStepAdd(BaseModel):
|
||||
|
||||
|
||||
class TaskStepUpdate(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
process_name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
equipment_ids: Optional[List[str]] = Field(None, alias="equipmentIds")
|
||||
|
||||
@@ -1004,10 +1004,12 @@ def _enrich_list_rows(
|
||||
ver = ver_by_id.get(inst.app_version_id or "")
|
||||
schema = _loads(getattr(ver, "form_schema_json", None), {}) if ver else {}
|
||||
form_data = _loads(inst.form_data_json, {}) or {}
|
||||
preview = [
|
||||
{"label": f.get("label") or "", "value": f.get("value") or ""}
|
||||
for f in flatten_form_fields_for_summary(schema, form_data)[:limit]
|
||||
]
|
||||
preview = []
|
||||
for f in flatten_form_fields_for_summary(schema, form_data)[:limit]:
|
||||
item = dict(f)
|
||||
item["label"] = item.get("label") or ""
|
||||
item["value"] = item.get("value") or ""
|
||||
preview.append(item)
|
||||
merged = dict(row)
|
||||
merged.update(summary)
|
||||
merged["previewFields"] = preview
|
||||
@@ -2317,6 +2319,494 @@ def list_instance_history(
|
||||
}
|
||||
|
||||
|
||||
_HISTORY_EXPORT_MAX_ROWS = 5000
|
||||
|
||||
_EXPORT_RESULT_TEXT = {
|
||||
"completed": "审批通过",
|
||||
"approved": "审批通过",
|
||||
"rejected": "审批拒绝",
|
||||
"returned": "已打回",
|
||||
"cancelled": "已撤销",
|
||||
"running": "",
|
||||
"pending": "",
|
||||
}
|
||||
|
||||
_EXPORT_STATUS_TEXT = {
|
||||
"completed": "已结束",
|
||||
"approved": "已结束",
|
||||
"rejected": "已结束",
|
||||
"cancelled": "已结束",
|
||||
"returned": "已打回",
|
||||
"running": "进行中",
|
||||
"pending": "待处理",
|
||||
}
|
||||
|
||||
_EXPORT_ACTION_TEXT = {
|
||||
"submitted": "提交申请",
|
||||
"approved": "同意",
|
||||
"rejected": "拒绝",
|
||||
"returned": "打回",
|
||||
"completed": "同意",
|
||||
"pending": "待处理",
|
||||
"cancelled": "取消",
|
||||
"transferred": "转交",
|
||||
"done": "抄送",
|
||||
}
|
||||
|
||||
_FIXED_HISTORY_EXPORT_COLUMNS: List[Tuple[str, str]] = [
|
||||
("instanceNo", "审批编号"),
|
||||
("createTime", "创建时间"),
|
||||
("initiatorName", "创建人"),
|
||||
("currentAssignee", "当前负责人"),
|
||||
("approvalResult", "审批结果"),
|
||||
("approvalStatus", "审批状态"),
|
||||
("updateTime", "更新时间"),
|
||||
("finishTime", "完成时间"),
|
||||
("initiatorDept", "创建人部门"),
|
||||
("title", "审批单标题"),
|
||||
("historyApprovers", "历史审批人"),
|
||||
("duration", "耗时(时:分:秒)"),
|
||||
("approvalRecord", "审批记录"),
|
||||
]
|
||||
|
||||
|
||||
def _format_export_duration(start: Optional[datetime], end: Optional[datetime]) -> str:
|
||||
if not start or not end:
|
||||
return ""
|
||||
delta = end - start
|
||||
if delta.total_seconds() < 0:
|
||||
return ""
|
||||
total = int(delta.total_seconds())
|
||||
h, rem = divmod(total, 3600)
|
||||
m, s = divmod(rem, 60)
|
||||
return f"{h}:{m}:{s}"
|
||||
|
||||
|
||||
def _export_upload_cell_value(field: Dict[str, Any]) -> str:
|
||||
files = field.get("files") or []
|
||||
if not isinstance(files, list):
|
||||
return str(field.get("value") or "")
|
||||
urls: List[str] = []
|
||||
for f in files:
|
||||
if not isinstance(f, dict):
|
||||
continue
|
||||
u = str(f.get("url") or "").strip()
|
||||
if u:
|
||||
urls.append(u)
|
||||
if urls:
|
||||
return ",".join(urls)
|
||||
return str(field.get("value") or "")
|
||||
|
||||
|
||||
def _export_progress_action(status: str, *, task_type: str = "") -> str:
|
||||
st = (status or "").strip()
|
||||
if (task_type or "").strip() == "cc" or st == "done":
|
||||
return "抄送"
|
||||
return _EXPORT_ACTION_TEXT.get(st, st or "")
|
||||
|
||||
|
||||
def _format_export_progress_record(progress: List[Dict[str, Any]]) -> str:
|
||||
lines: List[str] = []
|
||||
for item in progress or []:
|
||||
kind = (item.get("kind") or "").strip()
|
||||
title = (item.get("title") or "").strip()
|
||||
ts = (item.get("timestamp") or "").strip()
|
||||
task_type = (item.get("taskType") or "").strip()
|
||||
if kind == "submit":
|
||||
name = (item.get("assigneeUserName") or "").strip()
|
||||
if not name:
|
||||
continue
|
||||
lines.append(f"{name}|提交申请|{ts}|提交申请")
|
||||
continue
|
||||
people = item.get("people") or []
|
||||
if isinstance(people, list) and people:
|
||||
for p in people:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
name = (p.get("nickName") or p.get("assigneeUserName") or "").strip()
|
||||
if not name:
|
||||
continue
|
||||
pst = (p.get("status") or "").strip()
|
||||
if pst in ("pending", "running", "cancelled") and task_type != "cc":
|
||||
continue
|
||||
node = title or (item.get("nodeKey") or "").strip() or "-"
|
||||
pts = (p.get("timestamp") or ts or "").strip()
|
||||
action = _export_progress_action(pst, task_type=task_type)
|
||||
comment = (p.get("commentText") or "").strip()
|
||||
seg = f"{name}|{node}|{pts}|{action}"
|
||||
if comment:
|
||||
seg += f"|{comment}"
|
||||
lines.append(seg)
|
||||
continue
|
||||
name = (item.get("assigneeUserName") or "").strip()
|
||||
if not name:
|
||||
continue
|
||||
st = (item.get("status") or "").strip()
|
||||
if st in ("pending", "running", "cancelled") and task_type != "cc":
|
||||
continue
|
||||
node = title or (item.get("nodeKey") or "").strip() or "-"
|
||||
action = _export_progress_action(st, task_type=task_type)
|
||||
comment = (item.get("commentText") or "").strip()
|
||||
seg = f"{name}|{node}|{ts}|{action}"
|
||||
if comment:
|
||||
seg += f"|{comment}"
|
||||
lines.append(seg)
|
||||
return ";\r\n".join(lines)
|
||||
|
||||
|
||||
def _collect_history_approver_names(progress: List[Dict[str, Any]]) -> str:
|
||||
names: List[str] = []
|
||||
seen = set()
|
||||
for item in progress or []:
|
||||
if (item.get("kind") or "").strip() == "submit":
|
||||
continue
|
||||
people = item.get("people") or []
|
||||
if isinstance(people, list) and people:
|
||||
for p in people:
|
||||
if not isinstance(p, dict):
|
||||
continue
|
||||
st = (p.get("status") or "").strip()
|
||||
if st not in ("approved", "completed", "rejected", "returned", "transferred"):
|
||||
continue
|
||||
name = (p.get("nickName") or p.get("assigneeUserName") or "").strip()
|
||||
if name and name not in seen:
|
||||
seen.add(name)
|
||||
names.append(name)
|
||||
continue
|
||||
st = (item.get("status") or "").strip()
|
||||
if st not in ("approved", "completed", "rejected", "returned", "transferred"):
|
||||
continue
|
||||
name = (item.get("assigneeUserName") or "").strip()
|
||||
if name and name not in seen:
|
||||
seen.add(name)
|
||||
names.append(name)
|
||||
return ",".join(names)
|
||||
|
||||
|
||||
def _build_history_export_business_columns(schema: Any) -> List[Dict[str, Any]]:
|
||||
from app.services.oa_related_approval import LAYOUT_SKIP, _schema_rules
|
||||
|
||||
cols: 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)
|
||||
if typ == "relatedApproval":
|
||||
cols.append(
|
||||
{
|
||||
"key": field,
|
||||
"label": label,
|
||||
"kind": "related",
|
||||
"subLabel": "关联标题",
|
||||
}
|
||||
)
|
||||
else:
|
||||
cols.append({"key": field, "label": label, "kind": "business"})
|
||||
return cols
|
||||
|
||||
|
||||
def _merge_history_export_business_columns(col_lists: List[List[Dict[str, Any]]]) -> List[Dict[str, Any]]:
|
||||
merged: List[Dict[str, Any]] = []
|
||||
seen = set()
|
||||
for cols in col_lists:
|
||||
for c in cols:
|
||||
key = str(c.get("key") or "")
|
||||
if not key or key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
merged.append(dict(c))
|
||||
return merged
|
||||
|
||||
|
||||
def _history_export_query(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_no: str,
|
||||
user_id: str,
|
||||
can_view_all: bool,
|
||||
app_id: str,
|
||||
instance_id: Optional[str] = None,
|
||||
instance_ids: Optional[List[str]] = None,
|
||||
initiator_name: Optional[str] = None,
|
||||
apply_time_from: Optional[str] = None,
|
||||
apply_time_to: Optional[str] = None,
|
||||
finish_time_from: Optional[str] = None,
|
||||
finish_time_to: Optional[str] = None,
|
||||
instance_status: Optional[str] = None,
|
||||
):
|
||||
q = db.query(OaInstance).filter(
|
||||
OaInstance.tenant_no == tenant_no,
|
||||
OaInstance.del_flag == 0,
|
||||
OaInstance.app_id == app_id,
|
||||
)
|
||||
if not can_view_all:
|
||||
q = q.filter(OaInstance.initiator_user_id == user_id)
|
||||
initiator_name = None
|
||||
|
||||
clean_ids = [str(x).strip() for x in (instance_ids or []) if str(x).strip()]
|
||||
if clean_ids:
|
||||
q = q.filter(OaInstance.id.in_(clean_ids))
|
||||
else:
|
||||
iid = (instance_id or "").strip()
|
||||
if iid:
|
||||
q = q.filter(OaInstance.id.like(f"%{iid}%"))
|
||||
|
||||
q, _, _ = _apply_instance_list_filters(
|
||||
q,
|
||||
initiator_name=initiator_name,
|
||||
apply_time_from=apply_time_from,
|
||||
apply_time_to=apply_time_to,
|
||||
instance_status=instance_status,
|
||||
)
|
||||
|
||||
ft0 = _parse_day_start(finish_time_from)
|
||||
ft1 = _parse_day_end(finish_time_to)
|
||||
if ft0 is not None or ft1 is not None:
|
||||
q = q.filter(OaInstance.status.in_(tuple(_FINISH_STATUSES)))
|
||||
if ft0 is not None:
|
||||
q = q.filter(OaInstance.update_time >= ft0)
|
||||
if ft1 is not None:
|
||||
q = q.filter(OaInstance.update_time <= ft1)
|
||||
return q
|
||||
|
||||
|
||||
def export_instance_history(
|
||||
db: Session,
|
||||
*,
|
||||
tenant_no: str,
|
||||
user_id: str,
|
||||
can_view_all: bool,
|
||||
app_id: str,
|
||||
instance_id: Optional[str] = None,
|
||||
instance_ids: Optional[List[str]] = None,
|
||||
initiator_name: Optional[str] = None,
|
||||
apply_time_from: Optional[str] = None,
|
||||
apply_time_to: Optional[str] = None,
|
||||
finish_time_from: Optional[str] = None,
|
||||
finish_time_to: Optional[str] = None,
|
||||
instance_status: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
from app.services.oa_related_approval import flatten_form_fields_for_summary
|
||||
|
||||
app_id = (app_id or "").strip()
|
||||
if not app_id:
|
||||
raise OaEngineError("appId 不能为空")
|
||||
|
||||
app = oa_crud.get_app(db, app_id, tenant_no)
|
||||
app_name = (app.app_name or "") if app else ""
|
||||
|
||||
q = _history_export_query(
|
||||
db,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user_id,
|
||||
can_view_all=can_view_all,
|
||||
app_id=app_id,
|
||||
instance_id=instance_id,
|
||||
instance_ids=instance_ids,
|
||||
initiator_name=initiator_name,
|
||||
apply_time_from=apply_time_from,
|
||||
apply_time_to=apply_time_to,
|
||||
finish_time_from=finish_time_from,
|
||||
finish_time_to=finish_time_to,
|
||||
instance_status=instance_status,
|
||||
)
|
||||
total = q.count()
|
||||
if total > _HISTORY_EXPORT_MAX_ROWS:
|
||||
raise OaEngineError(f"导出条数超过上限 {_HISTORY_EXPORT_MAX_ROWS},请缩小筛选范围")
|
||||
insts = q.order_by(OaInstance.create_time.desc()).limit(_HISTORY_EXPORT_MAX_ROWS).all()
|
||||
if not insts:
|
||||
pub = oa_crud._find_latest_published(db, app_id, tenant_no)
|
||||
schema = _loads(getattr(pub, "form_schema_json", None), {}) if pub else {}
|
||||
business_columns = _build_history_export_business_columns(schema)
|
||||
return {
|
||||
"appName": app_name,
|
||||
"businessColumns": business_columns,
|
||||
"fixedColumns": [{"key": k, "label": lb} for k, lb in _FIXED_HISTORY_EXPORT_COLUMNS],
|
||||
"rows": [],
|
||||
"total": 0,
|
||||
}
|
||||
|
||||
ver_by_id: Dict[str, OaAppVersion] = {}
|
||||
ver_ids = list({i.app_version_id for i in insts if i.app_version_id})
|
||||
if ver_ids:
|
||||
for v in (
|
||||
db.query(OaAppVersion)
|
||||
.filter(OaAppVersion.id.in_(ver_ids), OaAppVersion.del_flag == 0)
|
||||
.all()
|
||||
):
|
||||
ver_by_id[v.id] = v
|
||||
|
||||
pub = oa_crud._find_latest_published(db, app_id, tenant_no)
|
||||
pub_schema = _loads(getattr(pub, "form_schema_json", None), {}) if pub else {}
|
||||
col_lists = [_build_history_export_business_columns(pub_schema)]
|
||||
for inst in insts:
|
||||
ver = ver_by_id.get(inst.app_version_id or "")
|
||||
schema = _loads(getattr(ver, "form_schema_json", None), {}) if ver else {}
|
||||
col_lists.append(_build_history_export_business_columns(schema))
|
||||
business_columns = _merge_history_export_business_columns(col_lists)
|
||||
|
||||
inst_ids = [i.id for i in insts]
|
||||
all_tasks = (
|
||||
db.query(OaTask)
|
||||
.filter(OaTask.instance_id.in_(inst_ids), OaTask.del_flag == 0)
|
||||
.all()
|
||||
)
|
||||
tasks_by_inst: Dict[str, List[OaTask]] = {}
|
||||
user_ids: List[str] = []
|
||||
for t in all_tasks:
|
||||
tasks_by_inst.setdefault(t.instance_id, []).append(t)
|
||||
if t.assignee_user_id:
|
||||
user_ids.append(t.assignee_user_id)
|
||||
|
||||
all_events = (
|
||||
db.query(OaInstanceEvent)
|
||||
.filter(OaInstanceEvent.instance_id.in_(inst_ids), OaInstanceEvent.del_flag == 0)
|
||||
.order_by(OaInstanceEvent.create_time.asc())
|
||||
.all()
|
||||
)
|
||||
events_by_inst: Dict[str, List[OaInstanceEvent]] = {}
|
||||
for e in all_events:
|
||||
events_by_inst.setdefault(e.instance_id, []).append(e)
|
||||
if e.actor_user_id:
|
||||
user_ids.append(e.actor_user_id)
|
||||
|
||||
for inst in insts:
|
||||
if inst.initiator_user_id:
|
||||
user_ids.append(inst.initiator_user_id)
|
||||
|
||||
name_map = _user_name_map(db, user_ids)
|
||||
|
||||
related_ids: List[str] = []
|
||||
flat_by_inst: Dict[str, List[Dict[str, Any]]] = {}
|
||||
for inst in insts:
|
||||
ver = ver_by_id.get(inst.app_version_id or "")
|
||||
schema = _loads(getattr(ver, "form_schema_json", None), {}) if ver else {}
|
||||
form_data = _loads(inst.form_data_json, {}) or {}
|
||||
flat = flatten_form_fields_for_summary(schema, form_data)
|
||||
flat_by_inst[inst.id] = flat
|
||||
for f in flat:
|
||||
for rid in f.get("relatedIds") or []:
|
||||
rid_s = str(rid or "").strip()
|
||||
if rid_s:
|
||||
related_ids.append(rid_s)
|
||||
|
||||
related_title_map: Dict[str, str] = {}
|
||||
if related_ids:
|
||||
rel_insts = (
|
||||
db.query(OaInstance)
|
||||
.filter(
|
||||
OaInstance.tenant_no == tenant_no,
|
||||
OaInstance.del_flag == 0,
|
||||
OaInstance.id.in_(list(set(related_ids))),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
rel_app_ids = list({r.app_id for r in rel_insts if r.app_id})
|
||||
rel_app_map = {}
|
||||
if rel_app_ids:
|
||||
for a in db.query(OaApp).filter(OaApp.id.in_(rel_app_ids)).all():
|
||||
rel_app_map[a.id] = a.app_name or ""
|
||||
for r in rel_insts:
|
||||
initiator = name_map.get(r.initiator_user_id or "", "")
|
||||
related_title_map[r.id] = (
|
||||
f"{initiator}提交的{rel_app_map.get(r.app_id, '')}" if initiator else (r.title or r.id)
|
||||
)
|
||||
|
||||
pending_by_inst: Dict[str, List[OaTask]] = {}
|
||||
for t in all_tasks:
|
||||
if (t.task_type or "") == "approval" and (t.status or "") == "pending":
|
||||
pending_by_inst.setdefault(t.instance_id, []).append(t)
|
||||
|
||||
rows_out: List[Dict[str, Any]] = []
|
||||
for idx, inst in enumerate(insts, start=1):
|
||||
ver = ver_by_id.get(inst.app_version_id or "")
|
||||
flow_root = None
|
||||
if ver:
|
||||
try:
|
||||
flow_root = _flow_root(_loads(ver.flow_tree_json, {}))
|
||||
except OaEngineError:
|
||||
flow_root = None
|
||||
tasks = _sort_tasks_for_detail(tasks_by_inst.get(inst.id, []), flow_root=flow_root)
|
||||
events = events_by_inst.get(inst.id, [])
|
||||
adv = _advanced(ver) if ver else dict(oa_crud.DEFAULT_ADVANCED_SETTINGS)
|
||||
progress = build_progress_items(
|
||||
inst=inst,
|
||||
flow_root=flow_root,
|
||||
tasks=tasks,
|
||||
name_map=name_map,
|
||||
events=events,
|
||||
db=db,
|
||||
advanced=adv,
|
||||
)
|
||||
summary = _instance_progress_summary(
|
||||
inst,
|
||||
app_name=app_name,
|
||||
initiator_user_name=name_map.get(inst.initiator_user_id or "", ""),
|
||||
pending_approval_tasks=pending_by_inst.get(inst.id, []),
|
||||
name_map=name_map,
|
||||
)
|
||||
st = (inst.status or "").strip()
|
||||
finish_time = (
|
||||
inst.update_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if st in _FINISH_STATUSES and inst.update_time
|
||||
else ""
|
||||
)
|
||||
duration_end = inst.update_time if st in _FINISH_STATUSES else None
|
||||
business: Dict[str, Any] = {}
|
||||
flat_map = {str(f.get("fieldKey") or ""): f for f in flat_by_inst.get(inst.id, [])}
|
||||
for col in business_columns:
|
||||
key = str(col.get("key") or "")
|
||||
field = flat_map.get(key) or {}
|
||||
if col.get("kind") == "related":
|
||||
ids = field.get("relatedIds") or []
|
||||
business[key] = f"已关联 {len(ids)} 条" if ids else ""
|
||||
titles = [related_title_map.get(str(i), str(i)) for i in ids]
|
||||
business[f"{key}__titles"] = "、".join(t for t in titles if t)
|
||||
elif field.get("fieldType") in ("upload", "fcUpload", "image", "oaAttachment"):
|
||||
business[key] = _export_upload_cell_value(field)
|
||||
else:
|
||||
business[key] = str(field.get("value") or "")
|
||||
|
||||
rows_out.append(
|
||||
{
|
||||
"index": idx,
|
||||
"id": inst.id,
|
||||
"business": business,
|
||||
"fixed": {
|
||||
"instanceNo": inst.id,
|
||||
"createTime": summary.get("applyTime") or "",
|
||||
"initiatorName": summary.get("initiatorUserName") or "",
|
||||
"currentAssignee": summary.get("currentApproverNames") or "-",
|
||||
"approvalResult": _EXPORT_RESULT_TEXT.get(st, st),
|
||||
"approvalStatus": _EXPORT_STATUS_TEXT.get(st, st),
|
||||
"updateTime": inst.update_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if inst.update_time
|
||||
else "",
|
||||
"finishTime": finish_time,
|
||||
"initiatorDept": _dept_name(
|
||||
db, inst.initiator_dept_id, tenant_no=tenant_no
|
||||
),
|
||||
"title": inst.title or "",
|
||||
"historyApprovers": _collect_history_approver_names(progress),
|
||||
"duration": _format_export_duration(inst.create_time, duration_end),
|
||||
"approvalRecord": _format_export_progress_record(progress),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"appName": app_name,
|
||||
"businessColumns": business_columns,
|
||||
"fixedColumns": [{"key": k, "label": lb} for k, lb in _FIXED_HISTORY_EXPORT_COLUMNS],
|
||||
"rows": rows_out,
|
||||
"total": len(rows_out),
|
||||
}
|
||||
|
||||
|
||||
def list_tasks(
|
||||
db: Session,
|
||||
*,
|
||||
|
||||
@@ -144,6 +144,36 @@ def _format_upload_display(raw: Any) -> str:
|
||||
return "、".join(names) if names else "0 个"
|
||||
|
||||
|
||||
def _upload_files_meta(raw: Any) -> List[Dict[str, str]]:
|
||||
"""摘要/列表用:上传字段规范化为 {url, name} 列表,供前端缩略图展示。"""
|
||||
if raw is None or raw == "" or raw == []:
|
||||
return []
|
||||
if isinstance(raw, list):
|
||||
items: List[Any] = raw
|
||||
elif isinstance(raw, str):
|
||||
items = [x.strip() for x in raw.split(",") if x.strip()]
|
||||
elif isinstance(raw, dict):
|
||||
items = [raw]
|
||||
else:
|
||||
items = [raw]
|
||||
files: List[Dict[str, str]] = []
|
||||
for it in items:
|
||||
if it is None or it == "":
|
||||
continue
|
||||
if isinstance(it, dict):
|
||||
urls = _extract_upload_urls(it)
|
||||
if not urls:
|
||||
continue
|
||||
for u in urls:
|
||||
files.append({"url": u, "name": _upload_display_name(it, u)})
|
||||
continue
|
||||
s = str(it).strip()
|
||||
if not s:
|
||||
continue
|
||||
files.append({"url": s, "name": _upload_display_name(s, s)})
|
||||
return files
|
||||
|
||||
|
||||
def _normalize_related_ids(raw: Any) -> List[str]:
|
||||
"""Order-preserving dedupe of related approval instance ids."""
|
||||
if isinstance(raw, list):
|
||||
@@ -408,14 +438,28 @@ def flatten_form_fields_for_summary(schema: Any, form_data: Optional[Dict[str, A
|
||||
if typ == "relatedApproval":
|
||||
ids = _normalize_related_ids(raw)
|
||||
item: Dict[str, Any] = {
|
||||
"fieldKey": field,
|
||||
"label": label,
|
||||
"value": f"已关联 {len(ids)} 条",
|
||||
"relatedIds": ids,
|
||||
}
|
||||
out.append(item)
|
||||
continue
|
||||
if typ in UPLOAD_TYPES:
|
||||
text, full = _format_value(typ, raw)
|
||||
item = {
|
||||
"fieldKey": field,
|
||||
"label": label,
|
||||
"value": text,
|
||||
"fieldType": typ,
|
||||
"files": _upload_files_meta(raw),
|
||||
}
|
||||
if full:
|
||||
item["fullWidth"] = True
|
||||
out.append(item)
|
||||
continue
|
||||
text, full = _format_value(typ, raw)
|
||||
item = {"label": label, "value": text}
|
||||
item = {"fieldKey": field, "label": label, "value": text}
|
||||
if full:
|
||||
item["fullWidth"] = True
|
||||
out.append(item)
|
||||
|
||||
Reference in new issue
Block a user