This commit is contained in:
zlwu committed 2026-06-12 15:17:54 +08:00
1 parent 4ea71a1bae
commit 3abe69c1cc
83 files changed
+33505 -21867

No files matched your search

+1 -1
View File
@@ -1 +1 @@
68064
91694
+47
View File
@@ -34,6 +34,7 @@ from app.models.models import SysUser
from app.crud import production_work_order as production_work_order_svc
from app.crud import production_task as production_task_svc
from app.crud import cell_product as cell_product_svc
from app.crud import qc_work_order as qc_work_order_svc
from app.schemas.production_work_order import (
ProductionWorkOrderListQuery,
ProductionWorkOrderComplete,
@@ -55,6 +56,7 @@ from app.schemas.production_task import (
TaskStepReorderBody,
)
from app.schemas.cell_product import CellProductCreate, CellProductUpdate
from app.schemas.qc_work_order import ProductionQcWorkOrderCreateBody
router = APIRouter(prefix="/web/admin", tags=["生产管理"])
@@ -1729,6 +1731,51 @@ async def configure_production_work_order(
return error_response(400, str(e))
@router.get("/production-work-orders/{wo_id}/cell-product-options")
async def list_production_work_order_cell_product_options(
wo_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
items = qc_work_order_svc.list_cell_product_options_for_production_work_order(
db, production_work_order_id=wo_id, tenant_no=tenant_no
)
return success_response(items)
except ValueError as e:
return error_response(400, str(e))
@router.post("/production-work-orders/{wo_id}/qc-work-orders")
async def create_qc_work_order_from_production_api(
wo_id: str,
body: ProductionQcWorkOrderCreateBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
wo = qc_work_order_svc.create_qc_work_order_from_production(
db,
production_work_order_id=wo_id,
cell_product_id=body.cell_product_id,
production_task_id=body.production_task_id,
remark=body.remark or "",
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
)
db.commit()
return success_response(qc_work_order_svc._wo_to_dict(wo), message="派发成功")
except ValueError as e:
db.rollback()
return error_response(400, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))
# ---------------------------------------------------------------------------
# 10. 生产任务
# ---------------------------------------------------------------------------
+2
View File
@@ -632,6 +632,7 @@ async def list_qc_work_orders(
createTimeEnd: Optional[str] = Query(None, alias="createTimeEnd"),
infusionTimeStart: Optional[str] = Query(None, alias="infusionTimeStart"),
infusionTimeEnd: Optional[str] = Query(None, alias="infusionTimeEnd"),
triggerSource: Optional[str] = Query(None, alias="triggerSource"),
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
):
@@ -645,6 +646,7 @@ async def list_qc_work_orders(
work_order_status=workOrderStatus,
work_order_type=workOrderType,
biz_type=bizType,
trigger_source=triggerSource,
create_time_start=createTimeStart,
create_time_end=createTimeEnd,
infusion_time_start=infusionTimeStart,
+276 -9
View File
@@ -20,6 +20,7 @@ from app.crud import sample_reception as sample_reception_svc
from app.crud.sample_reception import _donation_sample_id_col
from app.crud import sample_reception_check_result as check_result_svc
from app.crud.cell_product import _enrich_product_dicts as _cp_enrich, _row_to_dict as _cp_row_to_dict, get_cell_product
from app.services.qc_work_order_fee_summary import enrich_work_order_fee_summaries
from app.models.models import (
BizCellProduct,
BizCellSampleType,
@@ -30,6 +31,9 @@ from app.models.models import (
BizPilotInfusionScheduleProduct,
BizPilotPatient,
BizProtocolSample,
BizProductionTask,
BizProductionTaskOutput,
BizProductionWorkOrder,
BizQcLabTestItem,
BizQcTask,
BizQcThirdPartyItem,
@@ -52,6 +56,10 @@ _QC_BIZ_TYPE_LABELS = {
"outbound": "出库质检",
"temporary": "临时质检",
}
_TRIGGER_SOURCE_LABELS = {
"reception": "样本接收",
"production": "生产过程",
}
_ACCEPT_CONCLUSION_LABELS = {"js": "接收", "bj": "拒收"}
_WORK_ORDER_STATUS_LABELS = {"dpz": "待配置", "ypz": "已配置", "ywc": "已完成", "yzf": "已作废"}
_VALID_QC_WORK_ORDER_STATUSES = frozenset(_WORK_ORDER_STATUS_LABELS.keys())
@@ -413,20 +421,51 @@ def _enrich_work_order_list_items(
if not items:
return items
rec_ids = [x["receptionId"] for x in items if x.get("receptionId")]
if not rec_ids:
sample_codes_no_rec = list(
{
(x.get("sampleCode") or "").strip()
for x in items
if not (x.get("receptionId") or "").strip() and (x.get("sampleCode") or "").strip()
}
)
rec_map = {}
if rec_ids:
rec_map = {
r.id: r
for r in db.query(BizSampleReception)
.filter(BizSampleReception.id.in_(rec_ids), BizSampleReception.del_flag == 0)
.all()
}
sample_code_rec_map: Dict[str, BizSampleReception] = {}
if sample_codes_no_rec:
for r in (
db.query(BizSampleReception)
.filter(
BizSampleReception.sample_code.in_(sample_codes_no_rec),
BizSampleReception.del_flag == 0,
)
.order_by(BizSampleReception.create_time.desc())
.all()
):
code = (r.sample_code or "").strip()
if code and code not in sample_code_rec_map:
sample_code_rec_map[code] = r
if not rec_map and not sample_code_rec_map:
return items
rec_map = {
r.id: r
for r in db.query(BizSampleReception)
.filter(BizSampleReception.id.in_(rec_ids), BizSampleReception.del_flag == 0)
.all()
}
def _resolve_reception(item: Dict[str, Any]) -> Optional[BizSampleReception]:
rec = rec_map.get(item.get("receptionId") or "")
if rec:
return rec
code = (item.get("sampleCode") or "").strip()
return sample_code_rec_map.get(code) if code else None
cell_sample_ids: List[str] = []
donation_sample_ids: List[str] = []
cell_proto_ids: List[str] = []
donation_ids: List[str] = []
for item in items:
rec = rec_map.get(item.get("receptionId") or "")
rec = _resolve_reception(item)
source = _effective_reception_source(item, rec)
if source == "research_donation":
dsid = _effective_donation_sample_id(item, rec)
@@ -500,7 +539,7 @@ def _enrich_work_order_list_items(
item["receptionSource"] = ""
item["receptionSourceLabel"] = ""
continue
rec = rec_map.get(item.get("receptionId") or "")
rec = _resolve_reception(item)
source = _effective_reception_source(item, rec)
item["receptionSource"] = source
item["receptionSourceLabel"] = _reception_source_label(source)
@@ -540,6 +579,31 @@ def _enrich_work_order_list_items(
return items
def _enrich_production_work_order_refs(
db: Session, items: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
if not items:
return items
pwo_ids = list(
{x.get("productionWorkOrderId") for x in items if (x.get("productionWorkOrderId") or "").strip()}
)
pwo_map: Dict[str, str] = {}
if pwo_ids:
rows = (
db.query(BizProductionWorkOrder.id, BizProductionWorkOrder.work_order_no)
.filter(
BizProductionWorkOrder.id.in_(pwo_ids),
BizProductionWorkOrder.del_flag == 0,
)
.all()
)
pwo_map = {r.id: r.work_order_no or "" for r in rows}
for item in items:
pid = (item.get("productionWorkOrderId") or "").strip()
item["productionWorkOrderNo"] = pwo_map.get(pid, "") if pid else ""
return items
def _gen_daily_no(db: Session, tenant_no: str, prefix: str) -> str:
day = datetime.now().strftime("%Y%m%d")
like = f"{prefix}{day}%"
@@ -603,6 +667,13 @@ def _wo_to_dict(
"cellProductTypeName": "",
"cellProductPassage": "",
"cellProductSpecName": "",
"triggerSource": getattr(wo, "trigger_source", None) or "reception",
"triggerSourceLabel": _TRIGGER_SOURCE_LABELS.get(
getattr(wo, "trigger_source", None) or "reception", ""
),
"productionWorkOrderId": getattr(wo, "production_work_order_id", None) or "",
"productionWorkOrderNo": "",
"productionTaskId": getattr(wo, "production_task_id", None) or "",
"receptionTime": "",
"handlerName": "",
"sampleTypeName": "",
@@ -680,6 +751,7 @@ def create_work_order_from_reception(
sample_code=sample_code or None,
reception_source="research_donation",
qc_biz_type="inbound",
trigger_source="reception",
work_order_status="dpz",
has_lab="0",
has_third_party="0",
@@ -721,6 +793,7 @@ def create_work_order_from_reception(
sample_code=sample_code or None,
reception_source="cell_storage",
qc_biz_type="inbound",
trigger_source="reception",
work_order_status="dpz",
has_lab="0",
has_third_party="0",
@@ -737,6 +810,182 @@ def create_work_order_from_reception(
return wo
def create_qc_work_order_from_production(
db: Session,
*,
production_work_order_id: str,
cell_product_id: str,
production_task_id: Optional[str] = None,
remark: str = "",
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
) -> BizQcWorkOrder:
"""生产过程派发质检工单(flush,不 commit)。"""
pwo = (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.id == production_work_order_id,
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.del_flag == 0,
)
.first()
)
if not pwo:
raise ValueError("生产工单不存在")
wo_type = (getattr(pwo, "work_order_type", None) or "inbound").strip()
if wo_type != "inbound":
raise ValueError("仅入库生产工单可派发质检")
status = (pwo.work_order_status or "").strip()
if status == "yzf":
raise ValueError("已作废的生产工单不可派发质检")
if status not in ("jxz", "ywc"):
raise ValueError("仅进行中或已完成的入库生产工单可派发质检")
cp_id = _validate_cell_product_id(db, cell_product_id, tenant_no)
options = list_cell_product_options_for_production_work_order(
db, production_work_order_id=production_work_order_id, tenant_no=tenant_no
)
option_ids = {o["id"] for o in options}
if cp_id not in option_ids:
raise ValueError("所选细胞产品不在该生产工单的产出列表中")
task_id = (production_task_id or "").strip() or None
if task_id:
task = (
db.query(BizProductionTask)
.filter(
BizProductionTask.id == task_id,
BizProductionTask.work_order_id == pwo.id,
BizProductionTask.tenant_no == tenant_no,
BizProductionTask.del_flag == 0,
)
.first()
)
if not task:
raise ValueError("关联生产任务不存在")
wo = BizQcWorkOrder(
id=generate_uuid(),
work_order_no=_gen_daily_no(db, tenant_no, "ZJ"),
work_order_type="inbound",
# 生产过程触发不写入 reception_id,避免与 uk_qc_wo_reception(reception_id,del_flag) 冲突;
# 样本关联仍通过 sample_code / protocol_sample_id 等快照字段。
reception_id=None,
protocol_sample_id=pwo.protocol_sample_id,
collection_protocol_id=pwo.collection_protocol_id,
protocol_no=pwo.protocol_no or None,
sample_code=pwo.sample_code or None,
reception_source=getattr(pwo, "reception_source", "cell_storage") or "cell_storage",
qc_biz_type="inbound",
trigger_source="production",
cell_product_id=cp_id,
production_work_order_id=pwo.id,
production_task_id=task_id,
work_order_status="dpz",
has_lab="0",
has_third_party="0",
tenant_no=tenant_no,
remark=(remark or "").strip() or None,
create_by=user_id,
create_dept_id=create_dept_id or "",
update_by=user_id,
create_time=datetime.now(),
update_time=datetime.now(),
del_flag=0,
)
db.add(wo)
db.flush()
return wo
def list_cell_product_options_for_production_work_order(
db: Session,
*,
production_work_order_id: str,
tenant_no: str,
) -> List[Dict[str, Any]]:
"""汇总生产工单下任务产出中的细胞产品(去重)。"""
pwo = (
db.query(BizProductionWorkOrder)
.filter(
BizProductionWorkOrder.id == production_work_order_id,
BizProductionWorkOrder.tenant_no == tenant_no,
BizProductionWorkOrder.del_flag == 0,
)
.first()
)
if not pwo:
raise ValueError("生产工单不存在")
tasks = (
db.query(BizProductionTask)
.filter(
BizProductionTask.work_order_id == pwo.id,
BizProductionTask.tenant_no == tenant_no,
BizProductionTask.del_flag == 0,
)
.all()
)
if not tasks:
return []
task_map = {t.id: t for t in tasks}
task_ids = list(task_map.keys())
outputs = (
db.query(BizProductionTaskOutput)
.filter(
BizProductionTaskOutput.task_id.in_(task_ids),
BizProductionTaskOutput.tenant_no == tenant_no,
BizProductionTaskOutput.del_flag == 0,
BizProductionTaskOutput.cell_product_id.isnot(None),
BizProductionTaskOutput.cell_product_id != "",
)
.order_by(BizProductionTaskOutput.order_num.asc(), BizProductionTaskOutput.create_time.asc())
.all()
)
seen: set[str] = set()
items: List[Dict[str, Any]] = []
for out in outputs:
cp_id = (out.cell_product_id or "").strip()
if not cp_id or cp_id in seen:
continue
seen.add(cp_id)
task = task_map.get(out.task_id)
items.append(
{
"id": cp_id,
"cellProductId": cp_id,
"taskId": out.task_id,
"taskNo": task.task_no if task else "",
}
)
if not items:
return []
cp_ids = [i["id"] for i in items]
products = (
db.query(BizCellProduct)
.filter(
BizCellProduct.id.in_(cp_ids),
BizCellProduct.tenant_no == tenant_no,
BizCellProduct.del_flag == 0,
)
.all()
)
product_dicts = _cp_enrich(db, tenant_no, [_cp_row_to_dict(p) for p in products])
product_map = {d["id"]: d for d in product_dicts}
for item in items:
cp = product_map.get(item["id"]) or {}
item["productCode"] = cp.get("productCode", "")
item["productName"] = cp.get("productName", "")
item["productTypeName"] = cp.get("productTypeName", "")
item["passage"] = cp.get("passage", "")
item["storageSlotName"] = cp.get("storageSlotName", "")
return items
def list_work_orders(
db: Session,
*,
@@ -746,6 +995,7 @@ def list_work_orders(
work_order_status: Optional[str] = None,
work_order_type: Optional[str] = None,
biz_type: Optional[str] = None,
trigger_source: Optional[str] = None,
create_time_start: Optional[str] = None,
create_time_end: Optional[str] = None,
infusion_time_start: Optional[str] = None,
@@ -763,6 +1013,9 @@ def list_work_orders(
bt = (biz_type or "").strip()
if bt:
q0 = q0.filter(BizQcWorkOrder.biz_type == bt)
ts = (trigger_source or "").strip()
if ts:
q0 = q0.filter(BizQcWorkOrder.trigger_source == ts)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
@@ -841,7 +1094,9 @@ def list_work_orders(
items = _enrich_work_order_list_items(db, items)
if tenant_no:
items = _enrich_work_order_cell_products(db, tenant_no, items)
items = _enrich_production_work_order_refs(db, items)
items = _enrich_outbound_work_orders(db, tenant_no, items)
items = enrich_work_order_fee_summaries(db, items, tenant_no)
return items, int(total)
@@ -1040,6 +1295,10 @@ def get_work_order_detail(
out["labItems"] = _lab_lines(db, wo.id)
out["thirdPartyItems"] = _third_lines(db, wo.id)
out["tasks"] = task_svc.list_tasks_by_work_order(db, wo.id, tenant_no)
enriched_fee = enrich_work_order_fee_summaries(db, [out], tenant_no)
if enriched_fee:
out["paymentSummary"] = enriched_fee[0].get("paymentSummary")
out["feeSummary"] = enriched_fee[0].get("feeSummary")
return out
@@ -2034,9 +2293,17 @@ def get_outbound_qc_detail(
if not schedule_id:
out["tasks"] = task_svc.list_tasks_by_work_order(db, wo.id, tenant_no)
enriched_fee = enrich_work_order_fee_summaries(db, [out["workOrder"]], tenant_no)
if enriched_fee:
out["workOrder"]["feeSummary"] = enriched_fee[0].get("feeSummary")
out["feeSummary"] = enriched_fee[0].get("feeSummary")
return out
out["tasks"] = task_svc.list_tasks_by_work_order(db, wo.id, tenant_no)
enriched_fee = enrich_work_order_fee_summaries(db, [out["workOrder"]], tenant_no)
if enriched_fee:
out["workOrder"]["feeSummary"] = enriched_fee[0].get("feeSummary")
out["feeSummary"] = enriched_fee[0].get("feeSummary")
return out
@@ -550,6 +550,7 @@ def _build_qc_info(
q = db.query(BizQcWorkOrder).filter(
BizQcWorkOrder.tenant_no == tenant_no,
BizQcWorkOrder.del_flag == 0,
BizQcWorkOrder.trigger_source == "reception",
)
wo = q.filter(BizQcWorkOrder.sample_code == code).first() if code else None
if not wo and rev.reception_id:
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "label": "qc_task_assignee.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L1"}, {"id": "crud_qc_task_assignee_active_users_in_tenant", "label": "_active_users_in_tenant()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L14"}, {"id": "crud_qc_task_assignee_validate_assignee_user_ids", "label": "validate_assignee_user_ids()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L33"}, {"id": "crud_qc_task_assignee_sync_assignees_for_task", "label": "sync_assignees_for_task()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L54"}, {"id": "crud_qc_task_assignee_soft_delete_assignees_by_task_ids", "label": "soft_delete_assignees_by_task_ids()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L108"}, {"id": "crud_qc_task_assignee_list_assignees_by_task_ids", "label": "list_assignees_by_task_ids()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L129"}, {"id": "crud_qc_task_assignee_is_user_assignee", "label": "is_user_assignee()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L158"}, {"id": "crud_qc_task_assignee_enrich_tasks_with_assignees", "label": "enrich_tasks_with_assignees()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L174"}, {"id": "crud_qc_task_assignee_rationale_63", "label": "\u5168\u91cf\u66ff\u6362\u4efb\u52a1\u8d1f\u8d23\u4eba\uff08\u8f6f\u5220\u65e7\u8bb0\u5f55 + \u63d2\u5165\u65b0\u8bb0\u5f55\uff09\u3002", "file_type": "rationale", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L63"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "target": "datetime", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "target": "sqlalchemy_orm", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "target": "app_core_db_collation", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L9", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "target": "app_core_utils", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L10", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "target": "app_models_models", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L11", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "target": "crud_qc_task_assignee_active_users_in_tenant", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L14", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "target": "crud_qc_task_assignee_validate_assignee_user_ids", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L33", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_assignee_py", "target": "crud_qc_task_assignee_sync_assignees_for_task", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py", "source_location": "L54", "weight": 1.0}, {"source": "users_wLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_services_qc_work_order_fee_summary_py", "label": "qc_work_order_fee_summary.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L1"}, {"id": "services_qc_work_order_fee_summary_dec", "label": "_dec()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L27"}, {"id": "services_qc_work_order_fee_summary_received_amount_from_details", "label": "_received_amount_from_details()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L36"}, {"id": "services_qc_work_order_fee_summary_protocol_status_hint", "label": "_protocol_status_hint()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L45"}, {"id": "services_qc_work_order_fee_summary_protocol_overall_status", "label": "_protocol_overall_status()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L69"}, {"id": "services_qc_work_order_fee_summary_fmt_ymd", "label": "_fmt_ymd()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L91"}, {"id": "services_qc_work_order_fee_summary_payment_detail_row", "label": "_payment_detail_row()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L98"}, {"id": "services_qc_work_order_fee_summary_sort_payment_details", "label": "_sort_payment_details()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L116"}, {"id": "services_qc_work_order_fee_summary_build_protocol_payment_summary", "label": "build_protocol_payment_summary()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L127"}, {"id": "services_qc_work_order_fee_summary_build_protocol_payment_summaries", "label": "build_protocol_payment_summaries()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L155"}, {"id": "services_qc_work_order_fee_summary_build_basic_experiment_fee_summary", "label": "build_basic_experiment_fee_summary()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L197"}, {"id": "services_qc_work_order_fee_summary_build_basic_experiment_fee_summaries", "label": "build_basic_experiment_fee_summaries()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L217"}, {"id": "services_qc_work_order_fee_summary_is_inbound_cell_storage_item", "label": "_is_inbound_cell_storage_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L253"}, {"id": "services_qc_work_order_fee_summary_load_reception_map", "label": "_load_reception_map()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L260"}, {"id": "services_qc_work_order_fee_summary_load_protocol_id_by_no", "label": "_load_protocol_id_by_no()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L274"}, {"id": "services_qc_work_order_fee_summary_effective_collection_protocol_id", "label": "_effective_collection_protocol_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L292"}, {"id": "services_qc_work_order_fee_summary_enrich_work_order_fee_summaries", "label": "enrich_work_order_fee_summaries()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L308"}, {"id": "services_qc_work_order_fee_summary_rationale_1", "label": "\u8d28\u68c0\u5de5\u5355 \u00b7 \u7528\u6237\u8d39\u7528\u6458\u8981\uff08\u91c7\u96c6\u534f\u8bae / \u57fa\u7840\u5b9e\u9a8c\uff09", "file_type": "rationale", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_services_qc_work_order_fee_summary_py", "target": "decimal", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_work_order_py", "label": "qc_work_order.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L1"}, {"id": "schemas_qc_work_order_outboundqcdetailapplication", "label": "OutboundQcDetailApplication", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L5"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "schemas_qc_work_order_outboundqcdetailpatient", "label": "OutboundQcDetailPatient", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L14"}, {"id": "schemas_qc_work_order_outboundqcdetailinfusionplan", "label": "OutboundQcDetailInfusionPlan", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L28"}, {"id": "schemas_qc_work_order_outboundqcdetailinfusionschedule", "label": "OutboundQcDetailInfusionSchedule", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L39"}, {"id": "schemas_qc_work_order_outboundqcdetailproduct", "label": "OutboundQcDetailProduct", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L51"}, {"id": "schemas_qc_work_order_outboundqcdetailresponse", "label": "OutboundQcDetailResponse", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L64"}, {"id": "schemas_qc_work_order_qcworkordercreatebody", "label": "QcWorkOrderCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L76"}, {"id": "schemas_qc_work_order_productionqcworkordercreatebody", "label": "ProductionQcWorkOrderCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L89"}, {"id": "schemas_qc_work_order_qcworkorderupdatebody", "label": "QcWorkOrderUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L98"}, {"id": "schemas_qc_work_order_rationale_99", "label": "\u7f16\u8f91\u8d28\u68c0\u5de5\u5355\uff08\u5165\u5e93/\u51fa\u5e93\u53ef\u6539\u5907\u6ce8\u4e0e\u72b6\u6001\uff1b\u4e34\u65f6\u53ef\u542b\u8d28\u68c0\u5bf9\u8c61\uff09", "file_type": "rationale", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L99"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_work_order_py", "target": "pydantic", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_work_order_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_work_order_py", "target": "schemas_qc_work_order_outboundqcdetailapplication", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L5", "weight": 1.0}, {"source": "schemas_qc_work_order_outboundqcdetailapplication", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_work_order_py", "target": "schemas_qc_work_order_outboundqcdetailpatient", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L14", "weight": 1.0}, {"source": "schemas_qc_work_order_outboundqcdetailpatient", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L14", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_qc_work_order_py", "target": "schemas_qc_work_order_outboundqcdetailinfusionplan", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py", "source_location": "L28", "weight": 1.0}, {"source": "schemas_qc_work_order_outboundqcdetailinfusionplan", "target": "basLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_api_qc_py", "label": "qc.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L1"}, {"id": "api_qc_thirdpartycompanybody", "label": "ThirdPartyCompanyBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L44"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "api_qc_thirdpartycompanyupdatebody", "label": "ThirdPartyCompanyUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L61"}, {"id": "api_qc_list_qc_third_party_companies", "label": "list_qc_third_party_companies()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L79"}, {"id": "api_qc_list_qc_third_party_company_options", "label": "list_qc_third_party_company_options()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L101"}, {"id": "api_qc_create_qc_third_party_company", "label": "create_qc_third_party_company()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L115"}, {"id": "api_qc_update_qc_third_party_company", "label": "update_qc_third_party_company()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L149"}, {"id": "api_qc_delete_qc_third_party_company", "label": "delete_qc_third_party_company()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L185"}, {"id": "api_qc_thirdpartyitembody", "label": "ThirdPartyItemBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L205"}, {"id": "api_qc_thirdpartyitemupdatebody", "label": "ThirdPartyItemUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L217"}, {"id": "api_qc_list_qc_third_party_items", "label": "list_qc_third_party_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L230"}, {"id": "api_qc_list_qc_third_party_items_active", "label": "list_qc_third_party_items_active()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L259"}, {"id": "api_qc_create_qc_third_party_item", "label": "create_qc_third_party_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L274"}, {"id": "api_qc_update_qc_third_party_item", "label": "update_qc_third_party_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L303"}, {"id": "api_qc_delete_qc_third_party_item", "label": "delete_qc_third_party_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L334"}, {"id": "api_qc_labtestitembody", "label": "LabTestItemBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L354"}, {"id": "api_qc_labtestitemupdatebody", "label": "LabTestItemUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L365"}, {"id": "api_qc_itemparambody", "label": "ItemParamBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L379"}, {"id": "api_qc_taskresultbody", "label": "TaskResultBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L384"}, {"id": "api_qc_taskcompletewithresultbody", "label": "TaskCompleteWithResultBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L389"}, {"id": "api_qc_list_qc_lab_test_items", "label": "list_qc_lab_test_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L399"}, {"id": "api_qc_list_qc_lab_test_items_active", "label": "list_qc_lab_test_items_active()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L421"}, {"id": "api_qc_create_qc_lab_test_item", "label": "create_qc_lab_test_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L435"}, {"id": "api_qc_update_qc_lab_test_item", "label": "update_qc_lab_test_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L463"}, {"id": "api_Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_work_order_py", "label": "qc_work_order.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L1"}, {"id": "crud_qc_work_order_resolve_outbound_biz_type_for_schedule", "label": "resolve_outbound_biz_type_for_schedule()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L82"}, {"id": "crud_qc_work_order_dec", "label": "_dec()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L123"}, {"id": "crud_qc_work_order_fmt_dt", "label": "_fmt_dt()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L129"}, {"id": "crud_qc_work_order_normalize_date_bound", "label": "_normalize_date_bound()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L133"}, {"id": "crud_qc_work_order_reception_source_label", "label": "_reception_source_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L140"}, {"id": "crud_qc_work_order_qc_biz_type_label", "label": "_qc_biz_type_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L145"}, {"id": "crud_qc_work_order_work_order_qc_biz_type", "label": "_work_order_qc_biz_type()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L150"}, {"id": "crud_qc_work_order_validate_cell_product_id", "label": "_validate_cell_product_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L157"}, {"id": "crud_qc_work_order_enrich_work_order_cell_products", "label": "_enrich_work_order_cell_products()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L169"}, {"id": "crud_qc_work_order_enrich_outbound_work_orders", "label": "_enrich_outbound_work_orders()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L198"}, {"id": "crud_qc_work_order_outbound_qc_work_order_ids_by_keyword", "label": "_outbound_qc_work_order_ids_by_keyword()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L288"}, {"id": "crud_qc_work_order_accept_conclusion_label", "label": "_accept_conclusion_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L333"}, {"id": "crud_qc_work_order_load_sample_type_name_map", "label": "_load_sample_type_name_map()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L338"}, {"id": "crud_qc_work_order_effective_reception_source", "label": "_effective_reception_source()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L350"}, {"id": "crud_qc_work_order_effective_donation_sample_id", "label": "_effective_donation_sample_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L359"}, {"id": "crud_qc_work_order_effective_protocol_sample_id", "label": "_effective_protocol_sample_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L368"}, {"id": "crud_qc_work_order_sample_type_name_for_item", "label": "_sample_type_name_for_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L376"}, {"id": "crud_qc_work_order_sample_type_name_for_reception", "label": "_sample_type_name_for_reception()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L394"}, {"id": "crud_qc_work_order_enrich_work_order_list_items", "label": "_enrich_work_order_list_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L417"}, {"id": "crud_qc_work_order_enrich_production_work_order_refs", "label": "_enrich_production_work_order_refs()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L550"}, {"id": "crud_qc_work_order_gen_daily_no", "label": "_gen_daily_no()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_orLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_work_order_py", "label": "qc_work_order.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L1"}, {"id": "crud_qc_work_order_resolve_outbound_biz_type_for_schedule", "label": "resolve_outbound_biz_type_for_schedule()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L83"}, {"id": "crud_qc_work_order_dec", "label": "_dec()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L124"}, {"id": "crud_qc_work_order_fmt_dt", "label": "_fmt_dt()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L130"}, {"id": "crud_qc_work_order_normalize_date_bound", "label": "_normalize_date_bound()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L134"}, {"id": "crud_qc_work_order_reception_source_label", "label": "_reception_source_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L141"}, {"id": "crud_qc_work_order_qc_biz_type_label", "label": "_qc_biz_type_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L146"}, {"id": "crud_qc_work_order_work_order_qc_biz_type", "label": "_work_order_qc_biz_type()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L151"}, {"id": "crud_qc_work_order_validate_cell_product_id", "label": "_validate_cell_product_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L158"}, {"id": "crud_qc_work_order_enrich_work_order_cell_products", "label": "_enrich_work_order_cell_products()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L170"}, {"id": "crud_qc_work_order_enrich_outbound_work_orders", "label": "_enrich_outbound_work_orders()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L199"}, {"id": "crud_qc_work_order_outbound_qc_work_order_ids_by_keyword", "label": "_outbound_qc_work_order_ids_by_keyword()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L289"}, {"id": "crud_qc_work_order_accept_conclusion_label", "label": "_accept_conclusion_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L334"}, {"id": "crud_qc_work_order_load_sample_type_name_map", "label": "_load_sample_type_name_map()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L339"}, {"id": "crud_qc_work_order_effective_reception_source", "label": "_effective_reception_source()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L351"}, {"id": "crud_qc_work_order_effective_donation_sample_id", "label": "_effective_donation_sample_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L360"}, {"id": "crud_qc_work_order_effective_protocol_sample_id", "label": "_effective_protocol_sample_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L369"}, {"id": "crud_qc_work_order_sample_type_name_for_item", "label": "_sample_type_name_for_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L377"}, {"id": "crud_qc_work_order_sample_type_name_for_reception", "label": "_sample_type_name_for_reception()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L395"}, {"id": "crud_qc_work_order_enrich_work_order_list_items", "label": "_enrich_work_order_list_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L418"}, {"id": "crud_qc_work_order_enrich_production_work_order_refs", "label": "_enrich_production_work_order_refs()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L582"}, {"id": "crud_qc_work_order_gen_daily_no", "label": "_gen_daily_no()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_orLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_task_py", "label": "qc_task.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L1"}, {"id": "crud_qc_task_dec", "label": "_dec()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L35"}, {"id": "crud_qc_task_fmt_dt", "label": "_fmt_dt()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L41"}, {"id": "crud_qc_task_gen_task_no", "label": "_gen_task_no()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L45"}, {"id": "crud_qc_task_conclusion_label", "label": "_conclusion_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L58"}, {"id": "crud_qc_task_task_to_dict", "label": "_task_to_dict()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L63"}, {"id": "crud_qc_task_task_work_order_context", "label": "_task_work_order_context()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L96"}, {"id": "crud_qc_task_enrich_tasks_reception_fields", "label": "_enrich_tasks_reception_fields()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L133"}, {"id": "crud_qc_task_task_row_to_dict", "label": "_task_row_to_dict()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L241"}, {"id": "crud_qc_task_create_task_for_line", "label": "create_task_for_line()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L252"}, {"id": "crud_qc_task_list_tasks", "label": "list_tasks()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L297"}, {"id": "crud_qc_task_list_mine_tasks", "label": "list_mine_tasks()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L378"}, {"id": "crud_qc_task_assert_task_assignee", "label": "assert_task_assignee()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L461"}, {"id": "crud_qc_task_list_tasks_by_work_order", "label": "list_tasks_by_work_order()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L468"}, {"id": "crud_qc_task_get_task", "label": "get_task()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L486"}, {"id": "crud_qc_task_get_task_detail", "label": "get_task_detail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L495"}, {"id": "crud_qc_task_start_task", "label": "start_task()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L523"}, {"id": "crud_qc_task_set_task_remark", "label": "_set_task_remark()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L540"}, {"id": "crud_qc_task_complete_task", "label": "complete_task()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L544"}, {"id": "crud_qc_task_apply_task_result", "label": "apply_task_result()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L588"}, {"id": "crud_qc_task_update_task_attachments", "label": "update_task_attachments()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L640"}, {"id": "crud_qc_task_create_qc_task_manual", "label": "create_qc_task_manual()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L670"}, {"id": "crud_qc_task_delete_qc_task", "label": "delete_qc_task()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L719"}, {"id": "crud_qc_task_rationale_599", "label": "\u4fdd\u5b58\u68c0\u6d4b\u7ed3\u679c\u4e0e\u7ed3\u8bba\uff1b\u4efb\u52a1\u672a\u5b8c\u6210\u65f6\u987a\u5e26\u6807\u8bb0\u4e3a\u5df2\u5b8c\u6210\u3002", "file_type": "rationale", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py", "source_location": "L599"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_taLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_api_qc_py", "label": "qc.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L1"}, {"id": "api_qc_thirdpartycompanybody", "label": "ThirdPartyCompanyBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L44"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "api_qc_thirdpartycompanyupdatebody", "label": "ThirdPartyCompanyUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L61"}, {"id": "api_qc_list_qc_third_party_companies", "label": "list_qc_third_party_companies()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L79"}, {"id": "api_qc_list_qc_third_party_company_options", "label": "list_qc_third_party_company_options()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L101"}, {"id": "api_qc_create_qc_third_party_company", "label": "create_qc_third_party_company()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L115"}, {"id": "api_qc_update_qc_third_party_company", "label": "update_qc_third_party_company()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L149"}, {"id": "api_qc_delete_qc_third_party_company", "label": "delete_qc_third_party_company()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L185"}, {"id": "api_qc_thirdpartyitembody", "label": "ThirdPartyItemBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L205"}, {"id": "api_qc_thirdpartyitemupdatebody", "label": "ThirdPartyItemUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L217"}, {"id": "api_qc_list_qc_third_party_items", "label": "list_qc_third_party_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L230"}, {"id": "api_qc_list_qc_third_party_items_active", "label": "list_qc_third_party_items_active()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L259"}, {"id": "api_qc_create_qc_third_party_item", "label": "create_qc_third_party_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L274"}, {"id": "api_qc_update_qc_third_party_item", "label": "update_qc_third_party_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L303"}, {"id": "api_qc_delete_qc_third_party_item", "label": "delete_qc_third_party_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L334"}, {"id": "api_qc_labtestitembody", "label": "LabTestItemBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L354"}, {"id": "api_qc_labtestitemupdatebody", "label": "LabTestItemUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L365"}, {"id": "api_qc_itemparambody", "label": "ItemParamBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L379"}, {"id": "api_qc_taskresultbody", "label": "TaskResultBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L384"}, {"id": "api_qc_taskcompletewithresultbody", "label": "TaskCompleteWithResultBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L389"}, {"id": "api_qc_list_qc_lab_test_items", "label": "list_qc_lab_test_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L399"}, {"id": "api_qc_list_qc_lab_test_items_active", "label": "list_qc_lab_test_items_active()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L421"}, {"id": "api_qc_create_qc_lab_test_item", "label": "create_qc_lab_test_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L435"}, {"id": "api_qc_update_qc_lab_test_item", "label": "update_qc_lab_test_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py", "source_location": "L463"}, {"id": "api_Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_models_models_py", "label": "models.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L1"}, {"id": "models_models_userrole", "label": "UserRole", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L10"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "models_models_sysuser", "label": "SysUser", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L22"}, {"id": "base", "label": "Base", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "models_models_sysrole", "label": "SysRole", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L48"}, {"id": "models_models_sysuserrole", "label": "SysUserRole", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L68"}, {"id": "models_models_sysrolemenu", "label": "SysRoleMenu", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L87"}, {"id": "models_models_sysroledatascopedetail", "label": "SysRoleDataScopeDetail", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L106"}, {"id": "models_models_sysmenu", "label": "SysMenu", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L126"}, {"id": "models_models_sysdept", "label": "SysDept", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L152"}, {"id": "models_models_systenant", "label": "SysTenant", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L175"}, {"id": "models_models_syssite", "label": "SysSite", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L195"}, {"id": "models_models_syspost", "label": "SysPost", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L220"}, {"id": "models_models_sysuserpost", "label": "SysUserPost", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L240"}, {"id": "models_models_bizitemunit", "label": "BizItemUnit", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L259"}, {"id": "models_models_bizexpresscompany", "label": "BizExpressCompany", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L278"}, {"id": "models_models_bizcustomer", "label": "BizCustomer", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L297"}, {"id": "models_models_bizcellsampletype", "label": "BizCellSampleType", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L320"}, {"id": "models_models_bizcollectionregion", "label": "BizCollectionRegion", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L341"}, {"id": "models_models_bizcollectionhospital", "label": "BizCollectionHospital", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L359"}, {"id": "models_models_bizcollectionprotocolno", "label": "BizCollectionProtocolNo", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L378"}, {"id": "models_models_bizbiznosequence", "label": "BizBizNoSequence", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L400"}, {"id": "models_models_bizsamplecode", "label": "BizSampleCode", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L420"}, {"id": "models_models_sysdicttype", "label": "SysDictType", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L448"}, {"id": "models_models_sysethnic", "label": "SysEthnic", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L467"}, {"id": "models_models_sysdictdata", "label": "SysDictData", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location"Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_storage_release_review_py", "label": "storage_release_review.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L1"}, {"id": "crud_storage_release_review_fmt_dt", "label": "_fmt_dt()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L49"}, {"id": "crud_storage_release_review_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L53"}, {"id": "crud_storage_release_review_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L59"}, {"id": "crud_storage_release_review_create_review_from_reception", "label": "create_review_from_reception()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L87"}, {"id": "crud_storage_release_review_list_reviews", "label": "list_reviews()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L125"}, {"id": "crud_storage_release_review_enrich_review_list_items", "label": "_enrich_review_list_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L190"}, {"id": "crud_storage_release_review_get_user_name", "label": "_get_user_name()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L271"}, {"id": "crud_storage_release_review_get_review_detail", "label": "get_review_detail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L276"}, {"id": "crud_storage_release_review_build_cell_storage_protocol_info", "label": "_build_cell_storage_protocol_info()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L339"}, {"id": "crud_storage_release_review_build_research_donation_protocol_info", "label": "_build_research_donation_protocol_info()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L396"}, {"id": "crud_storage_release_review_build_cell_storage_transport_info", "label": "_build_cell_storage_transport_info()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L453"}, {"id": "crud_storage_release_review_build_research_donation_transport_info", "label": "_build_research_donation_transport_info()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L487"}, {"id": "crud_storage_release_review_build_reception_info", "label": "_build_reception_info()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L521"}, {"id": "crud_storage_release_review_build_qc_info", "label": "_build_qc_info()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L544"}, {"id": "crud_storage_release_review_build_production_info", "label": "_build_production_info()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L563"}, {"id": "crud_storage_release_review_submit_review", "label": "submit_review()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L581"}, {"id": "crud_storage_release_review_batch_submit_review", "label": "batch_submit_review()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L616"}, {"id": "crud_storage_release_review_store_sample", "label": "store_sample()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L645"}, {"id": "crud_storage_release_review_batch_store_samples", "label": "batch_store_samples()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py", "source_location": "L699"}, {"id": "crud_storage_release_review_get_storage_release_map_for_samples", "label": "get_storage_release_map_for_samples()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/storaLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_work_order_py", "label": "qc_work_order.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L1"}, {"id": "crud_qc_work_order_resolve_outbound_biz_type_for_schedule", "label": "resolve_outbound_biz_type_for_schedule()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L82"}, {"id": "crud_qc_work_order_dec", "label": "_dec()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L123"}, {"id": "crud_qc_work_order_fmt_dt", "label": "_fmt_dt()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L129"}, {"id": "crud_qc_work_order_normalize_date_bound", "label": "_normalize_date_bound()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L133"}, {"id": "crud_qc_work_order_reception_source_label", "label": "_reception_source_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L140"}, {"id": "crud_qc_work_order_qc_biz_type_label", "label": "_qc_biz_type_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L145"}, {"id": "crud_qc_work_order_work_order_qc_biz_type", "label": "_work_order_qc_biz_type()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L150"}, {"id": "crud_qc_work_order_validate_cell_product_id", "label": "_validate_cell_product_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L157"}, {"id": "crud_qc_work_order_enrich_work_order_cell_products", "label": "_enrich_work_order_cell_products()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L169"}, {"id": "crud_qc_work_order_enrich_outbound_work_orders", "label": "_enrich_outbound_work_orders()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L198"}, {"id": "crud_qc_work_order_outbound_qc_work_order_ids_by_keyword", "label": "_outbound_qc_work_order_ids_by_keyword()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L288"}, {"id": "crud_qc_work_order_accept_conclusion_label", "label": "_accept_conclusion_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L333"}, {"id": "crud_qc_work_order_load_sample_type_name_map", "label": "_load_sample_type_name_map()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L338"}, {"id": "crud_qc_work_order_effective_reception_source", "label": "_effective_reception_source()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L350"}, {"id": "crud_qc_work_order_effective_donation_sample_id", "label": "_effective_donation_sample_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L359"}, {"id": "crud_qc_work_order_effective_protocol_sample_id", "label": "_effective_protocol_sample_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L368"}, {"id": "crud_qc_work_order_sample_type_name_for_item", "label": "_sample_type_name_for_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L376"}, {"id": "crud_qc_work_order_sample_type_name_for_reception", "label": "_sample_type_name_for_reception()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L394"}, {"id": "crud_qc_work_order_enrich_work_order_list_items", "label": "_enrich_work_order_list_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L417"}, {"id": "crud_qc_work_order_enrich_production_work_order_refs", "label": "_enrich_production_work_order_refs()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L581"}, {"id": "crud_qc_work_order_gen_daily_no", "label": "_gen_daily_no()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_orLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_qc_work_order_py", "label": "qc_work_order.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L1"}, {"id": "crud_qc_work_order_resolve_outbound_biz_type_for_schedule", "label": "resolve_outbound_biz_type_for_schedule()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L82"}, {"id": "crud_qc_work_order_dec", "label": "_dec()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L123"}, {"id": "crud_qc_work_order_fmt_dt", "label": "_fmt_dt()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L129"}, {"id": "crud_qc_work_order_normalize_date_bound", "label": "_normalize_date_bound()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L133"}, {"id": "crud_qc_work_order_reception_source_label", "label": "_reception_source_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L140"}, {"id": "crud_qc_work_order_qc_biz_type_label", "label": "_qc_biz_type_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L145"}, {"id": "crud_qc_work_order_work_order_qc_biz_type", "label": "_work_order_qc_biz_type()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L150"}, {"id": "crud_qc_work_order_validate_cell_product_id", "label": "_validate_cell_product_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L157"}, {"id": "crud_qc_work_order_enrich_work_order_cell_products", "label": "_enrich_work_order_cell_products()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L169"}, {"id": "crud_qc_work_order_enrich_outbound_work_orders", "label": "_enrich_outbound_work_orders()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L198"}, {"id": "crud_qc_work_order_outbound_qc_work_order_ids_by_keyword", "label": "_outbound_qc_work_order_ids_by_keyword()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L288"}, {"id": "crud_qc_work_order_accept_conclusion_label", "label": "_accept_conclusion_label()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L333"}, {"id": "crud_qc_work_order_load_sample_type_name_map", "label": "_load_sample_type_name_map()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L338"}, {"id": "crud_qc_work_order_effective_reception_source", "label": "_effective_reception_source()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L350"}, {"id": "crud_qc_work_order_effective_donation_sample_id", "label": "_effective_donation_sample_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L359"}, {"id": "crud_qc_work_order_effective_protocol_sample_id", "label": "_effective_protocol_sample_id()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L368"}, {"id": "crud_qc_work_order_sample_type_name_for_item", "label": "_sample_type_name_for_item()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L376"}, {"id": "crud_qc_work_order_sample_type_name_for_reception", "label": "_sample_type_name_for_reception()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L394"}, {"id": "crud_qc_work_order_enrich_work_order_list_items", "label": "_enrich_work_order_list_items()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L417"}, {"id": "crud_qc_work_order_gen_daily_no", "label": "_gen_daily_no()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "L550"}, {"id": "crud_qc_work_order_user_nick_name_map", "label": "_user_nick_name_map()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py", "source_location": "LLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_api_production_py", "label": "production.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L1"}, {"id": "api_production_get_tenant_no", "label": "_get_tenant_no()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L64"}, {"id": "api_production_consumablecreatebody", "label": "ConsumableCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L77"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "api_production_consumableupdatebody", "label": "ConsumableUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L91"}, {"id": "api_production_list_consumables", "label": "list_consumables()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L106"}, {"id": "api_production_list_all_consumables", "label": "list_all_consumables()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L120"}, {"id": "api_production_create_consumable", "label": "create_consumable()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L130"}, {"id": "api_production_update_consumable", "label": "update_consumable()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L158"}, {"id": "api_production_delete_consumable", "label": "delete_consumable()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L187"}, {"id": "api_production_stockinbody", "label": "StockInBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L199"}, {"id": "api_production_stockoutbody", "label": "StockOutBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L205"}, {"id": "api_production_batchstockinitem", "label": "BatchStockInItem", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L213"}, {"id": "api_production_batchstockinbody", "label": "BatchStockInBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L220"}, {"id": "api_production_batchstockoutitem", "label": "BatchStockOutItem", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L226"}, {"id": "api_production_batchstockoutbody", "label": "BatchStockOutBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L232"}, {"id": "api_production_consumable_stock_in", "label": "consumable_stock_in()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L241"}, {"id": "api_production_consumable_stock_out", "label": "consumable_stock_out()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L263"}, {"id": "api_production_consumable_batch_stock_in", "label": "consumable_batch_stock_in()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L287"}, {"id": "api_production_consumable_batch_stock_out", "label": "consumable_batch_stock_out()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L305"}, {"id": "api_production_list_consumable_stock_logs", "label": "list_consumable_stock_logs()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L324"}, {"id": "api_production_list_all_consumable_stock_logs", "label": "list_all_consumable_stock_logs()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L343"}, {"id": "api_production_reagentcreatebody", "label": "ReagentCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L369"}, {"id": "api_production_reagentupdatebody", "label": "ReagentUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L383"}, {"id": "api_production_list_reagents", "label": "list_reagents()", "file_type": "code", "source_fiLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_services_qc_work_order_fee_summary_py", "label": "qc_work_order_fee_summary.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L1"}, {"id": "services_qc_work_order_fee_summary_dec", "label": "_dec()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L26"}, {"id": "services_qc_work_order_fee_summary_received_amount_from_details", "label": "_received_amount_from_details()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L35"}, {"id": "services_qc_work_order_fee_summary_protocol_status_hint", "label": "_protocol_status_hint()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L44"}, {"id": "services_qc_work_order_fee_summary_protocol_overall_status", "label": "_protocol_overall_status()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L68"}, {"id": "services_qc_work_order_fee_summary_build_protocol_payment_summary", "label": "build_protocol_payment_summary()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L90"}, {"id": "services_qc_work_order_fee_summary_build_protocol_payment_summaries", "label": "build_protocol_payment_summaries()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L114"}, {"id": "services_qc_work_order_fee_summary_build_basic_experiment_fee_summary", "label": "build_basic_experiment_fee_summary()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L156"}, {"id": "services_qc_work_order_fee_summary_build_basic_experiment_fee_summaries", "label": "build_basic_experiment_fee_summaries()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L176"}, {"id": "services_qc_work_order_fee_summary_enrich_work_order_fee_summaries", "label": "enrich_work_order_fee_summaries()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L212"}, {"id": "services_qc_work_order_fee_summary_rationale_1", "label": "\u8d28\u68c0\u5de5\u5355 \u00b7 \u7528\u6237\u8d39\u7528\u6458\u8981\uff08\u91c7\u96c6\u534f\u8bae / \u57fa\u7840\u5b9e\u9a8c\uff09", "file_type": "rationale", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_services_qc_work_order_fee_summary_py", "target": "decimal", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_services_qc_work_order_fee_summary_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_services_qc_work_order_fee_summary_py", "target": "sqlalchemy_orm", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L7", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_services_qc_work_order_fee_summary_py", "target": "app_models_clinical_payment", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L9", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_services_qc_work_order_fee_summary_py", "target": "app_models_models", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L10", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_services_qc_work_order_fee_summary_py", "target": "services_qc_work_order_fee_summary_dec", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py", "source_location": "L26", "weight": 1.0}, {"source": "users_wuzLine truncated
+8
View File
@@ -1287,9 +1287,17 @@ class BizQcWorkOrder(Base):
default="inbound",
comment="质检业务类型 inbound入库质检 outbound出库质检 temporary临时质检",
)
trigger_source = Column(
String(20),
nullable=False,
default="reception",
comment="触发来源 reception=样本接收 production=生产过程",
)
infusion_schedule_id = Column(String(64), nullable=True, comment="中试输注计划ID(出库质检关联)")
biz_type = Column(String(20), nullable=True, comment="业务类型: pilot_service=中试服务, clinical_application=临床应用")
cell_product_id = Column(String(64), nullable=True, comment="质检对象-细胞产品ID")
production_work_order_id = Column(String(64), nullable=True, comment="关联生产工单ID(生产过程触发时)")
production_task_id = Column(String(64), nullable=True, comment="关联生产任务ID(可选)")
work_order_status = Column(String(16), nullable=False, default="dpz", comment="工单状态")
has_lab = Column(String(1), nullable=False, default="0", comment="含实验室自检")
has_third_party = Column(String(1), nullable=False, default="0", comment="含第三方检测")
+9
View File
@@ -86,6 +86,15 @@ class QcWorkOrderCreateBody(BaseModel):
remark: Optional[str] = Field(None, description="备注")
class ProductionQcWorkOrderCreateBody(BaseModel):
"""生产过程派发质检工单"""
model_config = ConfigDict(populate_by_name=True)
cell_product_id: str = Field(..., alias="cellProductId", description="细胞产品ID")
production_task_id: Optional[str] = Field(None, alias="productionTaskId", description="关联生产任务ID(可选)")
remark: Optional[str] = Field(None, description="备注")
class QcWorkOrderUpdateBody(BaseModel):
"""编辑质检工单(入库/出库可改备注与状态;临时可含质检对象)"""
model_config = ConfigDict(populate_by_name=True)
@@ -0,0 +1,368 @@
"""质检工单 · 用户费用摘要(采集协议 / 基础实验)"""
from __future__ import annotations
from decimal import Decimal
from datetime import datetime
from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from app.models.clinical_payment import BizPilotPayment
from app.models.models import (
BizPilotInfusionSchedule,
BizProtocolPayment,
BizProtocolPaymentDetail,
)
_PAY_MODE_LABELS = {"dj": "趸交", "fq": "分期"}
_PAY_STATUS_LABELS = {
"dfj": "待缴费",
"jfqrz": "缴费确认中",
"yjf": "已缴费",
"tfqrz": "退费确认中",
"ytf": "已退费",
}
def _dec(val: Any) -> Optional[float]:
if val is None:
return None
try:
return float(val)
except (TypeError, ValueError):
return None
def _received_amount_from_details(details: List[BizProtocolPaymentDetail]) -> float:
total = Decimal("0")
for d in details:
st = (d.pay_status or "").strip()
if st in ("yjf", "jfqrz"):
total += d.transfer_amount or Decimal("0")
return float(total)
def _protocol_status_hint(pay_mode: str, statuses: List[str]) -> str:
counts: Dict[str, int] = {}
for st in statuses:
if st:
counts[st] = counts.get(st, 0) + 1
paid = counts.get("yjf", 0)
confirm = counts.get("jfqrz", 0)
pending = counts.get("dfj", 0)
if pay_mode == "fq":
parts: List[str] = ["分期"]
if paid:
parts.append(f"{paid} 期已缴")
if confirm:
parts.append(f"{confirm} 期待确认")
if pending:
parts.append(f"{pending} 期待缴费")
return " · ".join(parts) if len(parts) > 1 else parts[0]
if confirm:
return f"{confirm} 期待确认" if confirm > 1 else "收款确认中"
if pending:
return "待缴费"
return ""
def _protocol_overall_status(
statuses: List[str], contract: Optional[float], pending: Optional[float]
) -> Dict[str, str]:
if "tfqrz" in statuses:
return {"overallPayStatus": "tfqrz", "overallPayStatusLabel": "退费确认中", "overallPayStatusTagType": "warning"}
if statuses and all(s == "ytf" for s in statuses):
return {"overallPayStatus": "ytf", "overallPayStatusLabel": "已退费", "overallPayStatusTagType": "info"}
if (
contract is not None
and pending is not None
and pending <= 0
and "dfj" not in statuses
and "jfqrz" not in statuses
):
return {"overallPayStatus": "cleared", "overallPayStatusLabel": "已缴清", "overallPayStatusTagType": "success"}
if "jfqrz" in statuses:
return {"overallPayStatus": "jfqrz", "overallPayStatusLabel": "收款确认中", "overallPayStatusTagType": "warning"}
if "dfj" in statuses:
return {"overallPayStatus": "dfj", "overallPayStatusLabel": "待缴费", "overallPayStatusTagType": "info"}
return {"overallPayStatus": "", "overallPayStatusLabel": "—", "overallPayStatusTagType": "info"}
def _fmt_ymd(val: Any) -> str:
s = (str(val or "")).strip()
if len(s) == 8 and s.isdigit():
return f"{s[:4]}-{s[4:6]}-{s[6:8]}"
return s
def _payment_detail_row(d: BizProtocolPaymentDetail, pay_mode: str, idx: int) -> Dict[str, Any]:
st = (d.pay_status or "").strip()
inst_no = d.installment_no
if pay_mode == "fq" and inst_no is not None:
label = f"第{inst_no}期"
else:
label = f"缴费{idx + 1}"
return {
"installmentNo": inst_no,
"installmentLabel": label,
"installmentDueDate": _fmt_ymd(d.installment_due_date),
"dueAmount": _dec(d.due_amount),
"transferAmount": _dec(d.transfer_amount),
"payStatus": st,
"payStatusLabel": _PAY_STATUS_LABELS.get(st, st or "—"),
}
def _sort_payment_details(details: List[BizProtocolPaymentDetail]) -> List[BizProtocolPaymentDetail]:
return sorted(
details,
key=lambda d: (
d.installment_no if d.installment_no is not None else 10_000,
d.order_num if d.order_num is not None else 0,
d.create_time or datetime.min,
),
)
def build_protocol_payment_summary(
pay: Optional[BizProtocolPayment],
details: List[BizProtocolPaymentDetail],
) -> Optional[Dict[str, Any]]:
if not pay:
return None
pay_mode = (pay.pay_mode or "dj").strip() or "dj"
contract = _dec(pay.contract_amount)
sorted_details = _sort_payment_details(details)
detail_rows = [_payment_detail_row(d, pay_mode, i) for i, d in enumerate(sorted_details)]
received = _received_amount_from_details(sorted_details)
pending = max(contract - received, 0) if contract is not None else None
statuses = [(d.pay_status or "").strip() for d in sorted_details]
overall = _protocol_overall_status(statuses, contract, pending)
hint = _protocol_status_hint(pay_mode, statuses) or overall["overallPayStatusLabel"]
return {
"payMode": pay_mode,
"payModeLabel": _PAY_MODE_LABELS.get(pay_mode, pay_mode),
"contractAmount": contract,
"receivedAmount": received,
"pendingAmount": pending,
"statusHint": hint,
"installmentCount": len(detail_rows) if pay_mode == "fq" else None,
"paymentDetails": detail_rows,
**overall,
}
def build_protocol_payment_summaries(
db: Session,
protocol_ids: List[str],
tenant_no: str,
) -> Dict[str, Dict[str, Any]]:
ids = [x.strip() for x in protocol_ids if (x or "").strip()]
if not ids:
return {}
payments = (
db.query(BizProtocolPayment)
.filter(
BizProtocolPayment.collection_protocol_id.in_(ids),
BizProtocolPayment.tenant_no == tenant_no,
BizProtocolPayment.del_flag == 0,
)
.all()
)
pay_by_proto = {p.collection_protocol_id: p for p in payments}
payment_ids = [p.id for p in payments]
details_by_payment: Dict[str, List[BizProtocolPaymentDetail]] = {}
if payment_ids:
for d in (
db.query(BizProtocolPaymentDetail)
.filter(
BizProtocolPaymentDetail.payment_id.in_(payment_ids),
BizProtocolPaymentDetail.tenant_no == tenant_no,
BizProtocolPaymentDetail.del_flag == 0,
)
.all()
):
details_by_payment.setdefault(d.payment_id, []).append(d)
out: Dict[str, Dict[str, Any]] = {}
for pid in ids:
pay = pay_by_proto.get(pid)
if not pay:
continue
summary = build_protocol_payment_summary(pay, details_by_payment.get(pay.id, []))
if summary:
out[pid] = summary
return out
def build_basic_experiment_fee_summary(
schedule: Optional[BizPilotInfusionSchedule],
payment: Optional[BizPilotPayment],
) -> Optional[Dict[str, Any]]:
sale = _dec(schedule.amount) if schedule else None
if not schedule and not payment:
return None
pay_status = (payment.pay_status or "").strip() if payment else ""
transfer = None
if payment and pay_status in ("yjf", "jfqrz"):
transfer = _dec(payment.transfer_amount)
return {
"saleAmount": sale,
"paymentAmount": _dec(payment.amount) if payment else None,
"transferAmount": transfer,
"payStatus": pay_status or None,
"payStatusLabel": _PAY_STATUS_LABELS.get(pay_status, "—") if pay_status else None,
}
def build_basic_experiment_fee_summaries(
db: Session,
schedule_ids: List[str],
tenant_no: str,
) -> Dict[str, Dict[str, Any]]:
ids = [x.strip() for x in schedule_ids if (x or "").strip()]
if not ids:
return {}
schedules = {
s.id: s
for s in db.query(BizPilotInfusionSchedule)
.filter(
BizPilotInfusionSchedule.id.in_(ids),
BizPilotInfusionSchedule.tenant_no == tenant_no,
BizPilotInfusionSchedule.del_flag == 0,
)
.all()
}
payments = {
p.infusion_schedule_id: p
for p in db.query(BizPilotPayment)
.filter(
BizPilotPayment.infusion_schedule_id.in_(ids),
BizPilotPayment.tenant_no == tenant_no,
BizPilotPayment.del_flag == 0,
)
.all()
}
out: Dict[str, Dict[str, Any]] = {}
for sid in ids:
summary = build_basic_experiment_fee_summary(schedules.get(sid), payments.get(sid))
if summary:
out[sid] = summary
return out
def _is_inbound_cell_storage_item(item: Dict[str, Any]) -> bool:
wo_type = (item.get("workOrderType") or "inbound").strip() or "inbound"
if wo_type in ("outbound", "temporary"):
return False
return (item.get("receptionSource") or "cell_storage").strip() in ("", "cell_storage")
def _load_reception_map(db: Session, reception_ids: List[str]) -> Dict[str, Any]:
from app.models.models import BizSampleReception
ids = [x for x in reception_ids if x]
if not ids:
return {}
return {
r.id: r
for r in db.query(BizSampleReception)
.filter(BizSampleReception.id.in_(ids), BizSampleReception.del_flag == 0)
.all()
}
def _load_protocol_id_by_no(db: Session, protocol_nos: List[str], tenant_no: str) -> Dict[str, str]:
from app.models.models import BizCollectionProtocol
nos = [x for x in protocol_nos if x]
if not nos:
return {}
rows = (
db.query(BizCollectionProtocol.protocol_no, BizCollectionProtocol.id)
.filter(
BizCollectionProtocol.protocol_no.in_(nos),
BizCollectionProtocol.tenant_no == tenant_no,
BizCollectionProtocol.del_flag == 0,
)
.all()
)
return {(r.protocol_no or "").strip(): r.id for r in rows if (r.protocol_no or "").strip()}
def _effective_collection_protocol_id(
item: Dict[str, Any],
rec: Optional[Any],
proto_no_map: Dict[str, str],
) -> str:
pid = (item.get("collectionProtocolId") or "").strip()
if pid:
return pid
if rec is not None:
pid = (getattr(rec, "collection_protocol_id", None) or "").strip()
if pid:
return pid
pno = (item.get("protocolNo") or "").strip()
return proto_no_map.get(pno, "")
def enrich_work_order_fee_summaries(
db: Session,
items: List[Dict[str, Any]],
tenant_no: str,
) -> List[Dict[str, Any]]:
if not items or not tenant_no:
return items
inbound_items = [x for x in items if _is_inbound_cell_storage_item(x)]
rec_ids = [(x.get("receptionId") or "").strip() for x in inbound_items if (x.get("receptionId") or "").strip()]
rec_map = _load_reception_map(db, rec_ids)
proto_nos = list(
dict.fromkeys(
(x.get("protocolNo") or "").strip()
for x in inbound_items
if (x.get("protocolNo") or "").strip()
)
)
proto_no_map = _load_protocol_id_by_no(db, proto_nos, tenant_no)
protocol_ids: List[str] = []
for item in inbound_items:
rec = rec_map.get((item.get("receptionId") or "").strip())
pid = _effective_collection_protocol_id(item, rec, proto_no_map)
if pid:
protocol_ids.append(pid)
schedule_ids = list(
dict.fromkeys(
(x.get("infusionScheduleId") or "").strip()
for x in items
if (x.get("workOrderType") or "") == "outbound"
and (x.get("bizType") or "") == "basic_experiment"
and (x.get("infusionScheduleId") or "").strip()
)
)
payment_map = build_protocol_payment_summaries(db, list(dict.fromkeys(protocol_ids)), tenant_no)
fee_map = build_basic_experiment_fee_summaries(db, schedule_ids, tenant_no)
for item in items:
wo_type = item.get("workOrderType") or "inbound"
if wo_type == "outbound":
item["paymentSummary"] = None
if (item.get("bizType") or "") == "basic_experiment":
sid = (item.get("infusionScheduleId") or "").strip()
item["feeSummary"] = fee_map.get(sid) if sid else None
else:
item["feeSummary"] = None
elif wo_type == "temporary":
item["paymentSummary"] = None
item["feeSummary"] = None
else:
item["feeSummary"] = None
if _is_inbound_cell_storage_item(item):
rec = rec_map.get((item.get("receptionId") or "").strip())
pid = _effective_collection_protocol_id(item, rec, proto_no_map)
item["paymentSummary"] = payment_map.get(pid) if pid else None
else:
item["paymentSummary"] = None
return items
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+115 -140
View File
@@ -1,12 +1,12 @@
# Graph Report - app (2026-06-12)
## Corpus Check
- 188 files · ~199,729 words
- 189 files · ~201,737 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 3570 nodes · 16121 edges · 52 communities detected
- Extraction: 37% EXTRACTED · 63% INFERRED · 0% AMBIGUOUS · INFERRED: 10132 edges (avg confidence: 0.61)
- 3596 nodes · 16321 edges · 47 communities detected
- Extraction: 37% EXTRACTED · 63% INFERRED · 0% AMBIGUOUS · INFERRED: 10283 edges (avg confidence: 0.61)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
@@ -51,28 +51,23 @@
- [[_COMMUNITY_Community 38|Community 38]]
- [[_COMMUNITY_Community 39|Community 39]]
- [[_COMMUNITY_Community 40|Community 40]]
- [[_COMMUNITY_Community 41|Community 41]]
- [[_COMMUNITY_Community 42|Community 42]]
- [[_COMMUNITY_Community 43|Community 43]]
- [[_COMMUNITY_Community 44|Community 44]]
- [[_COMMUNITY_Community 45|Community 45]]
- [[_COMMUNITY_Community 46|Community 46]]
- [[_COMMUNITY_Community 47|Community 47]]
- [[_COMMUNITY_Community 48|Community 48]]
- [[_COMMUNITY_Community 49|Community 49]]
- [[_COMMUNITY_Community 50|Community 50]]
- [[_COMMUNITY_Community 51|Community 51]]
- [[_COMMUNITY_Community 52|Community 52]]
- [[_COMMUNITY_Community 53|Community 53]]
## God Nodes (most connected - your core abstractions)
1. `success_response()` - 552 edges
2. `error_response()` - 452 edges
3. `SysUser` - 377 edges
4. `BizQcTaskResult` - 278 edges
1. `success_response()` - 554 edges
2. `error_response()` - 454 edges
3. `SysUser` - 379 edges
4. `BizQcTaskResult` - 280 edges
5. `BizQcMaterialUsage` - 278 edges
6. `BizQcMaterial` - 273 edges
7. `BizQcMaterialStockBatch` - 265 edges
8. `BizMaterialStockLog` - 260 edges
9. `BizPilotPayment` - 259 edges
9. `BizPilotPayment` - 260 edges
10. `BizMaterial` - 257 edges
## Surprising Connections (you probably didn't know these)
@@ -80,8 +75,8 @@
core/api_request_log_middleware.py → models/models.py
- `get_menu_list()` --calls--> `success_response()` [INFERRED]
api/menu.py → core/response.py
- `保留匹配节点及其全部上级,供关键词检索后仍能正确组树。` --uses--> `SysDept` [INFERRED]
crud/dept.py → models/models.py
- `中试服务交付管理 API(交付信息、交付物料、交付方式)` --uses--> `SysUser` [INFERRED]
api/pilot_delivery.py → models/models.py
- `物品单位 CRUD(biz_item_unit)` --uses--> `BizItemUnit` [INFERRED]
crud/item_unit.py → models/models.py
- `快递公司 CRUD(biz_express_company)` --uses--> `BizExpressCompany` [INFERRED]
@@ -91,252 +86,232 @@
### Community 0 - "Community 0"
Cohesion: 0.01
Nodes (622): create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post(), create_role_management() (+614 more)
Nodes (613): create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post(), create_role_management() (+605 more)
### Community 1 - "Community 1"
Cohesion: 0.01
Nodes (502): attachment_download_url(), AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, dict_data_options(), DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody (+494 more)
Nodes (489): BatchPaymentIdsBody, BatchSubmitReceiptBody, BatchSubmitRefundBody, ConfirmReceiptBody, ConfirmRefundBody, PaymentIdBody, SubmitReceiptBody, SubmitRefundBody (+481 more)
### Community 2 - "Community 2"
Cohesion: 0.06
Nodes (303): 环境检测 — 检测项维护 + 登记管理 API, 保险公司及险种价格管理 API(保险公司 + 开票信息 + 险种价格), Base, ApiRequestLogCRUD, 耗材包 CRUD(biz_consumable_pack + biz_consumable_pack_item), 耗材 CRUD(biz_consumable), 字典类型 CRUD(sys_dict_type), 批量入库,同一事务;任一条失败则全部回滚。 (+295 more)
Cohesion: 0.05
Nodes (278): 环境检测 — 检测项维护 + 登记管理 API, InsuranceCompanyCreateBody, InsuranceCompanyInvoiceCreateBody, InsuranceCompanyInvoiceUpdateBody, InsuranceCompanyUpdateBody, InsuranceProjectPriceCreateBody, InsuranceProjectPriceUpdateBody, 保险公司及险种价格管理 API(保险公司 + 开票信息 + 险种价格) (+270 more)
### Community 3 - "Community 3"
Cohesion: 0.02
Nodes (214): list_customers(), list_protocol_nos(), _normalize_date_bound(), list_ethnics_for_select(), _list_insurance_companies(), _total_pages(), apply_data_scope(), _get_child_dept_ids() (+206 more)
Cohesion: 0.07
Nodes (206): CellSampleTypeCreateBody, CellSampleTypeUpdateBody, CollectionRegionCreateBody, CollectionRegionUpdateBody, CustomerCreateBody, CustomerUpdateBody, HospitalCreateBody, HospitalUpdateBody (+198 more)
### Community 4 - "Community 4"
Cohesion: 0.07
Nodes (209): CellSampleTypeCreateBody, CellSampleTypeUpdateBody, CollectionRegionCreateBody, CollectionRegionUpdateBody, CustomerCreateBody, CustomerUpdateBody, HospitalCreateBody, HospitalUpdateBody (+201 more)
Cohesion: 0.05
Nodes (182): generate_uuid(), _deduct_fee_on_delivery(), 中试交付管理 CRUD(交付信息、交付物料、交付方式 + 费用扣减), 创建交付时扣减费用。 流程: 1. schedule → plan → application 2. application.chan, List all deliveries for an application (through plan → schedule → delivery)., Bulk save: soft-delete existing + insert new., Create or update delivery method (upsert pattern)., activate_channel_agreement() (+174 more)
### Community 5 - "Community 5"
Cohesion: 0.02
Nodes (177): generate_uuid(), _apply_cell_product_status_change(), create_cell_product(), batch_stock_in(), batch_stock_out(), consumable_code_exists(), create_consumable(), _dec() (+169 more)
Nodes (153): delete(), get_multi_with_filters(), create_cell_sample_type(), get_cell_sample_type(), list_cell_sample_types(), 细胞样本类型 CRUD(biz_cell_sample_type), _row_to_dict(), soft_delete_cell_sample_type() (+145 more)
### Community 6 - "Community 6"
Cohesion: 0.02
Nodes (173): _format_attachment_file_size(), urls_to_attachment_list(), list_production_tasks(), apply_task_result(), assert_task_assignee(), _active_users_in_tenant(), enrich_tasks_with_assignees(), is_user_assignee() (+165 more)
Cohesion: 0.03
Nodes (117): attachment_download_url(), AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, dict_data_options(), DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody (+109 more)
### Community 7 - "Community 7"
Cohesion: 0.02
Nodes (142): get_role_menus(), get_user_by_id(), _avatar_url_field(), _build_current_user_payload(), _build_user_info_payload(), get_current_user_profile(), _jwt_user_payload(), logout() (+134 more)
Cohesion: 0.03
Nodes (95): get_designer_data(), _error_json(), lifespan(), 数据库异常:返回 JSON 并保留 CORS 头,避免浏览器只报跨域错误, 未捕获异常:统一 JSON 500,确保跨域场景下仍有 Access-Control-Allow-Origin, sqlalchemy_exception_handler(), unhandled_exception_handler(), BaseHTTPMiddleware (+87 more)
### Community 8 - "Community 8"
Cohesion: 0.03
Nodes (103): _business_type_label(), get_business_all_tasks(), _get_business_form_fields(), get_designer_data(), get_instance_detail(), ProcessDefCreateBody, ProcessDefUpdateBody, PublishVersionBody (+95 more)
Nodes (108): batch_delete_research_donations(), change_donation_status(), convert_from_storage(), convert_to_storage(), create_research_donation(), delete_research_donation(), _get_current_user_id(), get_research_donation_detail() (+100 more)
### Community 9 - "Community 9"
Cohesion: 0.04
Nodes (100): _ensure_customer_for_mother(), _persist_protocol_sub_resources(), align_fq_payment_details_payload(), _apply_payment_detail_list_filters(), _apply_submit_receipt_finance(), _apply_submit_refund_finance(), attachments_to_urls(), _audit_to_dict() (+92 more)
Nodes (115): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), list_production_tasks(), _outbound_work_order_ids_by_keyword() (+107 more)
### Community 10 - "Community 10"
Cohesion: 0.04
Nodes (98): eq_unicode(), like_unicode(), MySQL 字符串比较/模糊匹配时统一排序规则,避免 Illegal mix of collations。, 跨表字符串等值比较(JOIN / WHERE)时统一为 utf8mb4_unicode_ci。, 将列显式设为 utf8mb4_unicode_ci。, unicode_ci(), _fmt_dt(), get_handover_by_schedule() (+90 more)
Cohesion: 0.03
Nodes (77): _avatar_url_field(), _build_current_user_payload(), _build_user_info_payload(), get_current_user_profile(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致) (+69 more)
### Community 11 - "Community 11"
Cohesion: 0.07
Nodes (68): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, OccupancyCreateBody, 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约) (+60 more)
Cohesion: 0.04
Nodes (110): _persist_protocol_sub_resources(), list_ethnics_for_select(), apply_data_scope(), align_fq_payment_details_payload(), _apply_payment_detail_list_filters(), _apply_submit_receipt_finance(), _apply_submit_refund_finance(), attachments_to_urls() (+102 more)
### Community 12 - "Community 12"
Cohesion: 0.35
Nodes (61): BatchStockInBody, BatchStockInItem, BatchStockOutBody, BatchStockOutItem, ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody (+53 more)
Cohesion: 0.03
Nodes (98): get_protocol_nos_used_in_protocols(), create_dict_type(), dict_type_code_exists(), get_dict_type(), list_dict_types(), _row_to_dict(), soft_delete_dict_type(), update_dict_type() (+90 more)
### Community 13 - "Community 13"
Cohesion: 0.07
Nodes (58): batch_delete_research_donations(), change_donation_status(), convert_from_storage(), convert_to_storage(), create_research_donation(), delete_research_donation(), _get_current_user_id(), get_research_donation_detail() (+50 more)
Cohesion: 0.04
Nodes (95): _format_attachment_file_size(), urls_to_attachment_list(), get_check_results_for_reception(), _legacy_results_from_reception(), list_results_by_reception_ids(), _result_to_dict(), soft_delete_reception_check_results(), sync_legacy_columns_from_results() (+87 more)
### Community 14 - "Community 14"
Cohesion: 0.08
Nodes (51): code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), _is_enabled_status(), list_materials(), _norm_status() (+43 more)
Cohesion: 0.05
Nodes (90): api_save_application(), auto_create_payment(), auto_generate_schedules(), complete_application(), create_application(), create_basic_experiment_sale_schedules(), create_infusion_plan(), create_schedule() (+82 more)
### Community 15 - "Community 15"
Cohesion: 0.09
Nodes (50): complete_application(), create_basic_experiment_sale_schedules(), create_infusion_plan(), create_schedule(), _create_schedule_products(), _format_yyyy_mm_dd(), _generate_application_no(), get_application() (+42 more)
Cohesion: 0.06
Nodes (87): AccessLogCreateBody, AccessLogUpdateBody, batch_create_storage_slots(), CleanRoomCreateBody, CleanRoomUpdateBody, create_access_log(), create_storage_slot(), delete_access_log() (+79 more)
### Community 16 - "Community 16"
Cohesion: 0.06
Nodes (18): extract_avatar_storage_key(), 用户头像 URL 解析(sys_user.avatar 可能存 MinIO 相对路径或完整 URL), 从 upload 返回值或历史完整 URL 中提取 avatar/ 相对路径。, 服务端内部拼完整 URL(邮件等);Web API 应返回库中原始路径。, resolve_avatar_display_url(), BaseStorage, convert_heic_to_jpeg(), get_storage() (+10 more)
Cohesion: 0.08
Nodes (49): code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), _is_enabled_status(), list_materials(), _norm_status() (+41 more)
### Community 17 - "Community 17"
Cohesion: 0.12
Nodes (32): get_next_cell_product_code(), _ancestor_link_from_cell_product_input(), _ancestor_sample_chain_link(), _build_ancestor_chain(), _build_descendant_chain(), _build_facility_path_label(), build_split_product_codes(), create_cell_products_batch() (+24 more)
Nodes (32): add_task_step(), _clone_route_to_task(), _clone_step_consumables(), _clone_step_data_collections(), _clone_step_outputs(), _clone_step_reagents(), _consume_task_input_cell_products(), _create_occupancy_for_product() (+24 more)
### Community 18 - "Community 18"
Cohesion: 0.11
Nodes (33): get_next_cell_product_code(), _ancestor_link_from_cell_product_input(), _ancestor_sample_chain_link(), _apply_cell_product_status_change(), _build_ancestor_chain(), _build_descendant_chain(), _build_facility_path_label(), build_split_product_codes() (+25 more)
### Community 19 - "Community 19"
Cohesion: 0.12
Nodes (11): get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块 支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.ht, 统一下单接口 (V3) Args: out_trade_no: 商户订单号 total, 申请退款 Args: out_trade_no: 商户订单号 total_amount, 小程序调起支付的二次签名参数(与公众号 JSAPI 规则一致), 统一下单 (V2 API) Args: out_trade_no: 商户订单号 tot, 生成签名 V3 (SHA256 with RSA) (+3 more)
### Community 19 - "Community 19"
Cohesion: 0.21
Nodes (23): _amount_for_row(), _batch_map(), deduct_for_task(), _deduct_task_usage_legacy_batch(), _display_remark(), list_linked_task_usages(), list_my_usages(), list_requisition_options() (+15 more)
### Community 20 - "Community 20"
Cohesion: 0.16
Nodes (26): add_step(), copy_step(), create_route(), _create_step_consumable(), _create_step_data_collection(), _create_step_output(), _create_step_reagent(), get_route() (+18 more)
### Community 21 - "Community 21"
Cohesion: 0.17
Nodes (18): get_by_schedule(), get_method(), _get_tenant_no(), list_by_application(), list_materials(), 中试服务交付管理 API(交付信息、交付物料、交付方式), soft_delete(), create_delivery() (+10 more)
### Community 22 - "Community 22"
Cohesion: 0.21
Nodes (19): _consumable_row_to_dict(), create_process(), _create_sub_consumable(), _create_sub_data_collection(), _create_sub_output(), _create_sub_reagent(), _data_collection_row_to_dict(), get_process() (+11 more)
### Community 23 - "Community 23"
Cohesion: 0.18
Nodes (18): company_code_exists(), create_company(), get_company(), list_companies(), list_company_options(), _row_to_dict(), soft_delete_company(), update_company() (+10 more)
### Community 21 - "Community 21"
### Community 24 - "Community 24"
Cohesion: 0.21
Nodes (15): batch_generate_protocol_nos(), batch_issue_protocol_nos(), batch_revoke_protocol_nos(), create_protocol_no(), get_protocol_no(), list_protocol_nos(), _parse_issued_time(), protocol_no_exists() (+7 more)
### Community 22 - "Community 22"
Cohesion: 0.23
Nodes (12): create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_names_by_region_ids(), _hospital_to_dict(), list_collection_regions(), list_hospitals() (+4 more)
### Community 23 - "Community 23"
### Community 25 - "Community 25"
Cohesion: 0.24
Nodes (9): 微信消息通知服务 支持微信小程序模板消息推送 使用方式: from app.core.wechat_message import Wech, 发送模板消息 Args: openid: 用户openid templ, send_delivery_notice(), send_delivery_warning(), send_order_status(), send_payment_success(), send_refund_result(), send_urge_notification() (+1 more)
### Community 24 - "Community 24"
Cohesion: 0.24
Nodes (11): deduct_for_step(), _display_remark(), list_stock_logs(), _parse_receiver_purpose_from_remark(), 试剂变动记录 CRUD(biz_reagent_stock_log), 撤销步骤此前 step_save 扣减,便于重新填报。, 扣减试剂库存并生成变动记录。调用方控制事务,本函数不 commit。, _resolve_receiver_purpose() (+3 more)
### Community 26 - "Community 26"
Cohesion: 0.23
Nodes (12): Config, ProductBase, ProductCategoryBase, ProductCategoryCreate, ProductCategoryResponse, ProductCreate, ProductResponse, ProductUnitBase (+4 more)
### Community 25 - "Community 25"
### Community 27 - "Community 27"
Cohesion: 0.31
Nodes (12): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), list_materials(), options_list(), _row_to_dict() (+4 more)
### Community 28 - "Community 28"
Cohesion: 0.32
Nodes (11): create_env_detect_record(), _generate_record_no(), get_env_detect_record(), _join_csv(), list_env_detect_records(), _resolve_inspector_snapshots(), _resolve_item_snapshot(), _row_to_dict() (+3 more)
### Community 26 - "Community 26"
Cohesion: 0.27
Nodes (11): _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept(), get_dept(), list_depts(), list_depts_tree(), 保留匹配节点及其全部上级,供关键词检索后仍能正确组树。 (+3 more)
### Community 29 - "Community 29"
Cohesion: 0.24
Nodes (9): _ensure_customer_for_mother(), create_customer(), get_customer(), id_card_taken(), list_customers(), 客户 CRUD(biz_customer), _row_to_dict(), soft_delete_customer() (+1 more)
### Community 27 - "Community 27"
### Community 30 - "Community 30"
Cohesion: 0.3
Nodes (10): create_consumable_pack(), get_consumable_pack(), get_pack_detail(), _item_row_to_dict(), list_all_packs(), list_consumable_packs(), pack_code_exists(), _row_to_dict() (+2 more)
### Community 31 - "Community 31"
Cohesion: 0.29
Nodes (9): _build_tree(), _cascade_soft_delete(), create_menu(), get_menu(), list_menus(), list_menus_tree(), _row_to_dict(), soft_delete_menu() (+1 more)
### Community 28 - "Community 28"
Cohesion: 0.31
Nodes (8): create_express_company(), express_code_exists(), get_express_company(), list_express_companies(), 快递公司 CRUD(biz_express_company), _row_to_dict(), soft_delete_express_company(), update_express_company()
### Community 29 - "Community 29"
Cohesion: 0.38
Nodes (9): create_check_item(), get_check_item(), item_code_exists(), list_active_check_items(), list_check_items(), _order_query(), _row_to_dict(), soft_delete_check_item() (+1 more)
### Community 30 - "Community 30"
### Community 32 - "Community 32"
Cohesion: 0.38
Nodes (9): create_env_detect_item(), get_env_detect_item(), item_code_exists(), list_active_env_detect_items(), list_env_detect_items(), _order_query(), _row_to_dict(), soft_delete_env_detect_item() (+1 more)
### Community 31 - "Community 31"
Cohesion: 0.31
Nodes (8): category_code_exists(), create_process_category(), get_process_category(), list_process_categories(), 工艺工序分类 CRUD(biz_process_category), _row_to_dict(), soft_delete_process_category(), update_process_category()
### Community 32 - "Community 32"
Cohesion: 0.31
Nodes (8): create_item_unit(), get_item_unit(), list_item_units(), 物品单位 CRUD(biz_item_unit), _row_to_dict(), soft_delete_item_unit(), unit_code_exists(), update_item_unit()
### Community 33 - "Community 33"
Cohesion: 0.31
Nodes (8): create_cell_sample_type(), get_cell_sample_type(), list_cell_sample_types(), 细胞样本类型 CRUD(biz_cell_sample_type), _row_to_dict(), soft_delete_cell_sample_type(), type_code_exists(), update_cell_sample_type()
### Community 34 - "Community 34"
Cohesion: 0.31
Nodes (8): create_output_type(), get_output_type(), list_output_types(), 工艺产出物类型 CRUD(biz_output_type), _row_to_dict(), soft_delete_output_type(), type_code_exists(), update_output_type()
### Community 35 - "Community 35"
Cohesion: 0.36
Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_dict(), soft_delete_post(), update_post()
### Community 36 - "Community 36"
Cohesion: 0.36
Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
### Community 37 - "Community 37"
Cohesion: 0.36
Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
### Community 38 - "Community 38"
Cohesion: 0.25
Nodes (7): 菜单 code 权限守卫(V0.3:通过 sys_user_role → sys_role_menu → sys_menu.perms), 运营端角色守卫:admin / super_admin 可访问。, 接口菜单权限守卫。super_admin 直接放行。, 接口菜单权限守卫:拥有任一 menuCode 即可访问。super_admin 直接放行。, require_admin_or_super_admin(), require_any_menu_code(), require_menu_code()
### Community 39 - "Community 39"
### Community 34 - "Community 34"
Cohesion: 0.29
Nodes (2): BaseModel, SoftDeleteModel
### Community 40 - "Community 40"
### Community 35 - "Community 35"
Cohesion: 0.38
Nodes (6): derive_legacy_status_from_three(), derive_three_status_from_legacy(), 阶段A兼容:从旧 status 推导三字段。 约束:旧 status 本身混合了支付/配送/订单生命周期,因此只能尽力推断; 若业务侧已有更精确, 阶段A兼容:从三字段反推旧 status(用于旧前端/旧筛选参数兼容)。, ThreeStatus, _to_decimal()
### Community 41 - "Community 41"
### Community 36 - "Community 36"
Cohesion: 0.5
Nodes (3): BaseSettings, Config, Settings
### Community 42 - "Community 42"
### Community 37 - "Community 37"
Cohesion: 0.5
Nodes (2): _auto_fill_create_dept_id(), ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID
### Community 43 - "Community 43"
### Community 38 - "Community 38"
Cohesion: 0.67
Nodes (2): get_menu_list(), 运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,13 个一级菜单)
### Community 44 - "Community 44"
### Community 39 - "Community 39"
Cohesion: 1.0
Nodes (1): 请求级别的用户上下文变量 通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传
### Community 45 - "Community 45"
### Community 40 - "Community 40"
Cohesion: 1.0
Nodes (1): API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)
### Community 48 - "Community 48"
### Community 43 - "Community 43"
Cohesion: 1.0
Nodes (1): 发送订单状态通知 Args: openid: 用户openid ord
### Community 49 - "Community 49"
### Community 44 - "Community 44"
Cohesion: 1.0
Nodes (1): 发送支付成功通知 Args: openid: 用户openid ord
### Community 50 - "Community 50"
### Community 45 - "Community 45"
Cohesion: 1.0
Nodes (1): 发送配送通知 Args: openid: 配送员openid task
### Community 51 - "Community 51"
### Community 46 - "Community 46"
Cohesion: 1.0
Nodes (1): 发送配送时效预警 Args: openid: 配送员openid ta
### Community 52 - "Community 52"
### Community 47 - "Community 47"
Cohesion: 1.0
Nodes (1): 发送退款结果通知 Args: openid: 用户openid ord
### Community 53 - "Community 53"
### Community 48 - "Community 48"
Cohesion: 1.0
Nodes (1): 发送催单通知给配送员 Args: deliver_openid: 配送员openid
## Knowledge Gaps
- **109 isolated node(s):** `刷新会话 TTL(refreshToken 续期;已登录用户每次 API 校验通过后滑动续期)`, `写入 refreshToken(用于 one-time 使用/吊销)`, `根据 token 获取当前用户(包含 Redis 会话校验;会话滑动续期)`, `登录凭据 RSA 解密(与前端 JSEncrypt PKCS#1 v1.5 兼容)。`, `Base64 密文 -> JSON 对象(须含 username、password、ts 毫秒时间戳)。` (+104 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 39`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
- **Thin community `Community 34`** (8 nodes): `BaseModel`, `.to_dict()`, `get_active()`, `base.py`, `SoftDeleteModel`, `.restore()`, `.soft_delete()`, `__tablename__()`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 42`** (4 nodes): `_auto_fill_create_dept_id()`, `get_db()`, `database.py`, `ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID`
- **Thin community `Community 37`** (4 nodes): `_auto_fill_create_dept_id()`, `get_db()`, `database.py`, `ORM before_insert 钩子:当 create_dept_id 为空时,自动从请求上下文填充当前用户部门 ID`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 43`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,13 个一级菜单)`
- **Thin community `Community 38`** (3 nodes): `get_menu_list()`, `menu.py`, `运营端菜单接口(与 web-admin 前端静态路由 sqxp.ts 对齐,13 个一级菜单)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 44`** (2 nodes): `context.py`, `请求级别的用户上下文变量 通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
- **Thin community `Community 39`** (2 nodes): `context.py`, `请求级别的用户上下文变量 通过 contextvars 在请求处理链路中传递当前用户信息, 供 SQLAlchemy 事件监听器等底层机制使用,避免逐层手动传`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 45`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
- **Thin community `Community 40`** (2 nodes): `__init__.py`, `API 路由包(骨架:公共鉴权 + 静态菜单 + 运营端占位)`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 48`** (1 nodes): `发送订单状态通知 Args: openid: 用户openid ord`
- **Thin community `Community 43`** (1 nodes): `发送订单状态通知 Args: openid: 用户openid ord`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 49`** (1 nodes): `发送支付成功通知 Args: openid: 用户openid ord`
- **Thin community `Community 44`** (1 nodes): `发送支付成功通知 Args: openid: 用户openid ord`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 50`** (1 nodes): `发送配送通知 Args: openid: 配送员openid task`
- **Thin community `Community 45`** (1 nodes): `发送配送通知 Args: openid: 配送员openid task`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 51`** (1 nodes): `发送配送时效预警 Args: openid: 配送员openid ta`
- **Thin community `Community 46`** (1 nodes): `发送配送时效预警 Args: openid: 配送员openid ta`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 52`** (1 nodes): `发送退款结果通知 Args: openid: 用户openid ord`
- **Thin community `Community 47`** (1 nodes): `发送退款结果通知 Args: openid: 用户openid ord`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 53`** (1 nodes): `发送催单通知给配送员 Args: deliver_openid: 配送员openid`
- **Thin community `Community 48`** (1 nodes): `发送催单通知给配送员 Args: deliver_openid: 配送员openid`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `SysUser` connect `Community 1` to `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 21`, `Community 24`?**
_High betweenness centrality (0.113) - this node is a cross-community bridge._
- **Why does `generate_uuid()` connect `Community 5` to `Community 0`, `Community 2`, `Community 3`, `Community 4`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 13`, `Community 14`, `Community 17`, `Community 19`, `Community 20`, `Community 21`, `Community 22`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`?**
_High betweenness centrality (0.069) - this node is a cross-community bridge._
- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 3`, `Community 7`, `Community 8`, `Community 43`, `Community 13`, `Community 17`?**
_High betweenness centrality (0.055) - this node is a cross-community bridge._
- **Are the 560 inferred relationships involving `str` (e.g. with `unhandled_exception_handler()` and `get_current_user_by_token()`) actually correct?**
_`str` has 560 INFERRED edges - model-reasoned connections that need verification._
- **Are the 551 inferred relationships involving `success_response()` (e.g. with `list_storage_release_reviews()` and `get_storage_release_review_detail()`) actually correct?**
_`success_response()` has 551 INFERRED edges - model-reasoned connections that need verification._
- **Are the 451 inferred relationships involving `error_response()` (e.g. with `_json_error_response()` and `list_storage_release_reviews()`) actually correct?**
_`error_response()` has 451 INFERRED edges - model-reasoned connections that need verification._
- **Are the 375 inferred relationships involving `SysUser` (e.g. with `数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤) data_scope 值: 1 = 全部数据 2 = 本部门数据 3` and `从 SysUser 读取用户的 dept_id。`) actually correct?**
_`SysUser` has 375 INFERRED edges - model-reasoned connections that need verification._
- **Why does `SysUser` connect `Community 1` to `Community 2`, `Community 3`, `Community 4`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 12`, `Community 15`, `Community 21`, `Community 24`?**
_High betweenness centrality (0.117) - this node is a cross-community bridge._
- **Why does `generate_uuid()` connect `Community 4` to `Community 0`, `Community 2`, `Community 3`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 18`, `Community 20`, `Community 22`, `Community 23`, `Community 24`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`?**
_High betweenness centrality (0.066) - this node is a cross-community bridge._
- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 38`, `Community 6`, `Community 8`, `Community 7`, `Community 10`, `Community 11`, `Community 14`, `Community 15`, `Community 18`, `Community 21`?**
_High betweenness centrality (0.060) - this node is a cross-community bridge._
- **Are the 563 inferred relationships involving `str` (e.g. with `unhandled_exception_handler()` and `get_current_user_by_token()`) actually correct?**
_`str` has 563 INFERRED edges - model-reasoned connections that need verification._
- **Are the 553 inferred relationships involving `success_response()` (e.g. with `list_storage_release_reviews()` and `get_storage_release_review_detail()`) actually correct?**
_`success_response()` has 553 INFERRED edges - model-reasoned connections that need verification._
- **Are the 453 inferred relationships involving `error_response()` (e.g. with `_json_error_response()` and `list_storage_release_reviews()`) actually correct?**
_`error_response()` has 453 INFERRED edges - model-reasoned connections that need verification._
- **Are the 377 inferred relationships involving `SysUser` (e.g. with `数据权限过滤(V0.3:基于角色 data_scope 的查询级行级过滤) data_scope 值: 1 = 全部数据 2 = 本部门数据 3` and `从 SysUser 读取用户的 dept_id。`) actually correct?**
_`SysUser` has 377 INFERRED edges - model-reasoned connections that need verification._
+4 -4
View File
@@ -57,12 +57,12 @@
</div>
<div id="legend"></div>
</div>
<div id="stats">3570 nodes &middot; 16121 edges &middot; 58 communities</div>
<div id="stats">3596 nodes &middot; 16321 edges &middot; 53 communities</div>
</div>
<script>
const RAW_NODES = [{"id": "init_py", "label": "__init__.py", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 46, "community_name": "Community 46", "source_file": "__init__.py", "file_type": "code", "degree": 0}, {"id": "main_py", "label": "main.py", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "main.py", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 7}, {"id": "app_main_lifespan", "label": "lifespan()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "lifespan()", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_error_json", "label": "_error_json()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "_error_json()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 3}, {"id": "app_main_sqlalchemy_exception_handler", "label": "sqlalchemy_exception_handler()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "sqlalchemy_exception_handler()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_unhandled_exception_handler", "label": "unhandled_exception_handler()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "unhandled_exception_handler()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 5}, {"id": "app_main_mask_secret", "label": "_mask_secret()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "_mask_secret()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_root", "label": "root()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "root()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_health_check", "label": "health_check()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "health_check()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_rationale_81", "label": "\u6570\u636e\u5e93\u5f02\u5e38\uff1a\u8fd4\u56de JSON \u5e76\u4fdd\u7559 CORS \u5934\uff0c\u907f\u514d\u6d4f\u89c8\u5668\u53ea\u62a5\u8de8\u57df\u9519\u8bef", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "\u6570\u636e\u5e93\u5f02\u5e38\uff1a\u8fd4\u56de JSON \u5e76\u4fdd\u7559 CORS \u5934\uff0c\u907f\u514d\u6d4f\u89c8\u5668\u53ea\u62a5\u8de8\u57df\u9519\u8bef", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "rationale", "degree": 3}, {"id": "app_main_rationale_92", "label": "\u672a\u6355\u83b7\u5f02\u5e38\uff1a\u7edf\u4e00 JSON 500\uff0c\u786e\u4fdd\u8de8\u57df\u573a\u666f\u4e0b\u4ecd\u6709 Access-Control-Allow-Origin", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "\u672a\u6355\u83b7\u5f02\u5e38\uff1a\u7edf\u4e00 JSON 500\uff0c\u786e\u4fdd\u8de8\u57df\u573a\u666f\u4e0b\u4ecd\u6709 Access-Control-Allow-Origin", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "rationale", "degree": 3}, {"id": "tasks_init_py", "label": "__init__.py", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 7, "community_name": "Community 7", "source_file": "tasks/__init__.py", "file_type": "code", "degLine truncated
const RAW_EDGES = [{"from": "main_py", "to": "app_main_lifespan", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_error_json", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_sqlalchemy_exception_handler", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_unhandled_exception_handler", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_mask_secret", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_root", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_health_check", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_lifespan", "to": "core_login_crypto_ensure_initialized", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_lifespan", "to": "tasks_init_init_scheduled_tasks", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_lifespan", "to": "tasks_init_shutdown_scheduler", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_error_json", "to": "app_main_sqlalchemy_exception_handler", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_error_json", "to": "app_main_unhandled_exception_handler", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_sqlalchemy_exception_handler", "to": "app_main_rationale_81", "label": "rationale_for", "title": "rationale_for [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_sqlalchemy_exception_handler", "to": "exception", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_unhandled_exception_handler", "to": "app_main_rationale_92", "label": "rationale_for", "title": "rationale_for [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_unhandled_exception_handler", "to": "exception", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_unhandled_exception_handler", "to": "str", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_rationale_81", "to": "core_api_request_log_middleware_apirequestlogmiddleware", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_rationale_81", "to": "core_api_crypto_middleware_apicryptomiddleware", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_rationale_92", "to": "core_api_request_log_middleware_apirequestlogmiddleware", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_rationale_92", "to": "core_api_crypto_middleware_apicryptomiddleware", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "tasks_init_py", "to": "tasks_init_init_scheduled_tasks", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tasks_init_py", "to": "tasks_init_shutdown_scheduler", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_redis_session_enabled", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_redis_client", "labeLine truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 646}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 538}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 308}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 241}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 212}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 197}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 189}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 173}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 137}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 110}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 106}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 72}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 68}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 67}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 56}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 52}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 51}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 34}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 31}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 24}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 20}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 17}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 14}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 13}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 12}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 12}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 12}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 11}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 10}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 10}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 10}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 10}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 10}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 10}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 10}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 9}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 9}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 9}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 8}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 8}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 7}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 4}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 4}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 3}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 2}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 2}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 1}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 1}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 1}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 1}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 1}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 1}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 1}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 1}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 1}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 1}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 1}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 1}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 0}];
const RAW_NODES = [{"id": "init_py", "label": "__init__.py", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 41, "community_name": "Community 41", "source_file": "__init__.py", "file_type": "code", "degree": 0}, {"id": "main_py", "label": "main.py", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "main.py", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 7}, {"id": "app_main_lifespan", "label": "lifespan()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "lifespan()", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_error_json", "label": "_error_json()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "_error_json()", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 3}, {"id": "app_main_sqlalchemy_exception_handler", "label": "sqlalchemy_exception_handler()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "sqlalchemy_exception_handler()", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_unhandled_exception_handler", "label": "unhandled_exception_handler()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "unhandled_exception_handler()", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 5}, {"id": "app_main_mask_secret", "label": "_mask_secret()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "_mask_secret()", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_root", "label": "root()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "root()", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_health_check", "label": "health_check()", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "health_check()", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_rationale_81", "label": "\u6570\u636e\u5e93\u5f02\u5e38\uff1a\u8fd4\u56de JSON \u5e76\u4fdd\u7559 CORS \u5934\uff0c\u907f\u514d\u6d4f\u89c8\u5668\u53ea\u62a5\u8de8\u57df\u9519\u8bef", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "\u6570\u636e\u5e93\u5f02\u5e38\uff1a\u8fd4\u56de JSON \u5e76\u4fdd\u7559 CORS \u5934\uff0c\u907f\u514d\u6d4f\u89c8\u5668\u53ea\u62a5\u8de8\u57df\u9519\u8bef", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "rationale", "degree": 3}, {"id": "app_main_rationale_92", "label": "\u672a\u6355\u83b7\u5f02\u5e38\uff1a\u7edf\u4e00 JSON 500\uff0c\u786e\u4fdd\u8de8\u57df\u573a\u666f\u4e0b\u4ecd\u6709 Access-Control-Allow-Origin", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "\u672a\u6355\u83b7\u5f02\u5e38\uff1a\u7edf\u4e00 JSON 500\uff0c\u786e\u4fdd\u8de8\u57df\u573a\u666f\u4e0b\u4ecd\u6709 Access-Control-Allow-Origin", "community": 7, "community_name": "Community 7", "source_file": "main.py", "file_type": "rationale", "degree": 3}, {"id": "tasks_init_py", "label": "__init__.py", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 7, "community_name": "Community 7", "source_file": "tasks/__init__.py", "file_type": "code", "degLine truncated
const RAW_EDGES = [{"from": "main_py", "to": "app_main_lifespan", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_error_json", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_sqlalchemy_exception_handler", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_unhandled_exception_handler", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_mask_secret", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_root", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_health_check", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_lifespan", "to": "core_login_crypto_ensure_initialized", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_lifespan", "to": "tasks_init_init_scheduled_tasks", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_lifespan", "to": "tasks_init_shutdown_scheduler", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_error_json", "to": "app_main_sqlalchemy_exception_handler", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_error_json", "to": "app_main_unhandled_exception_handler", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_sqlalchemy_exception_handler", "to": "app_main_rationale_81", "label": "rationale_for", "title": "rationale_for [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_sqlalchemy_exception_handler", "to": "exception", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_unhandled_exception_handler", "to": "app_main_rationale_92", "label": "rationale_for", "title": "rationale_for [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_unhandled_exception_handler", "to": "exception", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_unhandled_exception_handler", "to": "str", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_rationale_81", "to": "core_api_request_log_middleware_apirequestlogmiddleware", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_rationale_81", "to": "core_api_crypto_middleware_apicryptomiddleware", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_rationale_92", "to": "core_api_request_log_middleware_apirequestlogmiddleware", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_rationale_92", "to": "core_api_crypto_middleware_apicryptomiddleware", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "tasks_init_py", "to": "tasks_init_init_scheduled_tasks", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tasks_init_py", "to": "tasks_init_shutdown_scheduler", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_redis_session_enabled", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_redis_client", "labeLine truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 638}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 529}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 283}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 208}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 191}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 186}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 133}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 129}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 128}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 122}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 119}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 119}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 110}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 107}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 94}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 92}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 54}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 35}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 35}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 31}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 28}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 21}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 21}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 20}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 17}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 13}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 13}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 13}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 12}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 12}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 12}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 11}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 10}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 8}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 8}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 7}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 4}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 4}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 3}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 2}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 2}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 1}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 1}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 1}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 1}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 1}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 1}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 1}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 1}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 1}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 1}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 1}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 1}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 0}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 0}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 0}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 0}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 0}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 0}];
// HTML-escape helper — prevents XSS when injecting graph data into innerHTML
function esc(s) {
+11753 -9119
View File
File diff suppressed because it is too large. Load diff
+9 -8
View File
@@ -27,7 +27,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/core/wechat_message.py": 1778578258.0458734,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/material.py": 1779283385.245847,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/qc_param.py": 1779022399.0175831,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py": 1781232619.5127413,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py": 1781245309.0810163,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/__init__.py": 1779418058.876012,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/research_donation.py": 1780458012.8329656,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/qc_material.py": 1781193802.4598653,
@@ -61,7 +61,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/category_parameter.py": 1778578258.0944862,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/common.py": 1778578258.094703,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/unit.py": 1778578258.0996897,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py": 1779965437.3667517,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/qc_work_order.py": 1781245309.0806582,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/outbound_release_material.py": 1779827058.9935315,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/product.py": 1778578258.0968297,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/workflow.py": 1778633845.5937889,
@@ -69,8 +69,8 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/outbound_handover.py": 1780455571.0749218,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/category.py": 1778578258.0941947,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/leave.py": 1778666088.7680702,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py": 1781063443.5073216,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py": 1781232662.9280763,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/storage_release_review.py": 1781245325.124061,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task.py": 1781233180.038229,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/process_route.py": 1779587424.6859367,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_code.py": 1780479191.5382729,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_result.py": 1779331731.6720955,
@@ -106,7 +106,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/role.py": 1779265616.3701816,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_material_stock_batch.py": 1781193849.087376,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_sms_notification.py": 1781063498.5542552,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py": 1781232601.6883087,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_task_assignee.py": 1781233180.3766735,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/research_donation_sample_collection.py": 1781063441.5812025,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py": 1779927536.8881981,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/menu.py": 1779265729.0381103,
@@ -122,7 +122,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/env_detect_item.py": 1781145689.9525824,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/process_category.py": 1779265700.0606413,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/item_unit.py": 1779265653.7504313,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py": 1781232652.837524,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_work_order.py": 1781247642.2371693,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/outbound_handover_inventory.py": 1779928420.2494187,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/env_detect_record.py": 1781145773.959546,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/outbound_release_material.py": 1779928413.9677577,
@@ -154,7 +154,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/api/outbound_release_review.py": 1779968232.5759368,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/public.py": 1780540754.892583,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/material.py": 1779526648.041865,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py": 1781232683.4209259,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/qc.py": 1781245328.1318824,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/collection_protocol.py": 1781063459.1075609,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/basic_info.py": 1779882001.7519956,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/pilot_infusion_confirm.py": 1779657043.1389203,
@@ -177,11 +177,12 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/api/sample_sms_notification.py": 1780325524.907156,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/sample_reception.py": 1781190032.4213104,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/insurance_company.py": 1779281097.279193,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py": 1779924318.5910363,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py": 1781245330.194127,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/protocol_sample_collection.py": 1780457788.2093296,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/leave.py": 1779266168.8693042,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/clinical_payment.py": 1781079914.3908625,
"/Users/wuzilong/work/00code/zkxw/backend/app/services/workflow_callback.py": 1778727966.7277644,
"/Users/wuzilong/work/00code/zkxw/backend/app/services/qc_work_order_fee_summary.py": 1781248273.0896494,
"/Users/wuzilong/work/00code/zkxw/backend/app/services/workflow_engine.py": 1778634664.6293883,
"/Users/wuzilong/work/00code/zkxw/backend/app/services/web_auth_roles.py": 1778775532.8878627,
"/Users/wuzilong/work/00code/zkxw/backend/app/services/protocol_payment_installment.py": 1781076733.062029,
@@ -0,0 +1,532 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>中科兴皖 · 一体化流程 + 角色聚焦(兼容原型)</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
:root {
--primary: #10b981; --primary-dark: #059669; --primary-bg: #ecfdf5;
--text: #1e293b; --text-muted: #64748b; --border: #d9e8e2;
--surface: #fff; --table-head: #f8fafc; --row-hover: #f1f5f9;
--radius: 10px; --warn: #d97706; --warn-bg: #fffbeb;
--info: #3b82f6; --info-bg: #eff6ff;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Inter, sans-serif; font-size: 14px; line-height: 1.5; color: var(--text); background: #fefefe; }
.layout { display: flex; min-height: 100vh; }
.sidebar { width: 220px; background: var(--surface); border-right: 1px solid var(--border); padding: 20px 14px; flex-shrink: 0; }
.logo { font-size: 15px; font-weight: 700; padding: 0 10px 16px; border-bottom: 1px solid var(--border); margin-bottom: 12px; }
.logo span { color: var(--primary); }
.nav-item { display: block; padding: 8px 12px; color: var(--text-muted); text-decoration: none; border-radius: 8px; font-size: 13px; margin-bottom: 2px; }
.nav-item.active { background: var(--primary-bg); color: var(--primary-dark); font-weight: 600; }
.nav-sub { font-size: 11px; color: var(--text-muted); padding: 8px 12px 4px; }
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
/* Role bar */
.role-bar {
background: var(--surface); border-bottom: 1px solid var(--border);
padding: 10px 20px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
}
.role-bar-label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
.role-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.role-tab {
padding: 6px 14px; border-radius: 8px; border: 1px solid var(--border);
background: var(--surface); font-size: 13px; cursor: pointer; transition: all 0.15s;
}
.role-tab:hover { border-color: var(--primary); }
.role-tab.active { background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600; }
.role-desc { font-size: 12px; color: var(--text-muted); flex: 1; min-width: 200px; }
.view-toggle { display: flex; gap: 4px; margin-left: auto; }
.view-toggle button {
padding: 5px 10px; font-size: 12px; border: 1px solid var(--border); border-radius: 6px;
background: var(--surface); cursor: pointer;
}
.view-toggle button.active { background: var(--primary-bg); border-color: var(--primary); color: var(--primary-dark); font-weight: 600; }
.topbar { padding: 12px 20px; border-bottom: 1px solid var(--border); background: var(--surface); }
.topbar h1 { font-size: 15px; font-weight: 600; }
.topbar p { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
.content { flex: 1; padding: 16px 20px; overflow: auto; }
.concept-banner {
padding: 10px 14px; background: linear-gradient(90deg, var(--primary-bg), #fff);
border: 1px solid var(--border); border-radius: var(--radius); font-size: 12px; color: var(--text-muted); margin-bottom: 14px;
}
.concept-banner strong { color: var(--primary-dark); }
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 14px; }
.panel-head {
padding: 12px 16px; border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px;
}
.panel-head h2 { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 6px; }
.panel-head h2::before { content: ''; width: 3px; height: 14px; background: var(--primary); border-radius: 2px; }
.stat-row { display: flex; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.stat-pill {
padding: 6px 12px; border-radius: 8px; background: var(--table-head); font-size: 12px;
}
.stat-pill em { font-style: normal; font-weight: 700; color: var(--warn); margin-left: 4px; }
table { width: 100%; border-collapse: collapse; }
th { background: var(--table-head); font-size: 12px; color: var(--text-muted); padding: 9px 12px; text-align: center; border-bottom: 1px solid var(--border); }
td { padding: 11px 12px; text-align: center; border-bottom: 1px solid var(--border); font-size: 13px; }
tr { cursor: pointer; }
tr:hover td { background: var(--row-hover); }
tr.selected td { background: var(--primary-bg); }
.tag { display: inline-block; padding: 2px 7px; border-radius: 5px; font-size: 11px; font-weight: 500; }
.tag.success { background: var(--primary-bg); color: var(--primary-dark); }
.tag.warning { background: var(--warn-bg); color: var(--warn); }
.tag.info { background: var(--info-bg); color: var(--info); }
.tag.muted { background: var(--table-head); color: var(--text-muted); }
.btn {
height: 30px; padding: 0 11px; border-radius: 7px; font-size: 12px; font-weight: 500;
border: 1px solid var(--border); background: var(--surface); cursor: pointer;
}
.btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
/* ── Process Spine (shared component) ── */
.spine-wrap {
border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface);
margin-bottom: 14px; overflow: hidden;
}
.spine-header {
padding: 10px 14px; display: flex; align-items: center; justify-content: space-between;
background: var(--table-head); cursor: pointer; user-select: none;
}
.spine-header h3 { font-size: 13px; font-weight: 600; }
.spine-header .meta { font-size: 12px; color: var(--text-muted); }
.spine-toggle { font-size: 12px; color: var(--primary-dark); font-weight: 500; }
.spine-body { padding: 12px 14px 14px; border-top: 1px solid var(--border); }
.spine-body.collapsed { display: none; }
.spine-compact { display: flex; align-items: center; gap: 0; overflow-x: auto; padding: 4px 0; }
.sp-node {
flex: 0 0 auto; text-align: center; min-width: 72px; position: relative; padding: 0 2px;
}
.sp-node:not(:last-child)::after {
content: ''; position: absolute; top: 11px; left: calc(50% + 18px); width: calc(100% - 36px); height: 2px; background: var(--border);
}
.sp-node.done:not(:last-child)::after { background: var(--primary); }
.sp-dot {
width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--border); margin: 0 auto 4px;
font-size: 10px; display: flex; align-items: center; justify-content: center; background: var(--surface); position: relative; z-index: 1;
}
.sp-node.done .sp-dot { border-color: var(--primary); background: var(--primary-bg); color: var(--primary-dark); }
.sp-node.you .sp-dot { border-color: var(--primary); background: var(--primary); color: #fff; box-shadow: 0 0 0 3px rgba(16,185,129,0.2); }
.sp-node.parallel .sp-dot { border-color: var(--warn); background: var(--warn-bg); color: var(--warn); }
.sp-node.indep .sp-dot { border-style: dashed; }
.sp-name { font-size: 10px; font-weight: 600; line-height: 1.2; }
.sp-node.dim .sp-name, .sp-node.dim .sp-dot { opacity: 0.35; }
.spine-side {
margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border);
display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--text-muted);
}
/* Detail drawer / bottom panel */
.detail-panel { display: none; }
.detail-panel.open { display: block; }
.detail-grid { display: grid; grid-template-columns: 1fr 340px; gap: 14px; }
.work-area { padding: 16px; }
.work-area h3 { font-size: 14px; font-weight: 600; margin-bottom: 12px; }
.form-row { margin-bottom: 12px; }
.form-row label { display: block; font-size: 11px; font-weight: 600; color: var(--text-muted); margin-bottom: 4px; }
.form-row input, .form-row select { width: 100%; max-width: 360px; height: 34px; border: 1px solid var(--border); border-radius: 8px; padding: 0 10px; }
.context-card {
padding: 14px; background: var(--table-head); border-left: 1px solid var(--border);
}
.context-card h4 { font-size: 12px; font-weight: 600; color: var(--text-muted); margin-bottom: 10px; }
.ctx-line { font-size: 12px; margin-bottom: 6px; }
.ctx-line span { color: var(--text-muted); }
/* Full process view (coordinator only) */
.full-process { display: none; }
.full-process.show { display: block; }
.focused-view.hidden { display: none; }
.fork-row { display: flex; gap: 20px; justify-content: center; margin: 8px 0; }
.flow-card {
width: 120px; padding: 10px; border: 2px solid var(--border); border-radius: 8px; text-align: center; font-size: 12px;
}
.flow-card.active { border-color: var(--primary); background: var(--primary-bg); }
.flow-card.done { border-color: var(--primary); opacity: 0.85; }
.toast {
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(60px);
background: var(--text); color: #fff; padding: 10px 18px; border-radius: 8px; font-size: 13px;
opacity: 0; transition: all 0.3s; z-index: 99;
}
.toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }
@media (max-width: 900px) {
.detail-grid { grid-template-columns: 1fr; }
.layout { flex-direction: column; }
.sidebar { width: 100%; }
}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar" id="sidebar"></aside>
<div class="main">
<div class="role-bar">
<span class="role-bar-label">模拟角色(切换看兼容效果)</span>
<div class="role-tabs" id="roleTabs"></div>
<div class="view-toggle" id="viewToggle" style="display:none">
<button type="button" data-view="focused" class="active">聚焦作业</button>
<button type="button" data-view="full">流程全景</button>
</div>
</div>
<p class="role-desc" id="roleDesc" style="padding:8px 20px 0;font-size:12px;color:var(--text-muted)"></p>
<header class="topbar">
<h1 id="pageTitle">—</h1>
<p id="pageSub">—</p>
</header>
<div class="content">
<div class="concept-banner">
<strong>兼容模式:</strong>同一套流程数据 · 角色默认只看<strong>本岗列表+操作区</strong> · 流程通过可折叠<strong>脊柱</strong>保持一体清晰 · 跟单可切换<strong>流程全景</strong>
</div>
<!-- 聚焦视图:各角色主路径 -->
<div class="focused-view" id="focusedView">
<div class="panel">
<div class="panel-head">
<h2 id="listTitle">待办列表</h2>
<span id="listHint" style="font-size:12px;color:var(--text-muted)"></span>
</div>
<div class="stat-row" id="statRow"></div>
<table>
<thead id="tableHead"></thead>
<tbody id="tableBody"></tbody>
</table>
</div>
<!-- 选中行后:脊柱 + 作业区 -->
<div class="detail-panel" id="detailPanel">
<div id="detailSpine"></div>
<div class="panel detail-grid">
<div class="work-area">
<h3 id="workTitle">—</h3>
<div id="workForm"></div>
<div style="margin-top:14px" id="workActions"></div>
</div>
<div class="context-card">
<h4>流程上下文(只读,跨部门共享)</h4>
<div id="contextLines"></div>
</div>
</div>
</div>
</div>
<!-- 全景视图:仅跟单 -->
<div class="full-process" id="fullProcess">
<div class="panel" style="padding:16px">
<h2 style="font-size:14px;margin-bottom:12px">业务办理 · 依赖全景(跟单/coordinator 专用)</h2>
<div id="fullFlowDiagram"></div>
<p style="font-size:12px;color:var(--text-muted);margin-top:12px">全景与职能列表是同一数据;实验员/财务日常使用「聚焦作业」即可。</p>
</div>
</div>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
const ROLES = {
finance: {
label: '财务', menu: ['缴退费确认'], defaultMenu: 0,
desc: '主路径:确认列表。流程脊柱默认折叠,只看金额与协议摘要。',
pageTitle: '缴退费财务确认', pageSub: '仅看待确认单据 · 不关心实验主链操作',
highlightNode: 'payment', dimNodes: ['qc', 'production'],
spineDefaultCollapsed: true,
canFullView: false,
stats: [{ label: '待确认', count: 3 }, { label: '今日已确认', count: 8 }],
columns: ['协议号', '客户', '金额', '类型', '实验进度', '操作'],
list: [
{ id: 1, protocol: 'XY-2025-0066', customer: '张明华', amount: '28,800', type: '缴费确认', milestone: '已接收', nodes: mkNodes({ payment: 'you', reception: 'done', qc: 'parallel', production: 'parallel' }) },
{ id: 2, protocol: 'XY-2025-0077', customer: '李芳', amount: '18,000', type: '缴费确认', milestone: '待采集', nodes: mkNodes({ payment: 'you', collection: 'parallel' }) },
{ id: 3, protocol: 'XY-2025-0044', customer: '陈静', amount: '2,000', type: '退费确认', milestone: '质检中', nodes: mkNodes({ payment: 'you', qc: 'parallel', production: 'parallel' }) }
]
},
qc_recv: {
label: '质检接收', menu: ['样本接收', '接收检查项'], defaultMenu: 0,
desc: '主路径:接收列表。脊柱高亮「接收」,缴费为虚线独立支路。',
pageTitle: '样本接收', pageSub: '待接收 / 已接收 · 提交后并行创建质检+生产工单',
highlightNode: 'reception', dimNodes: ['payment'],
spineDefaultCollapsed: false,
canFullView: false,
stats: [{ label: '待接收', count: 5 }, { label: '今日已接收', count: 12 }],
columns: ['样本编码', '协议号', '客户', '预计到达', '里程碑', '操作'],
list: [
{ id: 1, sample: 'ZK202506120001', protocol: 'XY-2025-0066', customer: '张明华', eta: '今日 14:00', milestone: '待接收', nodes: mkNodes({ reception: 'you', payment: 'indep' }) },
{ id: 2, sample: 'ZK202506110008', protocol: 'XY-2025-0077', customer: '李芳', eta: '明日 10:00', milestone: '待接收', nodes: mkNodes({ reception: 'you', collection: 'done', payment: 'indep' }) }
]
},
qc_lab: {
label: '质检实验', menu: ['我的质检任务', '质检工单'], defaultMenu: 0,
desc: '主路径:我的任务。脊柱高亮「质检」,可看生产并行进度(只读)。',
pageTitle: '我的质检任务', pageSub: '待执行 / 进行中 · 与生产工单并行',
highlightNode: 'qc', dimNodes: ['payment'],
spineDefaultCollapsed: false,
canFullView: false,
stats: [{ label: '待执行', count: 4 }, { label: '进行中', count: 2 }],
columns: ['任务号', '样本编码', '检测项目', '工单状态', '生产(只读)', '操作'],
list: [
{ id: 1, task: 'RW20260612001', sample: 'ZK202506120001', item: '细胞活性', wo: '配置中', prod: '待配置', nodes: mkNodes({ qc: 'you', production: 'parallel', reception: 'done' }) },
{ id: 2, task: 'RW20260612002', sample: 'XY202506100003', item: '无菌检测', wo: '进行中', prod: '进行中', nodes: mkNodes({ qc: 'you', production: 'parallel' }) }
]
},
production: {
label: '生产', menu: ['生产工单', '生产任务'], defaultMenu: 0,
desc: '主路径:生产工单。脊柱高亮「生产」,质检进度只读。',
pageTitle: '入库生产工单', pageSub: '待配置 / 进行中 · 与质检并行',
highlightNode: 'production', dimNodes: ['payment'],
spineDefaultCollapsed: false,
canFullView: false,
stats: [{ label: '待配置', count: 2 }, { label: '进行中', count: 3 }],
columns: ['工单号', '样本编码', '协议号', '状态', '质检(只读)', '操作'],
list: [
{ id: 1, wo: 'SC202606110003', sample: 'ZK202506120001', protocol: 'XY-2025-0066', status: '待配置', qc: '配置中', nodes: mkNodes({ production: 'you', qc: 'parallel', reception: 'done' }) }
]
},
coordinator: {
label: '市场跟单', menu: ['协议列表', '业务办理'], defaultMenu: 1,
desc: '可选聚焦列表或流程全景。日常跟单用列表+脊柱;卡点时切换全景。',
pageTitle: '细胞存储 · 业务办理', pageSub: '进行中协议 · 可切换「聚焦 / 全景」',
highlightNode: null, dimNodes: [],
spineDefaultCollapsed: false,
canFullView: true,
stats: [{ label: '进行中', count: 23 }, { label: '卡点', count: 4 }],
columns: ['协议号', '客户', '样本', '当前里程碑', '并行状态', '操作'],
list: [
{ id: 1, protocol: 'XY-2025-0066', customer: '张明华', sample: 'ZK…001', milestone: '接收完成', parallel: '质检+生产', nodes: mkNodes({ qc: 'parallel', production: 'parallel', reception: 'done', payment: 'indep' }) },
{ id: 2, protocol: 'XY-2025-0088', customer: '王强', sample: 'XY…003', milestone: '质检完成', parallel: '生产进行中', nodes: mkNodes({ qc: 'done', production: 'you', reception: 'done' }) }
]
}
};
const SPINE_STEPS = [
{ id: 'protocol', name: '协议' },
{ id: 'collection', name: '采集' },
{ id: 'reception', name: '接收' },
{ id: 'qc', name: '质检' },
{ id: 'production', name: '生产' },
{ id: 'release', name: '放行' },
{ id: 'storage', name: '入库' }
];
const SPINE_INDEP = { id: 'payment', name: '缴费' };
function mkNodes(overrides) {
const base = { protocol: 'done', collection: 'done', reception: 'done', qc: 'pending', production: 'pending', release: 'pending', storage: 'pending', payment: 'pending' };
return { ...base, ...overrides };
}
let currentRole = 'finance';
let currentView = 'focused';
let selectedRow = null;
let spineCollapsed = true;
function renderRoleTabs() {
document.getElementById('roleTabs').innerHTML = Object.entries(ROLES).map(([k, r]) =>
`<button type="button" class="role-tab ${k === currentRole ? 'active' : ''}" onclick="setRole('${k}')">${r.label}</button>`
).join('');
}
function renderSidebar() {
const r = ROLES[currentRole];
document.getElementById('sidebar').innerHTML = `
<div class="logo">中科兴皖<span> · 管理端</span></div>
<div class="nav-sub">当前角色菜单(聚焦)</div>
${r.menu.map((m, i) => `<a href="#" class="nav-item ${i === r.defaultMenu ? 'active' : ''}">${m}</a>`).join('')}
${r.canFullView ? '<a href="#" class="nav-item" onclick="setView(\'full\');return false">业务办理全景</a>' : ''}
`;
}
function setRole(role) {
currentRole = role;
selectedRow = null;
spineCollapsed = ROLES[role].spineDefaultCollapsed;
currentView = 'focused';
renderAll();
}
function setView(v) {
currentView = v;
document.querySelectorAll('.view-toggle button').forEach(b => b.classList.toggle('active', b.dataset.view === v));
document.getElementById('focusedView').classList.toggle('hidden', v === 'full');
document.getElementById('fullProcess').classList.toggle('show', v === 'full');
}
function renderSpine(row, roleKey, opts = {}) {
const r = ROLES[roleKey];
const nodes = row.nodes || mkNodes({});
const collapsed = opts.forceExpand ? false : spineCollapsed;
const hl = r.highlightNode;
const mainSteps = SPINE_STEPS.map(s => {
const st = nodes[s.id] || 'pending';
let cls = st === 'done' ? 'done' : st === 'you' ? 'you' : st === 'parallel' ? 'parallel' : '';
if (r.dimNodes && r.dimNodes.includes(s.id)) cls += ' dim';
if (hl === s.id && st !== 'you') cls = 'you';
const icon = st === 'done' ? '✓' : st === 'you' ? '●' : st === 'parallel' ? '…' : '○';
return `<div class="sp-node ${cls}"><div class="sp-dot">${icon}</div><div class="sp-name">${s.name}</div></div>`;
}).join('');
const paySt = nodes.payment || 'pending';
const payCls = paySt === 'indep' ? 'indep' : paySt === 'you' ? 'you' : paySt === 'done' ? 'done' : '';
const indep = `<div class="sp-node ${payCls}"><div class="sp-dot">${paySt === 'done' ? '✓' : paySt === 'you' ? '●' : '○'}</div><div class="sp-name">缴费</div></div>`;
const title = row.protocol || row.sample || '—';
const sub = row.customer ? `${row.customer} · ${row.milestone || row.type || ''}` : '';
return `
<div class="spine-wrap">
<div class="spine-header" onclick="toggleSpine()">
<div>
<h3>流程脊柱 · ${title}</h3>
<div class="meta">${sub} ${hl ? '· 高亮你的环节' : '· 跟单视图'}</div>
</div>
<span class="spine-toggle">${collapsed ? '展开流程 ▼' : '收起 ▲'}</span>
</div>
<div class="spine-body ${collapsed ? 'collapsed' : ''}">
<div class="spine-compact">${mainSteps}</div>
<div class="spine-side"><span>独立支路:</span>${indep}<span>— 不阻塞实验主链</span></div>
</div>
</div>`;
}
function toggleSpine() {
spineCollapsed = !spineCollapsed;
if (selectedRow) document.getElementById('detailSpine').innerHTML = renderSpine(selectedRow, currentRole);
}
function renderList(roleKey) {
const r = ROLES[roleKey];
document.getElementById('pageTitle').textContent = r.pageTitle;
document.getElementById('pageSub').textContent = r.pageSub;
document.getElementById('roleDesc').textContent = r.desc;
document.getElementById('listTitle').textContent = r.pageTitle;
document.getElementById('listHint').textContent = r.canFullView ? '可右上角切换流程全景' : '本岗聚焦列表';
document.getElementById('viewToggle').style.display = r.canFullView ? 'flex' : 'none';
document.getElementById('statRow').innerHTML = r.stats.map(s =>
`<div class="stat-pill">${s.label}<em>${s.count}</em></div>`
).join('');
document.getElementById('tableHead').innerHTML = `<tr>${r.columns.map(c => `<th>${c}</th>`).join('')}</tr>`;
document.getElementById('tableBody').innerHTML = r.list.map((row, i) => {
let cells = '';
if (roleKey === 'finance') {
cells = `<td>${row.protocol}</td><td>${row.customer}</td><td>${row.amount}</td><td>${row.type}</td><td><span class="tag info">${row.milestone}</span></td><td><button class="btn btn-primary btn-sm" onclick="event.stopPropagation();selectRow(${i})">确认</button></td>`;
} else if (roleKey === 'qc_recv') {
cells = `<td>${row.sample}</td><td>${row.protocol}</td><td>${row.customer}</td><td>${row.eta}</td><td><span class="tag warning">${row.milestone}</span></td><td><button class="btn btn-primary" onclick="event.stopPropagation();selectRow(${i})">接收</button></td>`;
} else if (roleKey === 'qc_lab') {
cells = `<td>${row.task}</td><td>${row.sample}</td><td>${row.item}</td><td>${row.wo}</td><td><span class="tag muted">${row.prod}</span></td><td><button class="btn btn-primary" onclick="event.stopPropagation();selectRow(${i})">执行</button></td>`;
} else if (roleKey === 'production') {
cells = `<td>${row.wo}</td><td>${row.sample}</td><td>${row.protocol}</td><td>${row.status}</td><td><span class="tag muted">${row.qc}</span></td><td><button class="btn btn-primary" onclick="event.stopPropagation();selectRow(${i})">配置</button></td>`;
} else {
cells = `<td>${row.protocol}</td><td>${row.customer}</td><td>${row.sample}</td><td>${row.milestone}</td><td>${row.parallel}</td><td><button class="btn" onclick="event.stopPropagation();selectRow(${i})">跟单</button></td>`;
}
return `<tr class="${selectedRow === row ? 'selected' : ''}" onclick="selectRow(${i})">${cells}</tr>`;
}).join('');
}
function selectRow(index) {
const r = ROLES[currentRole];
selectedRow = r.list[index];
document.getElementById('detailPanel').classList.add('open');
document.getElementById('detailSpine').innerHTML = renderSpine(selectedRow, currentRole);
const forms = {
finance: `
<div class="form-row"><label>到账金额</label><input value="${selectedRow.amount}" /></div>
<div class="form-row"><label>到账日期</label><input type="date" value="2026-06-12" /></div>
<div class="form-row"><label>财务备注</label><input placeholder="可选" /></div>`,
qc_recv: `
<div class="form-row"><label>接收时间</label><input type="datetime-local" /></div>
<div class="form-row"><label>接收温度 ℃</label><input value="4.2" /></div>
<div class="form-row"><label>接收结论</label><select><option>接收</option><option>拒收</option></select></div>`,
qc_lab: `
<div class="form-row"><label>检测项目</label><input value="${selectedRow.item}" readonly /></div>
<div class="form-row"><label>采集值 / 结果</label><input placeholder="实验录入" /></div>`,
production: `
<div class="form-row"><label>工艺路线</label><select><option>入库标准工艺 v2</option></select></div>
<div class="form-row"><label>生产任务</label><input placeholder="创建任务…" /></div>`,
coordinator: `
<div class="form-row"><label>跟单备注</label><input placeholder="记录协调事项" /></div>
<p style="font-size:12px;color:var(--text-muted)">跟单在此查看脊柱;需要时可切「流程全景」。</p>`
};
const titles = { finance: '财务确认', qc_recv: '样本接收登记', qc_lab: '质检任务执行', production: '生产工单配置', coordinator: '跟单备注' };
document.getElementById('workTitle').textContent = titles[currentRole];
document.getElementById('workForm').innerHTML = forms[currentRole];
document.getElementById('workActions').innerHTML = `<button class="btn btn-primary" onclick="toast('已提交 · 流程状态已同步更新')">提交</button><button class="btn" onclick="closeDetail()">关闭</button>`;
document.getElementById('contextLines').innerHTML = `
<div class="ctx-line"><span>协议:</span>${selectedRow.protocol || '—'}</div>
<div class="ctx-line"><span>客户:</span>${selectedRow.customer || '—'}</div>
<div class="ctx-line"><span>样本:</span>${selectedRow.sample || '—'}</div>
<div class="ctx-line"><span>实验里程碑:</span>${selectedRow.milestone || selectedRow.type || '—'}</div>
<div class="ctx-line" style="margin-top:10px;font-size:11px;color:var(--text-muted)">↑ 其他部门可见同一上下文,无需进同一操作页</div>`;
renderList(currentRole);
}
function closeDetail() {
selectedRow = null;
document.getElementById('detailPanel').classList.remove('open');
renderList(currentRole);
}
function renderFullProcess() {
document.getElementById('fullFlowDiagram').innerHTML = `
<div style="font-size:12px;color:var(--text-muted);margin-bottom:10px">协议 → 采集 → 接收 → (质检 ∥ 生产) → 放行 → 入库 · 缴费独立</div>
<div class="fork-row">
<div class="flow-card done">协议✓</div><span>→</span>
<div class="flow-card done">采集✓</div><span>→</span>
<div class="flow-card done">接收✓</div><span>→</span>
<div class="flow-card active">质检…</div>
<div class="flow-card active">生产…</div><span>→</span>
<div class="flow-card">放行</div><span>→</span>
<div class="flow-card">入库</div>
</div>
<div style="margin-top:12px;padding:10px;border:1px dashed var(--border);border-radius:8px;font-size:12px">缴费 ○ 独立支路(虚线关联,不阻塞主链)</div>`;
}
function renderAll() {
renderRoleTabs();
renderSidebar();
renderList(currentRole);
setView(currentView);
renderFullProcess();
if (selectedRow) document.getElementById('detailSpine').innerHTML = renderSpine(selectedRow, currentRole);
}
document.getElementById('viewToggle').addEventListener('click', e => {
const btn = e.target.closest('button[data-view]');
if (btn) setView(btn.dataset.view);
});
function toast(msg) {
const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2500);
}
renderAll();
</script>
</body>
</html>
@@ -0,0 +1,511 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>中科兴皖 · 业务办理(并行流程原型 v2)</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
:root {
--primary: #10b981; --primary-dark: #059669; --primary-bg: #ecfdf5;
--text: #1e293b; --text-muted: #64748b; --border: #d9e8e2;
--bg-page: #fefefe; --surface: #fff; --table-head: #f8fafc;
--row-hover: #f1f5f9; --shadow: 0 1px 3px rgba(0,0,0,0.05);
--radius: 10px; --warn: #d97706; --warn-bg: #fffbeb;
--info: #3b82f6; --info-bg: #eff6ff;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Inter, sans-serif; font-size: 14px; line-height: 1.5; color: var(--text); background: var(--bg-page); }
.layout { display: flex; min-height: 100vh; }
.sidebar { width: 220px; background: var(--surface); border-right: 1px solid var(--border); padding: 24px 16px; flex-shrink: 0; }
.logo { font-size: 16px; font-weight: 700; padding: 0 12px 20px; border-bottom: 1px solid var(--border); margin-bottom: 12px; }
.logo span { color: var(--primary); }
.nav-item { display: block; padding: 8px 12px; color: var(--text-muted); text-decoration: none; border-radius: 8px; font-size: 13px; }
.nav-item.active { background: var(--primary-bg); color: var(--primary-dark); font-weight: 600; }
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.topbar { height: 56px; padding: 0 24px; display: flex; align-items: center; justify-content: space-between; background: var(--surface); border-bottom: 1px solid var(--border); }
.topbar h1 { font-size: 15px; font-weight: 600; }
.topbar-meta { font-size: 12px; color: var(--text-muted); }
.page { flex: 1; display: flex; min-height: 0; }
.case-list { width: 300px; border-right: 1px solid var(--border); background: var(--surface); display: flex; flex-direction: column; }
.case-list-head { padding: 14px 16px; border-bottom: 1px solid var(--border); }
.case-list-head h2 { font-size: 13px; font-weight: 600; margin-bottom: 8px; }
.search-input { width: 100%; height: 34px; border: 1px solid var(--border); border-radius: 8px; padding: 0 10px; font-size: 12px; }
.case-items { flex: 1; overflow-y: auto; }
.case-card { padding: 12px 16px; border-bottom: 1px solid var(--border); cursor: pointer; }
.case-card:hover { background: var(--row-hover); }
.case-card.active { background: var(--primary-bg); border-left: 3px solid var(--primary); padding-left: 13px; }
.case-protocol { font-weight: 600; font-size: 13px; }
.case-customer { font-size: 12px; color: var(--text-muted); margin: 4px 0 8px; }
.active-tags { display: flex; flex-wrap: wrap; gap: 4px; }
.tag { font-size: 11px; padding: 1px 6px; border-radius: 5px; font-weight: 500; }
.tag.success { background: var(--primary-bg); color: var(--primary-dark); }
.tag.warning { background: var(--warn-bg); color: var(--warn); }
.tag.info { background: var(--info-bg); color: var(--info); }
.tag.muted { background: var(--table-head); color: var(--text-muted); }
.console { flex: 1; overflow-y: auto; padding: 12px 16px 16px; }
.proto-tip {
padding: 10px 14px; background: linear-gradient(90deg, var(--primary-bg), #fff);
border: 1px solid var(--border); border-radius: var(--radius); font-size: 12px; color: var(--text-muted); margin-bottom: 12px;
}
.proto-tip strong { color: var(--primary-dark); }
.legend { display: flex; flex-wrap: wrap; gap: 12px 20px; font-size: 11px; color: var(--text-muted); margin-bottom: 12px; }
.legend span { display: flex; align-items: center; gap: 5px; }
.legend i { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.legend .solid { background: var(--primary); }
.legend .dash { border: 2px dashed var(--text-muted); background: transparent; height: 0; width: 16px; }
.legend .fork { background: var(--warn); }
.summary-bar {
display: flex; flex-wrap: wrap; gap: 16px 28px; padding: 12px 16px;
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 12px;
}
.summary-item dt { font-size: 11px; color: var(--text-muted); }
.summary-item dd { font-size: 14px; font-weight: 600; }
/* ── Flow graph (DAG, not linear) ── */
.flow-panel {
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
padding: 16px; margin-bottom: 12px; overflow-x: auto;
}
.flow-panel h3 { font-size: 12px; font-weight: 600; color: var(--text-muted); margin-bottom: 14px; }
.flow-main { display: flex; align-items: center; gap: 0; min-width: max-content; padding-bottom: 8px; }
.flow-arrow { color: var(--text-muted); font-size: 18px; padding: 0 4px; flex-shrink: 0; user-select: none; }
.flow-node {
width: 88px; text-align: center; cursor: pointer; flex-shrink: 0;
padding: 4px; border-radius: 8px; transition: background 0.12s;
}
.flow-node:hover { background: var(--row-hover); }
.flow-node.selected { background: var(--primary-bg); outline: 2px solid var(--primary); outline-offset: 1px; }
.flow-node.independent { opacity: 0.95; }
.node-box {
width: 72px; height: 52px; margin: 0 auto 4px; border-radius: 8px; border: 2px solid var(--border);
background: var(--surface); display: flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 600; line-height: 1.2; padding: 4px; position: relative;
}
.flow-node.done .node-box { border-color: var(--primary); background: var(--primary-bg); color: var(--primary-dark); }
.flow-node.active .node-box { border-color: var(--primary); background: var(--primary); color: #fff; box-shadow: 0 0 0 3px rgba(16,185,129,0.25); }
.flow-node.ready .node-box { border-color: var(--warn); background: var(--warn-bg); color: var(--warn); }
.flow-node.locked .node-box { opacity: 0.45; cursor: not-allowed; }
.flow-node.independent .node-box { border-style: dashed; }
.node-dept { font-size: 9px; color: var(--text-muted); }
.node-status { font-size: 10px; margin-top: 2px; }
.flow-node.done .node-status { color: var(--primary-dark); }
.flow-node.active .node-status, .flow-node.ready .node-status { color: var(--warn); font-weight: 600; }
/* Fork: reception → qc + production */
.flow-fork { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; }
.fork-bar { width: 2px; height: 12px; background: var(--border); }
.fork-row { display: flex; gap: 24px; align-items: center; position: relative; }
.fork-row::before {
content: ''; position: absolute; top: 26px; left: 44px; right: 44px; height: 2px; background: var(--border);
}
.fork-label {
font-size: 10px; color: var(--warn); font-weight: 600; background: var(--warn-bg);
padding: 2px 8px; border-radius: 4px; margin-bottom: 4px;
}
.join-wrap { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; }
.join-label { font-size: 10px; color: var(--text-muted); margin: 4px 0; }
/* Independent track: payment */
.flow-side {
margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--border);
display: flex; align-items: center; gap: 8px; min-width: max-content;
}
.side-label { font-size: 11px; color: var(--text-muted); white-space: nowrap; }
.workspace {
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
min-height: 320px; display: flex; flex-direction: column;
}
.workspace-head {
padding: 12px 16px; border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px;
}
.workspace-head h3 { font-size: 15px; font-weight: 600; }
.node-badge { font-size: 11px; background: var(--primary-bg); color: var(--primary-dark); padding: 2px 8px; border-radius: 6px; font-weight: 500; }
.dep-hint { font-size: 12px; color: var(--text-muted); padding: 8px 16px; background: var(--table-head); border-bottom: 1px solid var(--border); }
.dep-hint strong { color: var(--primary-dark); }
.workspace-body { padding: 16px; flex: 1; }
.workspace-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.btn {
height: 32px; padding: 0 12px; border-radius: 8px; font-size: 13px; font-weight: 500;
border: 1px solid var(--border); background: var(--surface); cursor: pointer;
}
.btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
.btn-sm { height: 28px; padding: 0 10px; font-size: 12px; }
.readonly-panel { background: var(--table-head); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; margin-bottom: 12px; }
.readonly-panel h4 { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; }
.kv-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.kv-row dt { font-size: 11px; color: var(--text-muted); }
.kv-row dd { font-size: 13px; font-weight: 500; }
.parallel-note {
margin-top: 12px; padding: 10px 12px; background: var(--info-bg); border: 1px solid rgba(59,130,246,0.2);
border-radius: 8px; font-size: 12px; color: #1d4ed8;
}
.locked-msg { text-align: center; padding: 40px 20px; color: var(--text-muted); }
.toast {
position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(80px);
background: var(--text); color: #fff; padding: 12px 20px; border-radius: var(--radius);
font-size: 13px; opacity: 0; transition: all 0.3s; z-index: 99; max-width: 92vw; text-align: center;
}
.toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }
@media (max-width: 960px) {
.page { flex-direction: column; }
.case-list { width: 100%; max-height: 200px; }
.kv-row { grid-template-columns: 1fr 1fr; }
}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar">
<div class="logo">中科兴皖<span> · 管理端</span></div>
<a href="#" class="nav-item active">细胞存储 · 业务办理</a>
</aside>
<div class="main">
<header class="topbar">
<h1>细胞存储 · 业务办理</h1>
<span class="topbar-meta">v2 · 并行/独立依赖,非简单串行</span>
</header>
<div class="page">
<aside class="case-list">
<div class="case-list-head">
<h2>进行中业务</h2>
<input class="search-input" id="caseSearch" placeholder="协议号 / 客户 / 样本" />
</div>
<div class="case-items" id="caseList"></div>
</aside>
<section class="console">
<div class="proto-tip">
<strong>v2 修正:</strong>流程不是 8 步串行。缴费<strong>不阻塞</strong>接收/质检/生产;接收后<strong>质检与生产并行</strong>;放行需<strong>两者都完成</strong>(汇合门禁)。点击节点查看下方工作区。
</div>
<div class="legend">
<span><i class="solid"></i> 主链 / 已完成</span>
<span><i class="fork"></i> 并行进行中</span>
<span><i class="dash"></i> 独立支路(不阻塞实验链)</span>
<span>汇合 ⬦ = 需全部前置完成才解锁</span>
</div>
<div class="summary-bar" id="summaryBar"></div>
<div class="flow-panel">
<h3>里程碑依赖图(每个节点独立状态,只按 dependsOn 解锁)</h3>
<div id="flowGraph"></div>
</div>
<div class="workspace">
<div class="workspace-head">
<h3><span id="nodeTitle">—</span> <span class="node-badge" id="nodeBadge">—</span></h3>
<div class="workspace-actions" id="nodeActions"></div>
</div>
<div class="dep-hint" id="depHint"></div>
<div class="workspace-body" id="nodeBody"></div>
</div>
</section>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
/**
* 节点定义:dependsOn = 硬依赖(全部完成才解锁)
* independent = true → 不参与阻塞其他节点(如缴费)
* parallelGroup = 仅 UI 分组,逻辑上各自独立状态
*/
const NODES = {
protocol: { name: '协议单', dept: '市场', dependsOn: [], embed: 'ProtocolAuditTab' },
collection: { name: '样本采集', dept: '采集岗', dependsOn: ['protocol'], embed: 'SampleCollectionInfoTab' },
reception: { name: '样本接收', dept: '质检接收', dependsOn: ['collection'], embed: 'sample-reception' },
qc: { name: '质检工单', dept: '质检部', dependsOn: ['reception'], parallelGroup: 'lab', embed: 'qc-work-order' },
production: { name: '生产工单', dept: '生产部', dependsOn: ['reception'], parallelGroup: 'lab', embed: 'production-work-order' },
release: { name: '入库放行', dept: 'QA', dependsOn: ['qc', 'production'], joinGate: true, embed: 'storage-release-review' },
storage: { name: '样本入库', dept: '库管', dependsOn: ['release'], embed: 'StorageEntryInfoTab' },
payment: { name: '缴费', dept: '市场+财务', dependsOn: [], independent: true, embed: 'PaymentInfoTab' }
};
const STATUS_LABEL = {
locked: '未解锁', ready: '可办理', active: '进行中', done: '已完成', skipped: '—'
};
const CASES = [
{
id: 'c1', protocol: 'XY-2025-0066', customer: '张明华', sample: 'ZK202506120001', type: '脐带血',
// 已接收:质检、生产并行进行中;缴费仍待办但不阻塞
nodes: {
protocol: 'done', collection: 'done', reception: 'done',
qc: 'active', production: 'ready',
release: 'locked', storage: 'locked', payment: 'ready'
}
},
{
id: 'c2', protocol: 'XY-2025-0077', customer: '李芳', sample: '—', type: '胎盘',
nodes: {
protocol: 'done', collection: 'ready', reception: 'locked',
qc: 'locked', production: 'locked', release: 'locked', storage: 'locked',
payment: 'active' // 财务在催缴费,但不影响后续实验链解锁逻辑
}
},
{
id: 'c3', protocol: 'XY-2025-0088', customer: '王强', sample: 'XY202506100003', type: '脐带血',
nodes: {
protocol: 'done', collection: 'done', reception: 'done',
qc: 'done', production: 'active', // 质检已完成,生产还在做
release: 'locked', storage: 'locked', payment: 'done'
}
},
{
id: 'c4', protocol: 'XY-2025-0055', customer: '赵敏', sample: 'ZK202506090005', type: '脐带血',
nodes: {
protocol: 'done', collection: 'done', reception: 'done',
qc: 'done', production: 'done', release: 'ready', storage: 'locked',
payment: 'done'
}
}
];
let activeCaseId = 'c1';
let selectedNode = 'qc';
function getCase(id) { return CASES.find(c => c.id === id) || CASES[0]; }
/** 计算节点是否解锁(不含 independent 对别人的影响) */
function isUnlocked(caseData, nodeId) {
const node = NODES[nodeId];
return (node.dependsOn || []).every(dep => caseData.nodes[dep] === 'done');
}
function getNodeUiStatus(caseData, nodeId) {
const raw = caseData.nodes[nodeId];
if (raw === 'done') return 'done';
if (!isUnlocked(caseData, nodeId)) return 'locked';
if (raw === 'active') return 'active';
return 'ready'; // ready = 可办理
}
function getActiveTags(caseData) {
return Object.entries(caseData.nodes)
.filter(([, st]) => st === 'active' || st === 'ready')
.filter(([id]) => getNodeUiStatus(caseData, id) !== 'locked')
.map(([id]) => NODES[id].name);
}
function renderCaseList() {
const q = (document.getElementById('caseSearch').value || '').toLowerCase();
document.getElementById('caseList').innerHTML = CASES.filter(c =>
!q || [c.protocol, c.customer, c.sample].join(' ').toLowerCase().includes(q)
).map(c => {
const tags = getActiveTags(c);
return `
<div class="case-card ${c.id === activeCaseId ? 'active' : ''}" onclick="selectCase('${c.id}')">
<div class="case-protocol">${c.protocol}</div>
<div class="case-customer">${c.customer} · ${c.type}</div>
<div class="active-tags">
${tags.length ? tags.map(t => `<span class="tag warning">${t}</span>`).join('') : '<span class="tag muted">暂无待办节点</span>'}
${c.nodes.payment !== 'done' ? '<span class="tag info">缴费未完成</span>' : ''}
</div>
</div>`;
}).join('');
}
function renderSummary(c) {
const tags = getActiveTags(c);
document.getElementById('summaryBar').innerHTML = `
<dl class="summary-item"><dt>协议号</dt><dd>${c.protocol}</dd></dl>
<dl class="summary-item"><dt>客户</dt><dd>${c.customer}</dd></dl>
<dl class="summary-item"><dt>样本</dt><dd>${c.sample}</dd></dl>
<dl class="summary-item"><dt>并行进行中</dt><dd style="color:var(--warn)">${tags.join('、') || '—'}</dd></dl>
`;
}
function nodeHtml(id, caseData) {
const n = NODES[id];
const ui = getNodeUiStatus(caseData, id);
const sel = id === selectedNode ? ' selected' : '';
const indep = n.independent ? ' independent' : '';
const st = caseData.nodes[id];
const statusText = ui === 'locked' ? '未解锁' : STATUS_LABEL[st] || STATUS_LABEL[ui];
return `
<div class="flow-node ${ui}${indep}${sel}" onclick="selectNode('${id}')" title="${n.dept}">
<div class="node-box">${n.name}</div>
<div class="node-dept">${n.dept}</div>
<div class="node-status">${statusText}</div>
</div>`;
}
function renderFlowGraph(c) {
document.getElementById('flowGraph').innerHTML = `
<div class="flow-main">
${nodeHtml('protocol', c)}
<span class="flow-arrow">→</span>
${nodeHtml('collection', c)}
<span class="flow-arrow">→</span>
${nodeHtml('reception', c)}
<span class="flow-arrow">→</span>
<div class="flow-fork">
<span class="fork-label">接收后并行创建</span>
<div class="fork-row">
${nodeHtml('qc', c)}
${nodeHtml('production', c)}
</div>
</div>
<span class="flow-arrow">→</span>
<div class="join-wrap">
<span class="join-label">⬦ 质检+生产均完成</span>
${nodeHtml('release', c)}
</div>
<span class="flow-arrow">→</span>
${nodeHtml('storage', c)}
</div>
<div class="flow-side">
<span class="side-label">独立支路(不阻塞实验主链):</span>
${nodeHtml('payment', c)}
<span style="font-size:11px;color:var(--text-muted)">— 未缴费仍可接收、质检、生产;储存证/合同等另按业务规则控制</span>
</div>
`;
}
function getDepHint(id, c) {
const n = NODES[id];
if (n.independent) return '独立节点:与实验主链无阻塞关系,可随时办理。';
if (!isUnlocked(c, id)) {
const pending = (n.dependsOn || []).filter(d => c.nodes[d] !== 'done');
return `未解锁:需先完成 ${pending.map(d => '「' + NODES[d].name + '」').join(n.joinGate ? ' 且 ' : '、')}`;
}
if (n.parallelGroup === 'lab') return '并行节点:与「' + (id === 'qc' ? '生产工单' : '质检工单') + '」同时进行,互不等对方完成。';
if (n.joinGate) return '汇合门禁:质检工单与生产工单均完成后,方可审核放行。';
return '已解锁,可按权限办理。';
}
function renderNode(c, id) {
const n = NODES[id];
const ui = getNodeUiStatus(c, id);
document.getElementById('nodeTitle').textContent = n.name;
document.getElementById('nodeBadge').textContent = n.dept + ' · ' + n.embed;
document.getElementById('depHint').innerHTML = getDepHint(id, c);
if (ui === 'locked') {
document.getElementById('nodeActions').innerHTML = '';
document.getElementById('nodeBody').innerHTML = `
<div class="locked-msg">
<p><strong>🔒 此节点尚未解锁</strong></p>
<p style="margin-top:8px;font-size:13px">${getDepHint(id, c)}</p>
</div>`;
return;
}
const st = c.nodes[id];
const isActive = st === 'active' || st === 'ready';
document.getElementById('nodeActions').innerHTML = getActions(id, isActive);
document.getElementById('nodeBody').innerHTML = getBody(c, id, isActive);
}
function getActions(id, canEdit) {
const btns = {
protocol: canEdit ? '<button class="btn btn-primary btn-sm" onclick="completeNode(\'protocol\')">提交审核</button>' : '<button class="btn btn-sm">查看</button>',
collection: canEdit ? '<button class="btn btn-primary btn-sm" onclick="completeNode(\'collection\')">确认采集</button>' : '<button class="btn btn-sm">查看</button>',
reception: canEdit ? '<button class="btn btn-primary btn-sm" onclick="completeReception()">提交接收</button>' : '<button class="btn btn-sm">查看</button>',
qc: canEdit ? '<button class="btn btn-primary btn-sm" onclick="toast(\'配置检测\')">配置检测</button><button class="btn btn-sm" onclick="completeNode(\'qc\')">标记完成</button>' : '<button class="btn btn-sm">查看报告</button>',
production: canEdit ? '<button class="btn btn-primary btn-sm" onclick="toast(\'创建生产任务\')">创建任务</button><button class="btn btn-sm" onclick="completeNode(\'production\')">标记完成</button>' : '<button class="btn btn-sm">查看</button>',
release: canEdit ? '<button class="btn btn-primary btn-sm" onclick="completeNode(\'release\')">审核通过</button>' : '<button class="btn btn-sm">查看</button>',
storage: canEdit ? '<button class="btn btn-primary btn-sm" onclick="completeNode(\'storage\')">确认入库</button>' : '<button class="btn btn-sm">查看</button>',
payment: canEdit ? '<button class="btn btn-primary btn-sm" onclick="completeNode(\'payment\')">提交/确认缴费</button>' : '<button class="btn btn-sm">查看</button>'
};
return btns[id] || '';
}
function getBody(c, id, canEdit) {
if (id === 'reception' && canEdit) {
return `<div class="readonly-panel"><h4>接收登记(不依赖缴费状态)</h4>
<p style="font-size:13px;color:var(--text-muted)">即使缴费未完成,只要采集完成即可接收。提交后<strong>同时</strong>创建质检工单 + 生产工单。</p></div>
<div class="parallel-note">⚡ 提交接收 → 后端同一事务创建 ZJ 质检工单 与 SC 生产工单(与现有实现一致)</div>`;
}
if (id === 'qc' || id === 'production') {
const other = id === 'qc' ? 'production' : 'qc';
const otherSt = c.nodes[other];
return `<div class="readonly-panel"><h4>${NODES[id].name}</h4>
<dl class="kv-row">
<div><dt>本节点</dt><dd>${STATUS_LABEL[c.nodes[id]]}</dd></div>
<div><dt>并行节点「${NODES[other].name}」</dt><dd>${STATUS_LABEL[otherSt]}(不阻塞本节点)</dd></div>
</dl></div>
<div class="parallel-note">两路可不同人、不同时间推进;放行环节才要求两者都 done。</div>`;
}
if (id === 'release') {
return `<div class="readonly-panel"><h4>汇合门禁</h4>
<dl class="kv-row">
<div><dt>质检工单</dt><dd>${c.nodes.qc === 'done' ? '✓ 已完成' : '✗ 未完成'}</dd></div>
<div><dt>生产工单</dt><dd>${c.nodes.production === 'done' ? '✓ 已完成' : '✗ 未完成'}</dd></div>
<div><dt>缴费</dt><dd>${c.nodes.payment === 'done' ? '已缴费' : '未缴费(不阻塞放行)'}</dd></div>
</dl></div>`;
}
if (id === 'payment') {
return `<div class="readonly-panel"><h4>独立支路 · 缴费</h4>
<p style="font-size:13px">与接收/质检/生产<strong>无 dependsOn 关系</strong>。列表上显示「缴费未完成」提醒,但不锁实验主链。</p>
<p style="font-size:12px;color:var(--text-muted);margin-top:8px">若储存证开具等业务需要已缴费,在<strong>储存证节点</strong>单独加 softDependsOn: ['payment'] 即可。</p></div>`;
}
return `<p style="color:var(--text-muted);font-size:13px">嵌入 ${NODES[id].embed} · ${canEdit ? '可编辑' : '只读'}</p>`;
}
function selectCase(id) { activeCaseId = id; const c = getCase(id); selectedNode = getActiveTags(c)[0] ? Object.keys(NODES).find(k => NODES[k].name === getActiveTags(c)[0]) : 'protocol'; renderAll(); }
function selectNode(id) {
const c = getCase(activeCaseId);
if (getNodeUiStatus(c, id) === 'locked') { toast(getDepHint(id, c)); return; }
selectedNode = id; renderAll();
}
function completeReception() {
const c = getCase(activeCaseId);
c.nodes.reception = 'done';
c.nodes.qc = 'active';
c.nodes.production = 'ready';
if (c.sample === '—') c.sample = 'ZK' + Date.now().toString().slice(-6);
toast('接收完成 → 质检工单、生产工单已并行创建');
selectedNode = 'qc';
renderAll();
}
function completeNode(id) {
const c = getCase(activeCaseId);
c.nodes[id] = 'done';
// 解锁:把 dependsOn 含 id 且全部 deps done 的 locked 节点改为 ready
Object.keys(NODES).forEach(nid => {
if (c.nodes[nid] === 'locked' || c.nodes[nid] === 'ready' || c.nodes[nid] === 'active') {
if (isUnlocked(c, nid) && c.nodes[nid] !== 'done' && c.nodes[nid] !== 'active') {
c.nodes[nid] = 'ready';
}
}
});
if (id === 'qc' || id === 'production') {
if (c.nodes.qc === 'done' && c.nodes.production === 'done') {
c.nodes.release = 'ready';
toast('质检+生产均完成 → 入库放行已解锁');
} else {
toast('「' + NODES[id].name + '」已完成,并行另一路仍可继续');
}
} else {
toast('「' + NODES[id].name + '」已完成');
}
renderAll();
}
function renderAll() {
const c = getCase(activeCaseId);
renderCaseList(); renderSummary(c); renderFlowGraph(c); renderNode(c, selectedNode);
}
function toast(msg) {
const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2800);
}
document.getElementById('caseSearch').addEventListener('input', renderCaseList);
renderAll();
</script>
</body>
</html>
@@ -0,0 +1,689 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>质检工单 · 单列 Tab 模式 Demo</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
<style>
:root {
--primary: #10b981; --primary-dark: #059669; --primary-bg: #ecfdf5;
--text: #1e293b; --muted: #64748b; --border: #e2e8f0; --border-soft: #d9e8e2;
--radius: 10px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Inter, -apple-system, sans-serif;
font-size: 14px; line-height: 1.5; color: var(--text);
background: #fefefe;
}
.page-container { max-width: 1200px; margin: 0 auto; padding: 24px; }
.panel {
background: #fff; border: 1px solid var(--border-soft);
border-radius: var(--radius); overflow: hidden;
}
.panel-top { padding: 16px 20px 0; }
.title-bar-heading {
font-size: 15px; font-weight: 600; padding-left: 10px;
border-left: 3px solid var(--primary); margin-bottom: 12px;
}
.work-order-type-tabs {
display: flex; border-bottom: 1px solid var(--border); padding: 0 20px;
}
.wo-type-tab {
padding: 10px 16px; font-size: 13px; color: var(--muted);
border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: default;
}
.wo-type-tab.active { color: var(--primary-dark); border-bottom-color: var(--primary); font-weight: 600; }
.demo-banner {
margin: 12px 20px 0; padding: 10px 12px;
background: var(--primary-bg); border-radius: 8px;
font-size: 12px; color: var(--muted); line-height: 1.55;
}
.demo-banner strong { color: var(--primary-dark); }
.filter-block { padding: 16px 20px; border-bottom: 1px solid var(--border); }
.filter-grid {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px 16px;
}
.filter-item label { display: block; font-size: 11px; color: var(--muted); margin-bottom: 4px; }
.filter-item input, .filter-item select {
width: 100%; height: 36px; border: 1px solid var(--border);
border-radius: 8px; padding: 0 10px; font-size: 13px; background: #fff;
}
.filter-actions {
display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px;
}
/* ★ 单列布局(替代现网 minmax(480px) 多列网格) */
.qc-work-order-cards-body { border-top: 1px solid var(--border); }
.qc-work-order-cards-wrap {
display: flex;
flex-direction: column;
gap: 14px;
padding: 16px 20px;
min-height: 120px;
}
/* 样本组卡片(一行一个) */
.qc-sample-row {
border: 1px solid var(--border);
border-radius: 10px;
background: #fff;
overflow: hidden;
transition: border-color 0.15s, box-shadow 0.15s;
}
.qc-sample-row:hover {
border-color: #a7f3d0;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.08);
}
.qc-sample-row__ctx {
display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
padding: 10px 16px;
background: var(--primary-bg);
border-bottom: 1px solid #a7f3d0;
font-size: 12px;
}
.qc-sample-row__idx { font-size: 12px; color: #94a3b8; min-width: 20px; }
.qc-sample-row__code {
font-weight: 600; font-size: 14px;
font-family: ui-monospace, Menlo, monospace; color: var(--text);
}
.qc-sample-row__sub { color: var(--muted); }
.qc-sample-row__badge {
margin-left: auto;
font-size: 11px; font-weight: 600;
padding: 2px 8px; border-radius: 4px;
background: #fff; color: var(--primary-dark); border: 1px solid #a7f3d0;
}
/* 组内 Tab(同样本多条工单) */
.qc-wo-tabs {
display: flex; border-bottom: 1px solid var(--border);
background: #fafafa; overflow-x: auto;
}
.qc-wo-tab {
padding: 10px 16px; font-size: 12px; color: var(--muted);
border-bottom: 2px solid transparent; cursor: pointer;
white-space: nowrap; transition: color 0.12s, background 0.12s;
}
.qc-wo-tab:hover { color: var(--text); background: #f8fafc; }
.qc-wo-tab.active {
color: var(--primary-dark); font-weight: 600;
border-bottom-color: var(--primary); background: #fff;
}
.qc-wo-tab__no { font-family: ui-monospace, Menlo, monospace; }
.qc-wo-tab__dot {
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
margin-right: 4px; vertical-align: middle;
}
.qc-wo-tab__dot--reception { background: #3b82f6; }
.qc-wo-tab__dot--production { background: var(--primary); }
.qc-wo-pane { display: none; }
.qc-wo-pane.active { display: block; }
/* 工单 head(对齐 QcWorkOrderCardList) */
.qc-work-order-card__head {
display: flex; flex-direction: column; gap: 10px;
padding: 14px 16px; background: #fafafa;
border-bottom: 1px solid var(--border);
}
.qc-work-order-card__head-inner {
display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px 20px;
}
.qc-work-order-card__head-main { flex: 1; min-width: 280px; }
.qc-work-order-card__title-row {
display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 10px;
}
.qc-work-order-card__no {
font-size: 15px; font-weight: 600; color: #1e293b;
font-family: ui-monospace, Menlo, monospace;
}
.qc-work-order-card__meta {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px 12px;
}
.qc-work-order-card__meta-item dt { font-size: 11px; color: #94a3b8; margin-bottom: 2px; }
.qc-work-order-card__meta-item dd { font-size: 13px; color: #334155; }
.qc-work-order-card__ops {
display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
gap: 8px; flex-shrink: 0; align-self: flex-end;
}
.el-tag {
display: inline-block; font-size: 11px; padding: 2px 7px;
border-radius: 4px; font-weight: 500;
}
.el-tag--success { background: var(--primary-bg); color: var(--primary-dark); }
.el-tag--warning { background: #fffbeb; color: #d97706; }
.el-tag--info { background: #f1f5f9; color: #64748b; }
.el-tag--primary { background: #eff6ff; color: #2563eb; }
.btn-op-item {
height: 32px; padding: 0 12px; border-radius: 8px; font-size: 13px;
border: 1px solid var(--border); background: #fff; cursor: pointer;
}
.btn-op-item.primary {
background: var(--primary); border-color: var(--primary); color: #fff;
}
/* 任务区(对齐 QcWorkOrderTaskSubList) */
.qc-work-order-card__tasks {
padding: 10px 16px 12px; background: #f8fafc;
}
.qc-work-order-task-sublist__head {
display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;
}
.qc-work-order-task-sublist__head-title {
padding-left: 10px; border-left: 3px solid var(--primary);
font-size: 12px; font-weight: 600; color: #334155;
}
.qc-work-order-task-sublist__count { font-size: 12px; color: #94a3b8; }
/* 任务:三列 × 两行融合(行内并排,减少左侧空白) */
.qc-task-cards-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.qc-task-card {
display: flex; flex-direction: column; justify-content: center; gap: 5px;
min-height: 68px; padding: 7px 10px;
border: 1px solid var(--border); border-radius: 8px; background: #fff;
transition: border-color 0.15s, box-shadow 0.15s;
min-width: 0;
}
.qc-task-card:hover {
border-color: #a7f3d0;
box-shadow: 0 1px 4px rgba(16, 185, 129, 0.1);
}
.qc-task-card__line {
display: flex; align-items: center; gap: 6px; min-width: 0;
}
.qc-task-card__line--top { flex-wrap: wrap; row-gap: 4px; }
.qc-task-card__name {
font-size: 13px; font-weight: 600; color: #1e293b; line-height: 1.25;
flex-shrink: 0; max-width: 100%;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.qc-task-card__tags {
display: flex; flex-wrap: wrap; align-items: center;
gap: 3px; flex: 1; min-width: 0;
}
.qc-task-card__code {
font-family: ui-monospace, Menlo, monospace;
font-size: 10px; color: #475569; line-height: 1.25;
flex: 1; min-width: 0;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.qc-task-card__sep { flex-shrink: 0; color: #cbd5e1; font-size: 11px; }
.qc-task-card__assignees {
flex-shrink: 0; max-width: 64px;
font-size: 11px; color: #64748b;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.qc-work-order-task-sublist__ops {
flex-shrink: 0; display: flex; gap: 4px; margin-left: auto;
}
.btn-op-item.sm { height: 26px; font-size: 11px; padding: 0 8px; }
.btn-op-item.sm.icon-only { min-width: 26px; padding: 0; justify-content: center; }
.qc-work-order-task-sublist__empty {
min-height: 72px; display: flex; align-items: center; justify-content: center;
font-size: 13px; color: #94a3b8; text-align: center;
border: 1px dashed var(--border); border-radius: 8px; background: #fff;
}
.pagination {
display: flex; justify-content: flex-end; align-items: center; gap: 8px;
padding: 12px 20px; border-top: 1px solid var(--border);
font-size: 13px; color: var(--muted);
}
.pagination button {
height: 28px; min-width: 28px; padding: 0 8px;
border: 1px solid var(--border); border-radius: 6px; background: #fff; cursor: pointer;
}
.pagination button.active { background: var(--primary); color: #fff; border-color: var(--primary); }
@media (max-width: 900px) {
.qc-task-cards-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.filter-grid { grid-template-columns: 1fr 1fr; }
.qc-work-order-card__head-inner { flex-direction: column; }
.qc-work-order-card__ops { align-self: stretch; justify-content: flex-start; }
}
@media (max-width: 520px) {
.qc-task-cards-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="page-container">
<section class="panel">
<div class="panel-top">
<h2 class="title-bar-heading">质检工单</h2>
</div>
<div class="work-order-type-tabs">
<div class="wo-type-tab active">入库质检工单</div>
<div class="wo-type-tab">出库质检工单</div>
<div class="wo-type-tab">临时质检工单</div>
</div>
<p class="demo-banner">
<strong>目标布局:</strong>列表<strong>一行一个样本组</strong>(单列)+ 同样本 <strong>Tab 切换</strong>;
质检任务为<strong>三列 × 两行融合</strong>卡片:第 1 行「项目名 + Tag」,第 2 行「任务号 · 负责人 · 操作」,减少左侧空白。
</p>
<div class="filter-block">
<div class="filter-grid">
<div class="filter-item">
<label>关键词</label>
<input type="text" placeholder="工单号 / 样本编码 / 入库编码" />
</div>
<div class="filter-item">
<label>工单状态</label>
<select><option>全部</option><option>待配置</option><option>已配置</option><option>已完成</option></select>
</div>
<div class="filter-item">
<label>触发来源</label>
<select><option>全部</option><option>样本接收</option><option>生产过程</option></select>
</div>
<div class="filter-item">
<label>创建时间</label>
<input type="text" placeholder="开始日期 — 结束日期" />
</div>
</div>
<div class="filter-actions">
<button class="btn-op-item">重置</button>
<button class="btn-op-item primary">搜索</button>
</div>
</div>
<div class="list-body qc-work-order-cards-body">
<div class="qc-work-order-cards-wrap">
<!-- 行 1:同样本 2 条 → Tab 切换 -->
<article class="qc-sample-row" data-group="g1">
<div class="qc-sample-row__ctx">
<span class="qc-sample-row__idx">1</span>
<span class="qc-sample-row__code">ZK202506120001</span>
<span class="qc-sample-row__sub">脐带血 · 协议 XY-2025-0066 · 已生效 · 接收 2026-06-12 · 李四</span>
<span class="qc-sample-row__badge">2 条质检</span>
</div>
<div class="qc-wo-tabs">
<div class="qc-wo-tab active" data-group="g1" data-pane="g1-p1">
<span class="qc-wo-tab__dot qc-wo-tab__dot--reception"></span>
<span class="qc-wo-tab__no">ZJ202606120001</span> · 样本接收
<span class="el-tag el-tag--warning" style="margin-left:6px">已配置</span>
</div>
<div class="qc-wo-tab" data-group="g1" data-pane="g1-p2">
<span class="qc-wo-tab__dot qc-wo-tab__dot--production"></span>
<span class="qc-wo-tab__no">ZJ202606120015</span> · 生产过程
<span class="el-tag el-tag--info" style="margin-left:6px">待配置</span>
</div>
</div>
<div class="qc-wo-pane active" id="g1-p1">
<header class="qc-work-order-card__head">
<div class="qc-work-order-card__head-inner">
<div class="qc-work-order-card__head-main">
<div class="qc-work-order-card__title-row">
<span class="qc-work-order-card__no">ZJ202606120001</span>
<span class="el-tag el-tag--warning">已配置</span>
<span class="el-tag el-tag--primary">样本接收</span>
</div>
<dl class="qc-work-order-card__meta">
<div class="qc-work-order-card__meta-item"><dt>实验室入库编码</dt><dd>LAB-202506120001</dd></div>
<div class="qc-work-order-card__meta-item"><dt>来源类型</dt><dd><span class="el-tag el-tag--success">细胞存储</span></dd></div>
<div class="qc-work-order-card__meta-item"><dt>样本类型</dt><dd>脐带血</dd></div>
<div class="qc-work-order-card__meta-item"><dt>检测方式</dt><dd>实验室 / 第三方</dd></div>
<div class="qc-work-order-card__meta-item"><dt>创建时间</dt><dd>2026-06-12 09:35</dd></div>
</dl>
</div>
<div class="qc-work-order-card__ops">
<button class="btn-op-item primary">⚙ 调整检测</button>
<button class="btn-op-item">👁 查看</button>
<button class="btn-op-item">⋯</button>
</div>
</div>
</header>
<section class="qc-work-order-card__tasks">
<div class="qc-work-order-task-sublist__head">
<div class="qc-work-order-task-sublist__head-title">质检任务</div>
<span class="qc-work-order-task-sublist__count">共 3 项任务 ↻</span>
</div>
<div class="qc-task-cards-grid">
<div class="qc-task-card">
<div class="qc-task-card__line qc-task-card__line--top">
<span class="qc-task-card__name">HBV-DNA</span>
<div class="qc-task-card__tags">
<span class="el-tag el-tag--success">已完成</span>
<span class="el-tag el-tag--success">实验室</span>
<span class="el-tag el-tag--success">通过</span>
</div>
</div>
<div class="qc-task-card__line">
<span class="qc-task-card__code">RW202606120001</span>
<span class="qc-task-card__sep">·</span>
<span class="qc-task-card__assignees">张质检</span>
<div class="qc-work-order-task-sublist__ops">
<button class="btn-op-item primary sm">查看</button>
</div>
</div>
</div>
<div class="qc-task-card">
<div class="qc-task-card__line qc-task-card__line--top">
<span class="qc-task-card__name">细菌培养</span>
<div class="qc-task-card__tags">
<span class="el-tag el-tag--success">已完成</span>
<span class="el-tag el-tag--success">实验室</span>
</div>
</div>
<div class="qc-task-card__line">
<span class="qc-task-card__code">RW202606120002</span>
<span class="qc-task-card__sep">·</span>
<span class="qc-task-card__assignees">张质检</span>
<div class="qc-work-order-task-sublist__ops">
<button class="btn-op-item primary sm">查看</button>
</div>
</div>
</div>
<div class="qc-task-card">
<div class="qc-task-card__line qc-task-card__line--top">
<span class="qc-task-card__name">支原体检测</span>
<div class="qc-task-card__tags">
<span class="el-tag el-tag--warning">进行中</span>
<span class="el-tag el-tag--warning">第三方</span>
<span class="el-tag el-tag--info">待结论</span>
</div>
</div>
<div class="qc-task-card__line">
<span class="qc-task-card__code">RW202606120003</span>
<span class="qc-task-card__sep">·</span>
<span class="qc-task-card__assignees">—</span>
<div class="qc-work-order-task-sublist__ops">
<button class="btn-op-item primary sm">开始</button>
<button class="btn-op-item sm icon-only">⋯</button>
</div>
</div>
</div>
</div>
</section>
</div>
<div class="qc-wo-pane" id="g1-p2">
<header class="qc-work-order-card__head">
<div class="qc-work-order-card__head-inner">
<div class="qc-work-order-card__head-main">
<div class="qc-work-order-card__title-row">
<span class="qc-work-order-card__no">ZJ202606120015</span>
<span class="el-tag el-tag--info">待配置</span>
<span class="el-tag el-tag--success">生产过程</span>
</div>
<dl class="qc-work-order-card__meta">
<div class="qc-work-order-card__meta-item"><dt>触发来源</dt><dd>生产过程</dd></div>
<div class="qc-work-order-card__meta-item"><dt>质检对象</dt><dd>CP-202506120003</dd></div>
<div class="qc-work-order-card__meta-item"><dt>产品名称</dt><dd>脐带血 MSC · P2</dd></div>
<div class="qc-work-order-card__meta-item"><dt>关联生产工单</dt><dd>SC202606120001</dd></div>
<div class="qc-work-order-card__meta-item"><dt>检测方式</dt><dd>—</dd></div>
<div class="qc-work-order-card__meta-item"><dt>创建时间</dt><dd>2026-06-14 15:20</dd></div>
</dl>
</div>
<div class="qc-work-order-card__ops">
<button class="btn-op-item primary">⚙ 配置检测</button>
<button class="btn-op-item">👁 查看</button>
<button class="btn-op-item">⋯</button>
</div>
</div>
</header>
<section class="qc-work-order-card__tasks">
<div class="qc-work-order-task-sublist__head">
<div class="qc-work-order-task-sublist__head-title">质检任务</div>
<span class="qc-work-order-task-sublist__count">共 0 项任务 ↻</span>
</div>
<div class="qc-work-order-task-sublist__empty">暂无任务,请先在工单行「配置检测」</div>
</section>
</div>
</article>
<!-- 行 2:同样本 3 条 → Tab 切换 -->
<article class="qc-sample-row" data-group="g2">
<div class="qc-sample-row__ctx">
<span class="qc-sample-row__idx">2</span>
<span class="qc-sample-row__code">ZK202506090005</span>
<span class="qc-sample-row__sub">胎盘 · 协议 XY-2025-0055 · 赵敏</span>
<span class="qc-sample-row__badge">3 条质检</span>
</div>
<div class="qc-wo-tabs">
<div class="qc-wo-tab active" data-group="g2" data-pane="g2-p1">
<span class="qc-wo-tab__dot qc-wo-tab__dot--reception"></span>
<span class="qc-wo-tab__no">ZJ202606090001</span> · 样本接收
<span class="el-tag el-tag--success" style="margin-left:6px">已完成</span>
</div>
<div class="qc-wo-tab" data-group="g2" data-pane="g2-p2">
<span class="qc-wo-tab__dot qc-wo-tab__dot--production"></span>
<span class="qc-wo-tab__no">ZJ202606100008</span> · 生产过程
<span class="el-tag el-tag--success" style="margin-left:6px">已完成</span>
</div>
<div class="qc-wo-tab" data-group="g2" data-pane="g2-p3">
<span class="qc-wo-tab__dot qc-wo-tab__dot--production"></span>
<span class="qc-wo-tab__no">ZJ202606110012</span> · 生产过程
<span class="el-tag el-tag--warning" style="margin-left:6px">已配置</span>
</div>
</div>
<div class="qc-wo-pane active" id="g2-p1">
<header class="qc-work-order-card__head">
<div class="qc-work-order-card__head-inner">
<div class="qc-work-order-card__head-main">
<div class="qc-work-order-card__title-row">
<span class="qc-work-order-card__no">ZJ202606090001</span>
<span class="el-tag el-tag--success">已完成</span>
<span class="el-tag el-tag--primary">样本接收</span>
</div>
<dl class="qc-work-order-card__meta">
<div class="qc-work-order-card__meta-item"><dt>检测方式</dt><dd>实验室 / 第三方</dd></div>
<div class="qc-work-order-card__meta-item"><dt>创建时间</dt><dd>2026-06-09 10:00</dd></div>
</dl>
</div>
<div class="qc-work-order-card__ops"><button class="btn-op-item">👁 查看</button></div>
</div>
</header>
<section class="qc-work-order-card__tasks">
<div class="qc-task-cards-grid">
<div class="qc-task-card">
<div class="qc-task-card__line qc-task-card__line--top">
<span class="qc-task-card__name">无菌检测</span>
<div class="qc-task-card__tags"><span class="el-tag el-tag--success">已完成</span></div>
</div>
<div class="qc-task-card__line">
<span class="qc-task-card__code">RW202606090001</span>
<span class="qc-task-card__sep">·</span>
<span class="qc-task-card__assignees">张质检</span>
<div class="qc-work-order-task-sublist__ops"><button class="btn-op-item primary sm">查看</button></div>
</div>
</div>
<div class="qc-task-card">
<div class="qc-task-card__line qc-task-card__line--top">
<span class="qc-task-card__name">内毒素</span>
<div class="qc-task-card__tags"><span class="el-tag el-tag--success">已完成</span></div>
</div>
<div class="qc-task-card__line">
<span class="qc-task-card__code">RW202606090002</span>
<span class="qc-task-card__sep">·</span>
<span class="qc-task-card__assignees">李质检</span>
<div class="qc-work-order-task-sublist__ops"><button class="btn-op-item primary sm">查看</button></div>
</div>
</div>
</div>
</section>
</div>
<div class="qc-wo-pane" id="g2-p2">
<header class="qc-work-order-card__head">
<div class="qc-work-order-card__head-inner">
<div class="qc-work-order-card__head-main">
<div class="qc-work-order-card__title-row">
<span class="qc-work-order-card__no">ZJ202606100008</span>
<span class="el-tag el-tag--success">已完成</span>
<span class="el-tag el-tag--success">生产过程</span>
</div>
<dl class="qc-work-order-card__meta">
<div class="qc-work-order-card__meta-item"><dt>质检对象</dt><dd>CP-202506090001 · P1</dd></div>
<div class="qc-work-order-card__meta-item"><dt>关联生产工单</dt><dd>SC202606090001</dd></div>
</dl>
</div>
<div class="qc-work-order-card__ops"><button class="btn-op-item">👁 查看</button></div>
</div>
</header>
<section class="qc-work-order-card__tasks">
<div class="qc-task-cards-grid">
<div class="qc-task-card">
<div class="qc-task-card__line qc-task-card__line--top">
<span class="qc-task-card__name">细胞活性</span>
<div class="qc-task-card__tags">
<span class="el-tag el-tag--success">已完成</span>
<span class="el-tag el-tag--success">实验室</span>
</div>
</div>
<div class="qc-task-card__line">
<span class="qc-task-card__code">RW202606100008</span>
<span class="qc-task-card__sep">·</span>
<span class="qc-task-card__assignees">王质检</span>
<div class="qc-work-order-task-sublist__ops"><button class="btn-op-item primary sm">查看</button></div>
</div>
</div>
</div>
</section>
</div>
<div class="qc-wo-pane" id="g2-p3">
<header class="qc-work-order-card__head">
<div class="qc-work-order-card__head-inner">
<div class="qc-work-order-card__head-main">
<div class="qc-work-order-card__title-row">
<span class="qc-work-order-card__no">ZJ202606110012</span>
<span class="el-tag el-tag--warning">已配置</span>
<span class="el-tag el-tag--success">生产过程</span>
</div>
<dl class="qc-work-order-card__meta">
<div class="qc-work-order-card__meta-item"><dt>质检对象</dt><dd>CP-202506110002 · P3</dd></div>
<div class="qc-work-order-card__meta-item"><dt>关联生产工单</dt><dd>SC202606090001</dd></div>
</dl>
</div>
<div class="qc-work-order-card__ops">
<button class="btn-op-item primary">⚙ 调整检测</button>
<button class="btn-op-item">👁 查看</button>
</div>
</div>
</header>
<section class="qc-work-order-card__tasks">
<div class="qc-task-cards-grid">
<div class="qc-task-card">
<div class="qc-task-card__line qc-task-card__line--top">
<span class="qc-task-card__name">STR 鉴定</span>
<div class="qc-task-card__tags">
<span class="el-tag el-tag--warning">进行中</span>
<span class="el-tag el-tag--success">实验室</span>
</div>
</div>
<div class="qc-task-card__line">
<span class="qc-task-card__code">RW202606110001</span>
<span class="qc-task-card__sep">·</span>
<span class="qc-task-card__assignees">张质检</span>
<div class="qc-work-order-task-sublist__ops"><button class="btn-op-item primary sm">开始</button></div>
</div>
</div>
<div class="qc-task-card">
<div class="qc-task-card__line qc-task-card__line--top">
<span class="qc-task-card__name">支原体</span>
<div class="qc-task-card__tags">
<span class="el-tag el-tag--info">待执行</span>
<span class="el-tag el-tag--warning">第三方</span>
</div>
</div>
<div class="qc-task-card__line">
<span class="qc-task-card__code">RW202606110002</span>
<span class="qc-task-card__sep">·</span>
<span class="qc-task-card__assignees">—</span>
<div class="qc-work-order-task-sublist__ops"><button class="btn-op-item primary sm">开始</button></div>
</div>
</div>
</div>
</section>
</div>
</article>
<!-- 行 3:仅 1 条 → 无 Tab,与现网单卡一致 -->
<article class="qc-sample-row">
<div class="qc-sample-row__ctx">
<span class="qc-sample-row__idx">3</span>
<span class="qc-sample-row__code">ZK202506080003</span>
<span class="qc-sample-row__sub">脐带血 · 协议 XY-2025-0088 · 王强 · 接收 2026-06-08</span>
</div>
<header class="qc-work-order-card__head">
<div class="qc-work-order-card__head-inner">
<div class="qc-work-order-card__head-main">
<div class="qc-work-order-card__title-row">
<span class="qc-work-order-card__no">ZJ202606080001</span>
<span class="el-tag el-tag--info">待配置</span>
<span class="el-tag el-tag--primary">样本接收</span>
</div>
<dl class="qc-work-order-card__meta">
<div class="qc-work-order-card__meta-item"><dt>实验室入库编码</dt><dd>LAB-202506080001</dd></div>
<div class="qc-work-order-card__meta-item"><dt>来源类型</dt><dd><span class="el-tag el-tag--success">细胞存储</span></dd></div>
<div class="qc-work-order-card__meta-item"><dt>协议状态</dt><dd><span class="el-tag el-tag--success">已生效</span></dd></div>
<div class="qc-work-order-card__meta-item"><dt>样本类型</dt><dd>脐带血</dd></div>
<div class="qc-work-order-card__meta-item"><dt>接收时间</dt><dd>2026-06-08 09:15</dd></div>
<div class="qc-work-order-card__meta-item"><dt>接收经手人</dt><dd>李四</dd></div>
<div class="qc-work-order-card__meta-item"><dt>检测方式</dt><dd>—</dd></div>
<div class="qc-work-order-card__meta-item"><dt>创建时间</dt><dd>2026-06-08 09:20</dd></div>
</dl>
</div>
<div class="qc-work-order-card__ops">
<button class="btn-op-item primary">⚙ 配置检测</button>
<button class="btn-op-item">⋯</button>
</div>
</div>
</header>
<section class="qc-work-order-card__tasks">
<div class="qc-work-order-task-sublist__head">
<div class="qc-work-order-task-sublist__head-title">质检任务</div>
<span class="qc-work-order-task-sublist__count">共 0 项任务 ↻</span>
</div>
<div class="qc-work-order-task-sublist__empty">暂无任务,请先在工单行「配置检测」</div>
</section>
</article>
</div>
<div class="pagination">
<span>共 3 个样本 · 5 条质检工单</span>
<button>‹</button>
<button class="active">1</button>
<button>›</button>
</div>
</div>
</section>
</div>
<script>
document.querySelectorAll('.qc-wo-tab').forEach(tab => {
tab.addEventListener('click', () => {
const group = tab.closest('.qc-sample-row');
const paneId = tab.dataset.pane;
group.querySelectorAll('.qc-wo-tab').forEach(t => t.classList.remove('active'));
group.querySelectorAll('.qc-wo-pane').forEach(p => p.classList.remove('active'));
tab.classList.add('active');
document.getElementById(paneId).classList.add('active');
});
});
</script>
</body>
</html>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,13 @@
-- 质检工单:生产过程触发来源字段
ALTER TABLE biz_qc_work_order
ADD COLUMN trigger_source VARCHAR(20) NOT NULL DEFAULT 'reception'
COMMENT '触发来源 reception=样本接收 production=生产过程' AFTER qc_biz_type,
ADD COLUMN production_work_order_id VARCHAR(64) NULL
COMMENT '关联生产工单ID(生产过程触发时)' AFTER cell_product_id,
ADD COLUMN production_task_id VARCHAR(64) NULL
COMMENT '关联生产任务ID(可选)' AFTER production_work_order_id;
UPDATE biz_qc_work_order SET trigger_source = 'reception' WHERE trigger_source IS NULL OR trigger_source = '';
CREATE INDEX idx_qc_wo_sample_trigger ON biz_qc_work_order (tenant_no, sample_code, trigger_source, del_flag);
CREATE INDEX idx_qc_wo_prod_wo ON biz_qc_work_order (production_work_order_id, del_flag);
@@ -0,0 +1,399 @@
# 生产过程发起质检 + 质检工单统一展示 实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement task-by-task.
**Goal:** 生产专员在【生产工单】卡片列表中「派发质检工单」(选细胞产品 + 备注);质检专员在【质检工单】同列表看到接收/生产两种来源并按样本 Tab 展示。同时【生产工单】入库/出库/临时三个 Tab 列表由表格改为与质检侧一致的单列卡片模式。
**Architecture:** 扩展 `biz_qc_work_order` 触发来源字段;生产 API 建单;生产/质检列表均改为单列大卡片 + 内嵌任务区(生产任务沿用现有子列表逻辑并收敛紧凑样式);质检侧同样本 Tab 分组;生产侧每工单卡片操作区增加「派发质检工单」弹窗。
**Tech Stack:** MySQL 5.7 · FastAPI · Vue 3 · Element Plus
**UI 权威 Demo:** `docs/mockups/qc-work-order-related-cards-prototype.html`(质检侧);生产侧卡片结构对齐 `QcWorkOrderCardList.vue` + 现网 `WorkOrderTaskSubList.vue`
---
## 1. 需求结论(能否实现)
**可以实现,且与现有架构兼容。**
| 诉求 | 结论 |
|------|------|
| 生产专员派发质检(选细胞产品+备注) | 生产工单卡片「派发质检工单」+ 弹窗 + API |
| 质检工单界面可见两种来源 | 同表 + 样本 Tab 分组展示 |
| 生产工单三 Tab 改卡片列表 | **可以**;新建 `ProductionWorkOrderCardList`,单列 + 内嵌任务 |
---
## 2. 业务流程(目标态)
```
市场专员 → 采集协议单
↓
质检专员 → 样本接收(结论=接收)
├→ 自动:入库质检工单 [trigger_source=reception] (放行门禁)
└→ 自动:入库生产工单
↓
生产专员 → 生产任务执行 → 产出细胞产品
↓
生产专员 → 【生产工单】卡片列表 → 【派发质检工单】
(选择细胞产品 + 填写备注 → 提交)
↓
新建:入库质检工单 [trigger_source=production] (过程质检,不替代门禁)
↓
质检专员 → 【质检工单】列表
同一样本编码下 Tab 切换:样本接收 | 生产过程(可多条)
```
---
## 3. 已确认 UI 设计
### 3.1 质检工单列表(Demo 定稿)
参考 `docs/mockups/qc-work-order-related-cards-prototype.html`:
| 层级 | 规则 |
|------|------|
| 列表布局 | **单列**,一行 = 一个样本组(`flex-direction: column`) |
| 同样本多条工单 | 顶部样本上下文条 + **Tab 切换**工单;下方 meta + 任务区联动 |
| 仅 1 条工单 | 无 Tab,结构与现网单卡一致 |
| 质检任务 | **三列网格**;每卡 **2 行融合**:① 项目名 + Tag ② 任务号 · 负责人 · 操作 |
| 筛选 | 入库 Tab 增加「触发来源」:全部 / 样本接收 / 生产过程 |
### 3.2 生产工单列表(新增,对齐卡片模式)
**现网:** `production-work-order/index.vue` 使用 `el-table` + `type="expand"` 展开生产任务(`WorkOrderTaskSubList`)。
**目标:** 入库 / 出库 / 临时三个 Tab **均改为单列卡片**,结构与质检工单大卡片一致:
```
┌─ 生产工单 SC202606120001 ─────────────────────────────┐
│ head:工单号 + 状态 Tag + meta 栅格 + 操作按钮 │
│ 操作含:查看 | 派发任务 | 派发质检工单 | 完成 | … │
├──────────────────────────────────────────────────────┤
│ tasks:生产任务子列表(内嵌,非表格 expand) │
└──────────────────────────────────────────────────────┘
```
| Tab | 卡片 meta 字段 | 分组策略 |
|-----|----------------|----------|
| **入库** | 与现网表格列一致:样本编码、协议状态、样本类型、接收时间… | 单列;通常 1 样本 1 生产工单,**不做 Tab 分组**(与质检侧不同) |
| **出库** | 业务类型、申请单号、用户名称、输注时间… | 单列,一工单一卡 |
| **临时** | 工单号、状态、备注、配置时间… | 单列,一工单一卡 |
| 层级 | 规则 |
|------|------|
| 列表布局 | **单列** `flex-direction: column`(替换 `minmax` 多列 / 表格) |
| 生产任务 | 内嵌于卡片底部 `#fafafa` 任务区;复用 `WorkOrderTaskSubList`,样式收敛为 **三列 + 两行融合**(与质检任务视觉统一,内容字段仍为任务名/状态/工艺类型) |
| 工具栏 | 保留「已选 N 项」、新增/派发任务/完成;批量逻辑暂保留(卡片可加 checkbox 或后续迭代) |
| 移除 | `el-table` + `expand` 行 |
**新建组件:**
- `ProductionWorkOrderCardList.vue` — 对标 `QcWorkOrderCardList.vue`
- `index.vue` — 表格区域替换为上述 CardList
### 3.3 派发质检工单(生产工单入口)
**位置:** 生产工单卡片 **操作区** 主按钮(入库 Tab 必做;出库若有关联细胞产品可二期扩展)。
**交互:**
1. 点击 **「派发质检工单」**(`Promotion` / `Document` 类图标 + 文案,非 primary 实心,与「派发任务」并列描边按钮;每卡仅一个 primary 时仍留给「进入执行/派发任务」)
2. 打开弹窗 `DispatchQcWorkOrderDialog.vue`
3. 表单(`label-position="top"`,`.dialog-form-layout` + `.form-grid-2`):
- **细胞产品**(必填):下拉,数据来自该生产工单下已产出细胞产品
- **备注**(选填):`textarea`,max 512
4. 确定 → `POST /web/admin/production-work-orders/{id}/qc-work-orders`
5. 成功:`ElMessage.success`;可选「前往质检工单」跳转 `/#/quality-manage/qc-work-order?sampleCode=xxx`
**细胞产品选项来源(后端):**
```
GET /web/admin/production-work-orders/{id}/cell-product-options
→ [{ id, productCode, productName, productTypeName, passage, storageSlotName, taskNo }]
```
从该工单下所有生产任务的 **输出子表**(`biz_production_task_output` / cell_product)去重汇总;无产出时禁用按钮并 tooltip「请先完成生产任务并录入输出细胞产品」。
**前置条件:**
- 工单状态建议 `jxz` 或 `ywc`(`dpz` 无产出时不可派发)
- 租户隔离、软删除一致
---
## 4. 数据模型
### 4.1 `biz_qc_work_order` 新增字段
```sql
ALTER TABLE biz_qc_work_order
ADD COLUMN trigger_source VARCHAR(20) NOT NULL DEFAULT 'reception'
COMMENT '触发来源 reception=样本接收 production=生产过程' AFTER qc_biz_type,
ADD COLUMN production_work_order_id VARCHAR(64) NULL
COMMENT '关联生产工单ID(生产过程触发时)' AFTER cell_product_id,
ADD COLUMN production_task_id VARCHAR(64) NULL
COMMENT '关联生产任务ID(可选)' AFTER production_work_order_id;
-- 历史数据:全部为 reception
UPDATE biz_qc_work_order SET trigger_source = 'reception' WHERE trigger_source IS NULL OR trigger_source = '';
-- 索引
CREATE INDEX idx_qc_wo_sample_trigger ON biz_qc_work_order (tenant_no, sample_code, trigger_source, del_flag);
CREATE INDEX idx_qc_wo_prod_wo ON biz_qc_work_order (production_work_order_id, del_flag);
```
**说明:**
- `uk_qc_wo_reception(reception_id, del_flag)` 保留,保证接收触发仍 1:1。
- 生产过程触发 **不占用** 该唯一键;同一样本可有多条 `trigger_source=production` 工单。
- `cell_product_id` 已有;生产触发时 **必填**,接收触发时为空。
- **不放** `work_order_type=temporary`;过程质检仍属入库链路(`work_order_type=inbound`)。
### 4.2 业务规则(需产品确认,建议默认)
| 规则 | 建议默认值 |
|------|------------|
| 放行门禁质检 | 仅 `trigger_source=reception` 的那条参与入库放行 |
| 过程质检是否阻塞放行 | **否**(默认) |
| 发起时机 | 细胞产品已写入 `biz_cell_product`(生产任务输出后) |
| 同一产品重复发起 | 允许(如 P1/P2 各一次);可选幂等:同 `production_work_order_id + cell_product_id` 且状态非 yzf 时不重复建 |
| 检测项目 | 与现网一致:质检专员在工单上「配置检测」时自选 |
---
## 5. 后端实现
### 5.1 新建 CRUD
**文件:** `backend/app/crud/qc_work_order.py`
```python
def create_qc_work_order_from_production(
db, *,
production_work_order_id: str,
cell_product_id: str,
production_task_id: str | None = None,
remark: str = "",
tenant_no: str,
user_id: str,
create_dept_id: str = "",
) -> BizQcWorkOrder:
"""
1. 校验生产工单存在、tenant、work_order_type=inbound
2. 校验 cell_product 存在且 tenant 一致
3. 从生产工单快照 sample_code / protocol_no / reception_id 等
4. 插入 biz_qc_work_order:
trigger_source=production, work_order_status=dpz
5. flush,不 commit(由 API 层 commit)
"""
```
**文件:** `backend/app/schemas/qc_work_order.py` — 新增 `ProductionQcWorkOrderCreateBody`
### 5.2 API
**文件:** `backend/app/api/production.py`
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/production-work-orders/{work_order_id}/qc-work-orders` | 派发质检工单 Body: `{ cellProductId, remark? }` |
| GET | `/production-work-orders/{work_order_id}/cell-product-options` | 弹窗细胞产品下拉选项 |
权限:生产工单菜单(如 `biz:productionWorkOrder:list`);建单写操作可复用或新增 `biz:productionWorkOrder:dispatchQc`。
### 5.3 列表 API 扩展
**文件:** `backend/app/crud/qc_work_order.py` · `list_work_orders`
- `_wo_to_dict` 增加:`triggerSource`, `triggerSourceLabel`, `productionWorkOrderId`, `productionTaskId`
- 筛选参数:`triggerSource=reception|production`
- **分组(二选一):**
- **A(推荐)** 后端返回 `groups: [{ sampleCode, sampleMeta, workOrders: [...] }]`,分页按样本组
- **B** 仍返回 flat list,前端按 `sampleCode` 分组(改动小,分页语义弱)
### 5.4 接收触发现有逻辑
**文件:** `backend/app/crud/qc_work_order.py` · `create_work_order_from_reception`
创建时显式写 `trigger_source='reception'`。
### 5.5 入库放行联动
**文件:** `backend/app/crud/storage_release_review.py` · `_build_qc_info`
查询质检工单时增加:
```python
.filter(BizQcWorkOrder.trigger_source == 'reception')
```
避免多条同工单时取错;过程质检不参与放行展示。
---
## 6. 前端实现
### 6.1 生产侧 — 列表卡片化 + 派发质检
**核心文件:**
| 文件 | 改动 |
|------|------|
| `production-work-order/index.vue` | 移除 `el-table`;接入 `ProductionWorkOrderCardList`;挂载 `DispatchQcWorkOrderDialog` |
| 新建 `ProductionWorkOrderCardList.vue` | 单列卡片;head(meta+ops) + 内嵌 `WorkOrderTaskSubList`;三 Tab 字段分支 |
| `WorkOrderTaskSubList.vue` | 任务卡改为三列 + 两行融合(与质检任务样式对齐) |
| 新建 `DispatchQcWorkOrderDialog.vue` | 细胞产品 select + 备注 + 提交 |
| `api/production-work-order.ts` | `fetchCreateQcFromProduction`、`fetchProductionWorkOrderCellProductOptions` |
**卡片操作区按钮(入库示例,从左到右):**
| 按钮 | 类型 | 说明 |
|------|------|------|
| 派发任务 | primary(每卡唯一主色) | 现有逻辑,打开 `DispatchTaskDialog` |
| 派发质检工单 | 描边 | 打开 `DispatchQcWorkOrderDialog` |
| 完成工单 | 描边 | 现有逻辑 |
| 查看 / 编辑 / 删除 | 描边 / 更多 | 现有逻辑 |
**`DispatchQcWorkOrderDialog.vue` 要点:**
```vue
<!-- 560px dialog-form-layout -->
<!-- 只读:工单号、样本编码 -->
<!-- 必填:el-select 细胞产品(filterable,option 展示 编码 · 名称 · 代次) -->
<!-- 选填:备注 textarea -->
<!-- footer:取消 Close / 确定 Check -->
```
**不再**在 `TaskOutputList` 单独做「发起质检」入口(统一为工单卡片「派发质检工单」,避免两处入口)。
### 6.2 质检侧 — 列表重构
**核心文件:**
| 文件 | 改动 |
|------|------|
| `QcWorkOrderCardList.vue` | 单列布局;引入样本组 + Tab 容器 |
| 新建 `QcWorkOrderSampleGroup.vue` | 样本上下文条 + Tab + 内嵌单工单 head/tasks |
| `QcWorkOrderTaskSubList.vue` | 任务卡改为三列网格 + 两行融合布局 |
| `qc-work-order/index.vue` | 筛选增加「触发来源」;适配分组列表数据结构 |
| `api/qc-work-order.ts` | 类型扩展 + 分组 list 或前端 groupBy 工具 |
**Tab 标签:** `{workOrderNo} · 样本接收|生产过程` + 状态 Tag
**单条工单 meta 差异:**
| 字段 | 样本接收 | 生产过程 |
|------|----------|----------|
| 触发来源 | 样本接收 | 生产过程 |
| 质检对象 | 原始样本 / — | 细胞产品编码 + 名称 |
| 关联生产工单 | — | 生产工单号 |
样本级字段(样本编码、协议、接收信息)提到 **样本上下文条**,Tab 内 meta 不重复。
### 6.3 任务卡片样式(质检 + 生产任务统一)
质检任务(`QcWorkOrderTaskSubList`)与生产任务(`WorkOrderTaskSubList`)均采用:
```scss
.qc-task-cards-grid, .production-task-cards-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
// 每卡 2 行:name+tags / code·meta·ops
// min-height ~68px
```
可抽公共 SCSS 片段到 `web-admin/src/styles/` 或共享 class 前缀 `work-order-task-cards-grid`。
---
## 7. 任务分解(实施顺序)
### Phase 1 — 数据与后端建单(可独立验收)
- [ ] 1.1 SQL 迁移写入 `docs/sql/migrations/2026-06-12-qc-work-order-trigger-source.sql`
- [ ] 1.2 Model / Schema 增加字段
- [ ] 1.3 `create_qc_work_order_from_reception` 写 `trigger_source=reception`
- [ ] 1.4 实现 `create_qc_work_order_from_production`
- [ ] 1.5 实现 `list_cell_product_options_for_production_work_order`
- [ ] 1.6 生产 API:`POST .../qc-work-orders`、`GET .../cell-product-options`
- [ ] 1.7 `_wo_to_dict` + list 筛选 `triggerSource`
- [ ] 1.8 放行审核 `_build_qc_info` 仅取 reception 工单
- [ ] 1.9 运行 `python scripts/check_field_consistency.py`(若涉及 CRUD 字段)
### Phase 2 — 生产工单 UI(卡片化 + 派发质检)
- [ ] 2.1 API:`fetchCreateQcFromProduction`、`fetchProductionWorkOrderCellProductOptions`
- [ ] 2.2 新建 `DispatchQcWorkOrderDialog.vue`
- [ ] 2.3 新建 `ProductionWorkOrderCardList.vue`(入库/出库/临时 meta 分支)
- [ ] 2.4 `index.vue` 表格 → CardList;工具栏与选择逻辑适配
- [ ] 2.5 `WorkOrderTaskSubList.vue` 三列紧凑任务卡
- [ ] 2.6 卡片操作区「派发质检工单」接入弹窗
- [ ] 2.7 权限码(如需)
### Phase 3 — 质检列表 UI(Demo 落地)
- [ ] 3.1 `QcWorkOrderSampleGroup.vue`(Tab + 样本条)
- [ ] 3.2 `QcWorkOrderCardList.vue` 改单列 + 分组渲染
- [ ] 3.3 列表 API 分组或前端 `groupWorkOrdersBySample()`
- [ ] 3.4 筛选「触发来源」
- [ ] 3.5 `QcWorkOrderTaskSubList.vue` 三列紧凑任务卡
- [ ] 3.6 查看弹窗 / 配置检测:生产过程工单展示 `cellProduct*`、生产工单号
### Phase 4 — 联调与文档
- [ ] 4.1 端到端:接收 → 生产卡片 → 派发质检 → 质检列表 Tab 可见
- [ ] 4.2 生产三 Tab 卡片列表回归:派发任务、完成工单、查看
- [ ] 4.3 放行审核仍只认 reception 工单
- [ ] 4.4 更新 OpenSpec / 设计 spec(可选)
- [ ] 4.5 `./scripts/update-graph.sh web-admin`(若改 import/组件)
---
## 8. 风险与待确认项
| 项 | 说明 | 建议 |
|----|------|------|
| 放行门禁 | 过程质检是否影响放行 | 默认不影响,仅 reception 工单入门禁 |
| 分页语义 | 按工单分页 vs 按样本分页 | 改按样本组分页,总数展示「N 样本 · M 工单」 |
| 出库/临时 Tab | 是否同样本分组 | 质检入库 Tab 按样本 Tab;生产三 Tab 均 **一工单一卡** 无样本 Tab |
| 派发质检范围 | 出库生产是否派发 | 一期 **入库生产工单**;出库有细胞产品场景二期 |
| 生产列表批量选择 | 卡片模式多选 | 一期可保留工具栏批量按钮 + 卡片 checkbox,或暂禁批量仅单卡操作 |
| 临时质检 Tab | 与生产过程质检区别 | 临时工单无样本关联;勿与 production 触发混淆 |
| 权限 | 谁可派发质检 | 生产专员 + 生产工单相关权限 |
---
## 9. 验收标准
**质检工单**
1. 样本接收后,质检列表出现 1 条「样本接收」工单(行为与现网一致)。
2. 生产专员 **派发质检工单** 后,同一样本组出现新 Tab「生产过程」,状态为待配置。
3. 质检专员 Tab 切换可分别配置检测、查看任务;任务区为三列两行融合卡片。
4. 筛选「触发来源=生产过程」仅显示生产触发工单。
5. 入库放行审核详情中,质检信息仍为接收触发工单。
**生产工单**
6. 入库 / 出库 / 临时三个 Tab 均为 **单列卡片**,无 `el-table` 主列表。
7. 卡片内嵌生产任务子列表,任务为紧凑三列布局。
8. 入库生产工单卡片可点击 **派发质检工单**,选细胞产品 + 备注后创建成功。
9. 无细胞产品产出时按钮不可用或有明确提示。
10. 原有「派发任务」「完成工单」「查看」等操作在卡片模式下仍可用。
---
## 10. 参考
- UI Demo(质检):`docs/mockups/qc-work-order-related-cards-prototype.html`
- 质检卡片:`web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue`
- 生产工单页(待改):`web-admin/src/views/production/production-work-order/index.vue`
- 生产任务子列表:`web-admin/src/views/production/production-work-order/WorkOrderTaskSubList.vue`
- 派发任务弹窗(表单参考):`DispatchTaskDialog.vue`
- 接收建单:`backend/app/crud/qc_work_order.py` · `create_work_order_from_reception`
- 接收事务:`backend/app/crud/sample_reception.py`
- 质检工单表:`biz_qc_work_order`
@@ -0,0 +1,44 @@
# 质检工单 · 用户费用摘要 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在入库/出库质检工单列表与查看弹窗中,为采集协议单与基础实验来源展示用户费用当前快照。
**Architecture:** 后端新增 `qc_work_order_fee_summary` 服务批量计算 `paymentSummary` / `feeSummary`,在列表与详情 enrich 中挂载;前端在样本组/卡片增加摘要条,查看弹窗补充缴费区块。
**Tech Stack:** Python 3.9 / FastAPI / SQLAlchemy · Vue 3 / Element Plus / TypeScript
**Spec:** `docs/superpowers/specs/2026-06-12-qc-work-order-fee-summary-design.md`
---
### Task 1: 后端费用摘要服务
**Files:**
- Create: `backend/app/services/qc_work_order_fee_summary.py`
- Modify: `backend/app/crud/qc_work_order.py`
- [x] 实现 `build_protocol_payment_summaries` / `build_basic_experiment_fee_summaries` / `enrich_work_order_fee_summaries`
- [x] 在 `list_work_orders` 与 `get_work_order_detail` / `get_outbound_qc_detail` 调用 enrich
### Task 2: 前端 API 类型
**Files:**
- Modify: `web-admin/src/api/qc-work-order.ts`
- [x] 新增 `QcProtocolPaymentSummary` / `QcBasicExperimentFeeSummary` 并扩展 `QcWorkOrder`
### Task 3: 列表 UI
**Files:**
- Create: `web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue`
- Modify: `QcWorkOrderSampleGroup.vue`、`QcWorkOrderSingleCard.vue`、`index.vue`(筛选补基础实验)
### Task 4: 查看弹窗
**Files:**
- Modify: `index.vue`(入库 viewSections)、`OutboundQcViewDialog.vue`
### Task 5: 图谱更新
- [x] `./scripts/update-graph.sh frontend`(若新建组件)
@@ -0,0 +1,292 @@
# 质检工单 · 用户费用摘要展示设计
> 日期:2026-06-12
> 状态:已实现
> 需求确认:**方案 A — 当前快照**(展示合同/已收/待缴/缴费状态,不含历史轨迹、不含与质检动作的联动规则)
---
## 1. 背景与目标
### 1.1 业务诉求
质检专员在 **入库质检工单**、**出库质检工单** 列表中处理工单时,需要快速了解关联来源单据上的 **用户费用当前状态**,避免在配置/执行质检时忽略欠费、待确认收款等情况。
### 1.2 展示范围(已确认)
| Tab | 来源 | 系统标识 | 是否展示费用 |
|-----|------|----------|-------------|
| 入库质检 | 采集协议单(细胞存储) | `receptionSource = cell_storage` | **是** |
| 入库质检 | 捐赠协议单(科研捐赠) | `receptionSource = research_donation` | **否**(业务无缴费) |
| 出库质检 | 中试服务申请单 | `bizType = pilot_service` | **否**(渠道扣费) |
| 出库质检 | 基础实验 | `bizType = basic_experiment` | **是** |
| 出库质检 | 临床应用 | `bizType = clinical_application` | **否**(本期不做,架构预留) |
### 1.3 非目标
- 不展示费用 **变更历史**、操作日志、时间线
- 不根据费用状态 **阻断或自动推进** 质检流程
- 不在本模块 **编辑** 协议/申请单费用
- 不展示 **质检检测自身费用**(第三方检测 `thirdPartyFeeTotal`、任务 `feeAmount`)——与用户协议/申请单费用分区展示,避免混淆
### 1.4 与现有 UI 的关系
- **列表层**:新增紧凑「费用摘要条」,满足「特别关注」的扫视需求
- **查看弹窗层**:补充完整只读缴费区块,可跳转来源单据详情
- 视觉对齐 Fresh Mint 规范;金额用 `formatMoneyDisplay`;状态用 `el-tag`
---
## 2. 费用字段定义
### 2.1 入库 · 采集协议单(`paymentSummary`)
数据来源:`biz_protocol_payment` + `biz_protocol_payment_detail`(经 `collection_protocol_id` 关联)。
| 字段 | API 别名 | 说明 | 计算/口径 |
|------|----------|------|-----------|
| 缴费模式 | `payMode` | `dj` 趸交 / `fq` 分期 | 主表 |
| 缴费模式标签 | `payModeLabel` | 趸交 / 分期 | 后端或前端码表 |
| 合同金额 | `contractAmount` | 元 | 主表 `contract_amount` |
| 已收金额 | `receivedAmount` | 元 | 明细中 `payStatus ∈ {yjf, jfqrz}` 的 `transferAmount` 之和(与 `ProtocolViewDialog` 一致) |
| 待收金额 | `pendingAmount` | 元 | `max(contractAmount - receivedAmount, 0)`,合同为空时返回 `null` |
| 缴费状态摘要 | `statusHint` | 如「已缴清」「1 期待确认」「2 期待缴费」 | 由明细 `payStatus` 聚合生成 |
| 整体缴费状态 Tag | `overallPayStatus` / `overallPayStatusLabel` | 用于摘要条 Tag | 见 §2.1.1 |
#### 2.1.1 整体缴费状态(摘要 Tag 语义)
优先级从高到低:
1. 存在 `tfqrz`(退费确认中)→ Tag `warning`,文案「退费确认中」
2. 存在 `ytf` 且无其他未完结明细 → Tag `info`,文案「已退费」
3. 合同金额有效且 `pendingAmount <= 0` 且不存在 `dfj`/`jfqrz` → Tag `success`,文案「已缴清」
4. 存在 `jfqrz` → Tag `warning`,文案「收款确认中」
5. 存在 `dfj` → Tag `info`,文案「待缴费」
6. 其他 → Tag `info`,文案「—」
分期场景下 `statusHint` 附加明细计数,例如:「分期 · 2 期已缴 / 1 期待缴费」。
### 2.2 出库 · 基础实验(`feeSummary`)
数据来源:`biz_pilot_infusion_schedule.amount`(售卖金额)+ `biz_pilot_payment`(按 `infusion_schedule_id` 一对一)。
| 字段 | API 别名 | 说明 |
|------|----------|------|
| 售卖金额 | `saleAmount` | `schedule.amount` |
| 应缴金额 | `paymentAmount` | `BizPilotPayment.amount` |
| 实收金额 | `transferAmount` | 已确认收款时的 `transfer_amount`,否则 `null` |
| 缴费状态 | `payStatus` | `dfj` / `jfqrz` / `yjf` / `tfqrz` / `ytf` |
| 缴费状态标签 | `payStatusLabel` | 与临床应用缴费模块码表一致 |
| 缴费状态 Tag 类型 | `payStatusTagType` | 复用 `payStatusTagType()` |
无缴费记录时:`paymentAmount`、`payStatus` 等为 `null`,摘要展示「暂无缴费记录」。
---
## 3. 后端设计
### 3.1 列表 API 扩展
**接口**:现有 `GET /web/admin/qc-work-orders`(`list_work_orders`)
在 `_enrich_list_items` 流程末尾批量 enrich,避免 N+1:
#### 入库 Tab(`workOrderType=inbound`)
1. 收集本页工单的有效 `collectionProtocolId`(且 `receptionSource === cell_storage`)
2. 批量查询 payment 主表 + 明细
3. 为每条工单写入 `paymentSummary`(非 cell_storage 时为 `null`)
#### 出库 Tab(`workOrderType=outbound`)
1. 收集本页 `bizType === basic_experiment` 的 `infusionScheduleId`
2. 批量查询 `BizPilotPayment` + schedule.amount
3. 写入 `feeSummary`(非 basic_experiment 时为 `null`)
**新增工具函数建议位置**:`backend/app/crud/qc_work_order.py` 或拆至 `backend/app/services/qc_work_order_fee_summary.py`
```python
def build_protocol_payment_summaries(db, protocol_ids: List[str], tenant_no: str) -> Dict[str, dict]: ...
def build_basic_experiment_fee_summaries(db, schedule_ids: List[str], tenant_no: str) -> Dict[str, dict]: ...
```
### 3.2 详情 API(可选增强)
- **入库查看**:现有工单详情接口增加 `paymentSummary` + `paymentDetails`(明细列表,供弹窗展开;可限制最多返回当前协议全部明细,通常 ≤ 分期期数)
- **出库查看**:`get_outbound_qc_detail` 增加 `feeSummary`;`OutboundQcViewDialog` 已有 schedule 区块,补充缴费只读段
本期 **最低交付**:列表摘要字段;详情弹窗至少与列表同级信息,推荐附带 1~3 条关键明细(分期时下一期应缴)。
### 3.3 数据库
**无需 DDL**。只读 join 现有表:
- `biz_protocol_payment` / `biz_protocol_payment_detail`
- `biz_pilot_payment` / `biz_pilot_infusion_schedule`
### 3.4 性能
- 列表 enrich 按 **当前页 protocol_id / schedule_id 去重** 后批量查询
- 单页默认 10~20 条,额外 1~2 次 batch query,可接受
---
## 4. 前端设计
### 4.1 入库 · 样本组费用摘要条
**文件**:`web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue`
**位置**:`.qc-sample-row__ctx` 下方、工单 Tab 上方,仅当组内任一工单 `receptionSource === 'cell_storage'` 且存在 `paymentSummary` 时渲染。
**布局**(单行 flex,窄屏换行):
```
[协议费用] 趸交 · 合同 ¥28,000.00 · 已收 ¥28,000.00 · 待收 ¥0.00 · [已缴清]
```
- 背景:`#ecfdf5`(`--primary-bg`),上边框与 ctx 区分
- 字体:12px,标签 muted,金额 semibold
- 右侧 Tag:`overallPayStatusLabel` + 语义色
- 点击摘要条或「查看缴费」链接:打开入库工单查看弹窗并滚动至缴费区(可选,P1)
**组内多工单**:费用按 `collectionProtocolId` 去重,同协议只展示一份(组 context 已按样本聚合)。
### 4.2 出库 · 基础实验卡片摘要
**文件**:`web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue`
**条件**:`activeTypeTab === 'outbound' && row.bizType === 'basic_experiment' && row.feeSummary`
**位置**:`.qc-work-order-card__meta` 末尾或独立 `.qc-fee-summary` 行(全宽 `grid-column: 1 / -1`)
```
[费用信息] 售卖 ¥12,000.00 · 应缴 ¥12,000.00 · [待缴费]
```
已缴费时追加:`实收 ¥12,000.00`
### 4.3 查看弹窗
| 场景 | 文件 | 新增区块 |
|------|------|----------|
| 入库(cell_storage) | `index.vue` `viewSections` | 「协议缴费信息」:模式、合同、已收、待收、状态 Tag;分期时列 1 行下一期应缴(如有) |
| 出库(basic_experiment) | `OutboundQcViewDialog.vue` | 「缴费信息」:售卖、应缴、实收、状态;可复用 `ClinicalPaymentSubmitReadonly` 精简字段 |
跳转链接(可选 P1):
- 入库 → `/#/cell-storage/protocol` 并打开对应协议查看弹窗(需协议 id)
- 出库 → 基础实验申请单查看(`applicationId` 若列表已有)
### 4.4 筛选修正
**文件**:`index.vue` 出库 Tab 筛选「业务类型」
补全选项:
```html
<el-option label="基础实验" value="basic_experiment" />
```
与三种出库来源一致。
### 4.5 API 类型
**文件**:`web-admin/src/api/qc-work-order.ts`
```typescript
export type QcProtocolPaymentSummary = {
payMode?: string
payModeLabel?: string
contractAmount?: number | null
receivedAmount?: number | null
pendingAmount?: number | null
statusHint?: string
overallPayStatus?: string
overallPayStatusLabel?: string
}
export type QcBasicExperimentFeeSummary = {
saleAmount?: number | null
paymentAmount?: number | null
transferAmount?: number | null
payStatus?: string
payStatusLabel?: string
}
// QcWorkOrder 扩展
paymentSummary?: QcProtocolPaymentSummary | null
feeSummary?: QcBasicExperimentFeeSummary | null
```
### 4.6 公共组件(可选)
若入库/出库摘要条样式一致,可抽 `QcWorkOrderFeeSummaryBar.vue`(props: `variant: 'inbound-protocol' | 'outbound-basic'` + summary object)。**非必须**,首版可在两处各写 scoped 块,YAGNI。
---
## 5. 交互与空态
| 情况 | 展示 |
|------|------|
| 科研捐赠入库工单 | 不渲染费用区块 |
| 中试/临床出库工单 | 不渲染费用区块 |
| 采集协议无 payment 主表 | 摘要:「暂无缴费信息」 |
| 基础实验无 payment 记录 | 摘要:「暂无缴费记录」;仍展示售卖金额(若有) |
| 金额为 null | 显示 `—` |
**只读**:摘要区无编辑、无确认收款按钮。
---
## 6. 测试要点
### 6.1 入库
- [ ] 细胞存储样本组显示协议费用摘要,数值与采集协议单查看弹窗一致
- [ ] 科研捐赠样本组不显示费用摘要
- [ ] 趸交已缴清 / 分期部分待缴 / 收款确认中 / 退费确认中 Tag 正确
- [ ] 同页多条工单同协议,摘要去重一致
### 6.2 出库
- [ ] 基础实验卡片显示售卖 + 缴费状态
- [ ] 中试服务、临床应用卡片无费用摘要
- [ ] 筛选「基础实验」可正确过滤
- [ ] 查看弹窗缴费区与列表摘要一致
### 6.3 回归
- [ ] 第三方检测费用展示不受影响
- [ ] 临时质检 Tab 无费用区块
- [ ] 列表分页、分组、Tab 切换正常
---
## 7. 影响文件清单
| 层级 | 文件 |
|------|------|
| 后端 | `backend/app/crud/qc_work_order.py`(+ 可选 `services/qc_work_order_fee_summary.py`) |
| 后端 Schema | `backend/app/schemas/qc_work_order.py`(若响应体走 Pydantic) |
| 前端 API | `web-admin/src/api/qc-work-order.ts` |
| 前端页面 | `QcWorkOrderSampleGroup.vue`、`QcWorkOrderSingleCard.vue`、`index.vue`、`OutboundQcViewDialog.vue` |
| 工具 | 复用 `formatMoneyDisplay`、`payModeLabel`、`payStatusLabel`、`payStatusTagType` |
---
## 8. 后续扩展(本期不做)
- 临床应用出库工单费用摘要(`feeSummary` 结构可复用 `BizPilotPayment`)
- 摘要条点击跳转来源单据
- 与「分期缴费用户预警」模块联动高亮欠费
---
## 9. 决策记录
| 日期 | 决策 |
|------|------|
| 2026-06-12 | 「费用变化」= **当前快照**,不做历史轨迹 |
| 2026-06-12 | 入库仅 **采集协议单**;出库仅 **基础实验** |
| 2026-06-12 | 列表摘要 + 查看弹窗详情,推荐实现路径 |
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+128 -118
View File
@@ -1,11 +1,11 @@
# Graph Report - src (2026-06-12)
## Corpus Check
- Large corpus: 697 files · ~440,830 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
- Large corpus: 704 files · ~444,651 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
## Summary
- 2079 nodes · 2717 edges · 51 communities detected
- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 610 edges (avg confidence: 0.8)
- 2103 nodes · 2742 edges · 53 communities detected
- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 615 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
@@ -40,32 +40,34 @@
- [[_COMMUNITY_Community 28|Community 28]]
- [[_COMMUNITY_Community 29|Community 29]]
- [[_COMMUNITY_Community 30|Community 30]]
- [[_COMMUNITY_Community 32|Community 32]]
- [[_COMMUNITY_Community 31|Community 31]]
- [[_COMMUNITY_Community 33|Community 33]]
- [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 35|Community 35]]
- [[_COMMUNITY_Community 36|Community 36]]
- [[_COMMUNITY_Community 37|Community 37]]
- [[_COMMUNITY_Community 38|Community 38]]
- [[_COMMUNITY_Community 39|Community 39]]
- [[_COMMUNITY_Community 40|Community 40]]
- [[_COMMUNITY_Community 41|Community 41]]
- [[_COMMUNITY_Community 42|Community 42]]
- [[_COMMUNITY_Community 43|Community 43]]
- [[_COMMUNITY_Community 44|Community 44]]
- [[_COMMUNITY_Community 45|Community 45]]
- [[_COMMUNITY_Community 46|Community 46]]
- [[_COMMUNITY_Community 47|Community 47]]
- [[_COMMUNITY_Community 48|Community 48]]
- [[_COMMUNITY_Community 49|Community 49]]
- [[_COMMUNITY_Community 50|Community 50]]
- [[_COMMUNITY_Community 52|Community 52]]
- [[_COMMUNITY_Community 53|Community 53]]
- [[_COMMUNITY_Community 54|Community 54]]
- [[_COMMUNITY_Community 55|Community 55]]
- [[_COMMUNITY_Community 56|Community 56]]
- [[_COMMUNITY_Community 58|Community 58]]
- [[_COMMUNITY_Community 60|Community 60]]
## God Nodes (most connected - your core abstractions)
1. `get()` - 219 edges
2. `push()` - 43 edges
1. `get()` - 221 edges
2. `push()` - 44 edges
3. `WebSocketClient` - 26 edges
4. `emit()` - 16 edges
4. `emit()` - 17 edges
5. `MenuProcessor` - 16 edges
6. `VersionManager` - 15 edges
7. `handleDynamicRoutes()` - 15 edges
@@ -74,220 +76,228 @@
10. `withFullscreenLoading()` - 13 edges
## Surprising Connections (you probably didn't know these)
- `handleReceptionAttachmentDownload()` --calls--> `downloadAttachmentFile()` [INFERRED]
views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue → utils/download-attachment.ts
- `handleDownloadAttachment()` --calls--> `downloadAttachmentFile()` [INFERRED]
views/pilot-service/infusion-schedule-management/InfusionScheduleHandoverDialog.vue → utils/download-attachment.ts
- `subscribeTokenRefresh()` --calls--> `push()` [INFERRED]
utils/http/index.ts → views/operations/storage-facility/index.vue
- `fetchLeaveList()` --calls--> `get()` [INFERRED]
api/leave.ts → views/production/process/ProcessRouteTab.vue
- `fetchLeaveDetail()` --calls--> `get()` [INFERRED]
api/leave.ts → views/production/process/ProcessRouteTab.vue
- `fetchConsumableList()` --calls--> `get()` [INFERRED]
api/production.ts → views/production/process/ProcessRouteTab.vue
- `fetchReagentList()` --calls--> `get()` [INFERRED]
api/production.ts → views/production/process/ProcessRouteTab.vue
- `fetchAllReagents()` --calls--> `get()` [INFERRED]
api/production.ts → views/production/process/ProcessRouteTab.vue
## Communities
### Community 0 - "Community 0"
Cohesion: 0.01
Nodes (115): fetchAccessLogList(), fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList() (+107 more)
Nodes (114): fetchAccessLogList(), fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList() (+106 more)
### Community 1 - "Community 1"
Cohesion: 0.03
Nodes (80): batchSavePilotCheckup(), deletePilotCheckupActualItem(), fetchCheckupInstitutionList(), fetchCheckupInstitutionPackageDetail(), fetchCheckupInstitutionPackages(), fetchCheckupInstitutionProjects(), fetchPilotCheckupActualItems(), fetchPilotCheckupDetail() (+72 more)
Nodes (82): batchSavePilotCheckup(), deletePilotCheckupActualItem(), fetchCheckupInstitutionList(), fetchCheckupInstitutionPackageDetail(), fetchCheckupInstitutionPackages(), fetchCheckupInstitutionProjects(), fetchPilotCheckupActualItems(), fetchPilotCheckupDetail() (+74 more)
### Community 2 - "Community 2"
Cohesion: 0.03
Nodes (35): fetchGetMenuList(), fetchGetMenuCodes(), clickTab(), handleTouchEnd(), setTransition(), IframeRouteManager, MenuProcessor, RoutePermissionValidator (+27 more)
Nodes (36): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllConsumableStockLogs(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents(), fetchAllReagentStockLogs() (+28 more)
### Community 3 - "Community 3"
Cohesion: 0.03
Nodes (36): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllConsumableStockLogs(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents(), fetchAllReagentStockLogs() (+28 more)
Nodes (34): fetchAttachmentDownloadUrl(), handleReset(), handleSearch(), syncAppliedFilters(), fullFileUrl(), handleDownloadAttachment(), handlePreviewAttachment(), isVideoFile() (+26 more)
### Community 4 - "Community 4"
Cohesion: 0.04
Nodes (56): fetchUploadAttachment(), FireworkSystem, reload(), handleReportUpload(), formatNowDatetime(), handleReportUpload(), formatHandoverUserLabel(), formatNowDatetime() (+48 more)
Nodes (30): clickTab(), handleTouchEnd(), setTransition(), IframeRouteManager, RoutePermissionValidator, RouteRegistry, useCommon(), setupAfterEachGuard() (+22 more)
### Community 5 - "Community 5"
Cohesion: 0.03
Nodes (34): fetchAttachmentDownloadUrl(), fullFileUrl(), handleDownloadAttachment(), handlePreviewAttachment(), isVideoFile(), formatTempEmptyField(), handleReceptionAttachmentDownload(), shouldShowTempEmptyField() (+26 more)
### Community 6 - "Community 6"
Cohesion: 0.04
Nodes (25): fetchOutboundReleaseMaterial(), fetchOutboundReleaseReviewDetail(), fetchOutboundReleaseReviewList(), fetchReleaseReviewList(), submitOutboundReleaseReview(), fetchStorageReleaseReviewDetail(), fetchStorageReleaseReviewList(), submitStorageReleaseReview() (+17 more)
### Community 7 - "Community 7"
Cohesion: 0.05
Nodes (33): handleBlur(), handleFocus(), handleInput(), formatAmount(), formatAmount(), fmtMoney(), auditorDisplay(), dash() (+25 more)
### Community 6 - "Community 6"
Cohesion: 0.06
Nodes (48): fetchUploadAttachment(), reload(), handleReportUpload(), formatNowDatetime(), handleReportUpload(), pad(), handleMenuJump(), openExternalLink() (+40 more)
### Community 8 - "Community 8"
### Community 7 - "Community 7"
Cohesion: 0.06
Nodes (11): fetchGetLatestUpgradeLog(), getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager (+3 more)
### Community 9 - "Community 9"
Cohesion: 0.06
Nodes (33): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), updateInfusionScheduleStatus(), fetchCreateProductionWorkOrder(), fetchProductionWorkOrderDetail(), fetchProductionWorkOrderList() (+25 more)
### Community 10 - "Community 10"
Cohesion: 0.06
Nodes (39): fetchCreateSite(), fetchCreateUser(), fetchDeleteSite(), fetchDeleteUser(), fetchResetPassword(), fetchSiteList(), fetchUpdateSite(), fetchUpdateUser() (+31 more)
### Community 11 - "Community 11"
### Community 8 - "Community 8"
Cohesion: 0.05
Nodes (25): fetchClinicalPaymentDetail(), fetchClinicalPayments(), fetchClinicalReceiptConfirmed(), fetchClinicalReceiptPending(), fetchClinicalRefundConfirmed(), fetchClinicalRefundPending(), fetchOrderAdjusts(), fetchOrderDetail() (+17 more)
### Community 12 - "Community 12"
### Community 9 - "Community 9"
Cohesion: 0.06
Nodes (28): fetchFullmoonTodoList(), fetchUpdateFullmoonTodoInsurance(), fetchCollectionProtocolAuditList(), fetchCollectionProtocolDetail(), fetchCollectionProtocolList(), fetchPaymentDetailList(), fetchReceiptConfirmedList(), fetchReceiptConfirmList() (+20 more)
Nodes (24): fetchGetMenuList(), fetchGetMenuCodes(), fetchGetUserInfo(), fetchLogin(), fetchUpdateProfile(), fetchUploadProfileAvatar(), MenuProcessor, useAppMode() (+16 more)
### Community 10 - "Community 10"
Cohesion: 0.06
Nodes (31): handleBlur(), handleFocus(), handleInput(), fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), formatAmount() (+23 more)
### Community 11 - "Community 11"
Cohesion: 0.06
Nodes (29): fetchFullmoonTodoList(), fetchUpdateFullmoonTodoInsurance(), fetchCollectionProtocolAuditList(), fetchCollectionProtocolDetail(), fetchCollectionProtocolList(), fetchPaymentDetailList(), fetchReceiptConfirmedList(), fetchReceiptConfirmList() (+21 more)
### Community 12 - "Community 12"
Cohesion: 0.07
Nodes (21): auditorDisplay(), dash(), expressName(), formatYm(), formatYmd(), sampleTypeName(), siteName(), unitName() (+13 more)
### Community 13 - "Community 13"
Cohesion: 0.1
Nodes (2): WebSocketClient, TableCache
### Community 14 - "Community 14"
Cohesion: 0.05
Nodes (13): fetchCreateProductionWorkOrder(), fetchProductionWorkOrderCellProductOptions(), fetchProductionWorkOrderDetail(), fetchProductionWorkOrderList(), formatTempEmptyField(), shouldShowTempEmptyField(), submitAdd(), canDeleteWorkOrder() (+5 more)
### Community 15 - "Community 15"
Cohesion: 0.08
Nodes (33): fetchRefreshToken(), toLogin(), base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url() (+25 more)
### Community 15 - "Community 15"
Cohesion: 0.07
Nodes (22): for(), set(), walk(), annotateForkEdges(), chainApiToElements(), walkAncestorBranch(), walkAncestorParents(), if() (+14 more)
### Community 16 - "Community 16"
Cohesion: 0.07
Nodes (13): maskIdCard(), buildBasicExperimentApplicationFields(), buildDefaultApplicationFields(), buildOutboundPilotContextSections(), applicationStatusLabel(), applicationStatusTagType(), infusionMethodLabel(), infusionStatusLabel() (+5 more)
Cohesion: 0.08
Nodes (29): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), updateInfusionScheduleStatus(), handleAdd(), handleDelete(), handleDropdownCommand() (+21 more)
### Community 17 - "Community 17"
Cohesion: 0.07
Nodes (15): fetchEthnicList(), applyDonationBundleToForm(), createEmptyTransport(), ensureEthnicOptionInList(), handleOpenForAdd(), loadEthnicOptions(), normalizeSampleRow(), resetForm() (+7 more)
Nodes (26): fetchCreateSite(), fetchDeleteSite(), fetchSiteList(), fetchUpdateSite(), fetchUserList(), formatHandoverUserLabel(), formatNowDatetime(), fullFileUrl() (+18 more)
### Community 18 - "Community 18"
Cohesion: 0.08
Nodes (20): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), collectIds(), postorder(), preorder(), applyTreeExpand() (+12 more)
Cohesion: 0.07
Nodes (20): for(), set(), walk(), annotateForkEdges(), chainApiToElements(), walkAncestorBranch(), walkAncestorParents(), if() (+12 more)
### Community 19 - "Community 19"
Cohesion: 0.11
Nodes (25): completePilotApplication(), deletePilotApplication(), fetchPilotApplicationList(), startPilotApplication(), voidPilotApplication(), clearTableSelection(), handleBatchComplete(), handleBatchStart() (+17 more)
Cohesion: 0.07
Nodes (13): maskIdCard(), buildBasicExperimentApplicationFields(), buildDefaultApplicationFields(), buildOutboundPilotContextSections(), applicationStatusLabel(), applicationStatusTagType(), infusionMethodLabel(), infusionStatusLabel() (+5 more)
### Community 20 - "Community 20"
Cohesion: 0.12
Nodes (19): fetchGetUserInfo(), fetchLogin(), fetchUpdateProfile(), fetchUploadProfileAvatar(), fetchUserInfo(), buildWebEncryptedLoginBody(), defaultAvatarPathByRole(), hasCustomUserAvatar() (+11 more)
Cohesion: 0.09
Nodes (27): completePilotApplication(), deletePilotApplication(), fetchPilotApplicationList(), startPilotApplication(), voidPilotApplication(), handleBatchComplete(), handleBatchStart(), handleBatchVoid() (+19 more)
### Community 21 - "Community 21"
Cohesion: 0.07
Nodes (15): fetchEthnicList(), applyDonationBundleToForm(), createEmptyTransport(), ensureEthnicOptionInList(), handleOpenForAdd(), loadEthnicOptions(), normalizeSampleRow(), resetForm() (+7 more)
### Community 22 - "Community 22"
Cohesion: 0.12
Nodes (20): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd(), handleBatchDelete() (+12 more)
### Community 23 - "Community 23"
Cohesion: 0.12
Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
### Community 22 - "Community 22"
### Community 24 - "Community 24"
Cohesion: 0.1
Nodes (6): fetchProductionTaskDetail(), fetchProductionTaskList(), fetchUpdateProductionTask(), handleClose(), handleSubmit(), loadTaskDetail()
### Community 23 - "Community 23"
Cohesion: 0.11
Nodes (8): handleReset(), handleSearch(), syncAppliedFilters(), addMaterialRow(), handleReceptionAttachmentDownload(), nextMaterialRowKey(), onRequisitionSelect(), removeMaterialRow()
### Community 24 - "Community 24"
Cohesion: 0.12
Nodes (12): fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail(), fetchMyInstances(), fetchPendingTasks(), fetchProcessDefList() (+4 more)
### Community 25 - "Community 25"
Cohesion: 0.16
Nodes (14): fetchCreateEnvDetectItem(), fetchDeleteEnvDetectItem(), fetchEnvDetectItemDetail(), fetchEnvDetectItemList(), fetchEnvDetectItemsActive(), fetchUpdateEnvDetectItem(), handleAdd(), handleBatchDelete() (+6 more)
### Community 26 - "Community 26"
Cohesion: 0.16
Nodes (10): useTable(), useTableImpl(), getColumnChecks(), useTableColumns(), createErrorHandler(), createSmartDebounce(), defaultResponseAdapter(), extractPagination() (+2 more)
Cohesion: 0.12
Nodes (12): fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail(), fetchMyInstances(), fetchPendingTasks(), fetchProcessDefList() (+4 more)
### Community 27 - "Community 27"
Cohesion: 0.2
Nodes (2): ComponentLoader, RouteTransformer
### Community 28 - "Community 28"
Cohesion: 0.16
Nodes (10): useTable(), useTableImpl(), getColumnChecks(), useTableColumns(), createErrorHandler(), createSmartDebounce(), defaultResponseAdapter(), extractPagination() (+2 more)
### Community 29 - "Community 29"
Cohesion: 0.27
Nodes (14): useChart(), useChartComponent(), useChartOps(), colourBlend(), getCssVar(), getDarkColor(), getLightColor(), handleElementThemeColor() (+6 more)
### Community 29 - "Community 29"
### Community 30 - "Community 30"
Cohesion: 0.14
Nodes (8): useSettingsHandlers(), useSettingsPanel(), useSettingsState(), useCeremony(), useTheme(), themeAnimation(), toggleTheme(), toggleTransition()
### Community 30 - "Community 30"
### Community 31 - "Community 31"
Cohesion: 0.18
Nodes (5): fetchServiceOrderTxnList(), handleReset(), handleSearch(), handleTabChange(), loadData()
### Community 32 - "Community 32"
### Community 33 - "Community 33"
Cohesion: 0.15
Nodes (4): fetchQcMaterialBatchList(), fetchQcMaterialBatchOptions(), fetchQcMaterialList(), fetchQcMaterialOptions()
### Community 33 - "Community 33"
### Community 34 - "Community 34"
Cohesion: 0.17
Nodes (3): fetchResearchDonationAuditList(), fetchResearchDonationDetail(), fetchResearchDonationList()
### Community 34 - "Community 34"
### Community 35 - "Community 35"
Cohesion: 0.18
Nodes (3): fetchMaterialList(), fetchMaterialOptions(), fetchMaterialStockLogList()
### Community 36 - "Community 36"
Cohesion: 0.28
Nodes (1): FireworkSystem
### Community 38 - "Community 38"
Cohesion: 0.22
Nodes (3): fetchMyQcTaskList(), fetchQcTaskDetail(), fetchQcTaskList()
### Community 37 - "Community 37"
### Community 39 - "Community 39"
Cohesion: 0.39
Nodes (6): isIgnorableScriptError(), normalizeErrorMessage(), registerPromiseErrorHandler(), registerResourceErrorHandler(), scriptErrorHandler(), setupErrorHandle()
### Community 38 - "Community 38"
### Community 40 - "Community 40"
Cohesion: 0.36
Nodes (1): TableHeightCalculator
### Community 39 - "Community 39"
### Community 41 - "Community 41"
Cohesion: 0.25
Nodes (3): fetchOutboundQcDetail(), fetchQcWorkOrderDetail(), fetchQcWorkOrderList()
### Community 42 - "Community 42"
Cohesion: 0.33
Nodes (2): logApiCryptoBootInfo(), maskSecret()
### Community 40 - "Community 40"
Cohesion: 0.29
Nodes (2): fetchLeaveDetail(), fetchLeaveList()
### Community 41 - "Community 41"
### Community 43 - "Community 43"
Cohesion: 0.29
Nodes (2): fetchQcMaterialStockCheckDetail(), fetchQcMaterialStockCheckList()
### Community 42 - "Community 42"
### Community 44 - "Community 44"
Cohesion: 0.47
Nodes (3): isPaymentDueWithinWarnWindow(), parsePaymentDueYmd(), startOfLocalDay()
### Community 44 - "Community 44"
### Community 46 - "Community 46"
Cohesion: 0.33
Nodes (2): fetchSampleReceptionCheckItemList(), fetchSampleReceptionCheckItemsActive()
### Community 45 - "Community 45"
Cohesion: 0.33
Nodes (2): fetchQcThirdPartyCompanyList(), fetchQcThirdPartyCompanyOptions()
Nodes (2): fetchQcThirdPartyItemList(), fetchQcThirdPartyItemsActive()
### Community 47 - "Community 47"
Cohesion: 0.33
Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
### Community 49 - "Community 49"
Cohesion: 0.5
Nodes (2): formatProtocolDocDeleteConfirm(), formatProtocolDocSubject()
### Community 48 - "Community 48"
### Community 50 - "Community 50"
Cohesion: 0.5
Nodes (2): focusInput(), handleKeydown()
### Community 50 - "Community 50"
### Community 52 - "Community 52"
Cohesion: 0.67
Nodes (2): if(), logSidebarMenu()
### Community 52 - "Community 52"
### Community 54 - "Community 54"
Cohesion: 0.5
Nodes (1): fetchStorageEntrySmsList()
### Community 53 - "Community 53"
### Community 55 - "Community 55"
Cohesion: 0.5
Nodes (1): fetchSampleSmsNotificationList()
### Community 54 - "Community 54"
### Community 56 - "Community 56"
Cohesion: 0.5
Nodes (1): fetchQcSmsList()
### Community 56 - "Community 56"
### Community 58 - "Community 58"
Cohesion: 1.0
Nodes (2): getSettingDefaults(), resetToDefaults()
### Community 58 - "Community 58"
### Community 60 - "Community 60"
Cohesion: 1.0
Nodes (2): copyTextToClipboard(), copyTextWithFallback()
@@ -296,49 +306,49 @@ Nodes (2): copyTextToClipboard(), copyTextWithFallback()
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 27`** (17 nodes): `ComponentLoader`, `.constructor()`, `.createEmptyComponent()`, `.createErrorComponent()`, `.load()`, `.loadIframe()`, `.loadLayout()`, `.load()`, `RouteTransformer`, `.extractFirstSegment()`, `.handleFirstLevelRoute()`, `.handleIframeRoute()`, `.handleNormalRoute()`, `.isFirstLevelRoute()`, `.transform()`, `ComponentLoader.ts`, `RouteTransformer.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 38`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
- **Thin community `Community 36`** (9 nodes): `FireworkSystem`, `.constructor()`, `.createFirework()`, `.createParticle()`, `.getAvailableParticle()`, `.initializePool()`, `.preloadAllImages()`, `.preloadImage()`, `index.vue`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 39`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
- **Thin community `Community 40`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 40`** (7 nodes): `fetchCreateLeave()`, `fetchDeleteLeave()`, `fetchLeaveDetail()`, `fetchLeaveList()`, `fetchSubmitLeave()`, `fetchUpdateLeave()`, `leave.ts`
- **Thin community `Community 42`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 41`** (7 nodes): `fetchCancelQcMaterialStockCheck()`, `fetchCompleteQcMaterialStockCheck()`, `fetchCreateQcMaterialStockCheck()`, `fetchQcMaterialStockCheckDetail()`, `fetchQcMaterialStockCheckList()`, `fetchSaveQcMaterialStockCheckUnusedLines()`, `qc-material-stock-check.ts`
- **Thin community `Community 43`** (7 nodes): `fetchCancelQcMaterialStockCheck()`, `fetchCompleteQcMaterialStockCheck()`, `fetchCreateQcMaterialStockCheck()`, `fetchQcMaterialStockCheckDetail()`, `fetchQcMaterialStockCheckList()`, `fetchSaveQcMaterialStockCheckUnusedLines()`, `qc-material-stock-check.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 44`** (6 nodes): `fetchCreateSampleReceptionCheckItem()`, `fetchDeleteSampleReceptionCheckItem()`, `fetchSampleReceptionCheckItemList()`, `fetchSampleReceptionCheckItemsActive()`, `fetchUpdateSampleReceptionCheckItem()`, `sample-reception-check-item.ts`
- **Thin community `Community 46`** (6 nodes): `fetchCreateQcThirdPartyItem()`, `fetchDeleteQcThirdPartyItem()`, `fetchQcThirdPartyItemList()`, `fetchQcThirdPartyItemsActive()`, `fetchUpdateQcThirdPartyItem()`, `qc-third-party-item.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 45`** (6 nodes): `fetchCreateQcThirdPartyCompany()`, `fetchDeleteQcThirdPartyCompany()`, `fetchQcThirdPartyCompanyList()`, `fetchQcThirdPartyCompanyOptions()`, `fetchUpdateQcThirdPartyCompany()`, `qc-third-party-company.ts`
- **Thin community `Community 47`** (6 nodes): `fetchCreateQcLabTestItem()`, `fetchDeleteQcLabTestItem()`, `fetchQcLabTestItemList()`, `fetchQcLabTestItemsActive()`, `fetchUpdateQcLabTestItem()`, `qc-lab-test-item.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 47`** (5 nodes): `formatProtocolDocBatchDeleteConfirm()`, `formatProtocolDocDeleteConfirm()`, `formatProtocolDocSubject()`, `getProtocolDocTypeLabel()`, `protocol-doc-label.ts`
- **Thin community `Community 49`** (5 nodes): `formatProtocolDocBatchDeleteConfirm()`, `formatProtocolDocDeleteConfirm()`, `formatProtocolDocSubject()`, `getProtocolDocTypeLabel()`, `protocol-doc-label.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 48`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
- **Thin community `Community 50`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 50`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
- **Thin community `Community 52`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 52`** (4 nodes): `fetchGenerateStorageEntrySms()`, `fetchStorageEntrySmsList()`, `fetchToggleStorageEntrySmsNotified()`, `storage-entry-sms-notification.ts`
- **Thin community `Community 54`** (4 nodes): `fetchGenerateStorageEntrySms()`, `fetchStorageEntrySmsList()`, `fetchToggleStorageEntrySmsNotified()`, `storage-entry-sms-notification.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 53`** (4 nodes): `fetchGenerateSms()`, `fetchSampleSmsNotificationList()`, `fetchToggleSmsNotified()`, `sample-sms-notification.ts`
- **Thin community `Community 55`** (4 nodes): `fetchGenerateSms()`, `fetchSampleSmsNotificationList()`, `fetchToggleSmsNotified()`, `sample-sms-notification.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 54`** (4 nodes): `fetchGenerateQcSms()`, `fetchQcSmsList()`, `fetchToggleQcSmsNotified()`, `qc-sms-notification.ts`
- **Thin community `Community 56`** (4 nodes): `fetchGenerateQcSms()`, `fetchQcSmsList()`, `fetchToggleQcSmsNotified()`, `qc-sms-notification.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 56`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
- **Thin community `Community 58`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 58`** (3 nodes): `copyTextToClipboard()`, `copyTextWithFallback()`, `copy-text.ts`
- **Thin community `Community 60`** (3 nodes): `copyTextToClipboard()`, `copyTextWithFallback()`, `copy-text.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 6`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 22`, `Community 24`, `Community 25`, `Community 30`, `Community 32`, `Community 33`, `Community 34`, `Community 36`, `Community 40`, `Community 41`, `Community 44`, `Community 45`, `Community 52`, `Community 53`, `Community 54`?**
_High betweenness centrality (0.255) - this node is a cross-community bridge._
- **Why does `push()` connect `Community 4` to `Community 0`, `Community 1`, `Community 3`, `Community 5`, `Community 8`, `Community 10`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 23`?**
_High betweenness centrality (0.190) - this node is a cross-community bridge._
- **Why does `fetchNotDeletedRegions()` connect `Community 0` to `Community 4`?**
_High betweenness centrality (0.059) - this node is a cross-community bridge._
- **Are the 218 inferred relationships involving `get()` (e.g. with `collectLeafFacilityNodesInTreeOrder()` and `buildTreePathLabelFromFlat()`) actually correct?**
_`get()` has 218 INFERRED edges - model-reasoned connections that need verification._
- **Are the 42 inferred relationships involving `push()` (e.g. with `collectLeafFacilityNodesInTreeOrder()` and `fetchAllPagedRecords()`) actually correct?**
_`push()` has 42 INFERRED edges - model-reasoned connections that need verification._
- **Are the 15 inferred relationships involving `emit()` (e.g. with `handleSubmit()` and `handleBlur()`) actually correct?**
_`emit()` has 15 INFERRED edges - model-reasoned connections that need verification._
- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 11`, `Community 14`, `Community 16`, `Community 17`, `Community 18`, `Community 20`, `Community 21`, `Community 22`, `Community 24`, `Community 25`, `Community 26`, `Community 31`, `Community 33`, `Community 34`, `Community 35`, `Community 38`, `Community 41`, `Community 43`, `Community 46`, `Community 47`, `Community 54`, `Community 55`, `Community 56`?**
_High betweenness centrality (0.305) - this node is a cross-community bridge._
- **Why does `push()` connect `Community 6` to `Community 0`, `Community 1`, `Community 2`, `Community 3`, `Community 36`, `Community 7`, `Community 11`, `Community 13`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 21`?**
_High betweenness centrality (0.163) - this node is a cross-community bridge._
- **Why does `buildOutboundPilotContextSections()` connect `Community 19` to `Community 6`?**
_High betweenness centrality (0.043) - this node is a cross-community bridge._
- **Are the 220 inferred relationships involving `get()` (e.g. with `collectLeafFacilityNodesInTreeOrder()` and `groupInboundQcWorkOrders()`) actually correct?**
_`get()` has 220 INFERRED edges - model-reasoned connections that need verification._
- **Are the 43 inferred relationships involving `push()` (e.g. with `collectLeafFacilityNodesInTreeOrder()` and `fetchAllPagedRecords()`) actually correct?**
_`push()` has 43 INFERRED edges - model-reasoned connections that need verification._
- **Are the 16 inferred relationships involving `emit()` (e.g. with `handleSubmit()` and `handleBlur()`) actually correct?**
_`emit()` has 16 INFERRED edges - model-reasoned connections that need verification._
- **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.01 - nodes in this community are weakly interconnected._
+4 -4
View File
@@ -57,12 +57,12 @@
</div>
<div id="legend"></div>
</div>
<div id="stats">2079 nodes &middot; 2717 edges &middot; 309 communities</div>
<div id="stats">2103 nodes &middot; 2742 edges &middot; 313 communities</div>
</div>
<script>
const RAW_NODES = [{"id": "app_vue", "label": "App.vue", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "App.vue", "community": 39, "community_name": "Community 39", "source_file": "App.vue", "file_type": "code", "degree": 3}, {"id": "src_app_refreshbuttonautoicons", "label": "refreshButtonAutoIcons()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshButtonAutoIcons()", "community": 39, "community_name": "Community 39", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "src_app_refreshpaneltitleicons", "label": "refreshPanelTitleIcons()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshPanelTitleIcons()", "community": 39, "community_name": "Community 39", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "main_ts", "label": "main.ts", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "main.ts", "community": 39, "community_name": "Community 39", "source_file": "main.ts", "file_type": "code", "degree": 4}, {"id": "src_main_masksecret", "label": "maskSecret()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "maskSecret()", "community": 39, "community_name": "Community 39", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "src_main_logapicryptobootinfo", "label": "logApiCryptoBootInfo()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "logApiCryptoBootInfo()", "community": 39, "community_name": "Community 39", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "env_d_ts", "label": "env.d.ts", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "env.d.ts", "community": 88, "community_name": "Community 88", "source_file": "env.d.ts", "file_type": "code", "degree": 0}, {"id": "shims_loose_d_ts", "label": "shims-loose.d.ts", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "shims-loose.d.ts", "community": 89, "community_name": "Community 89", "source_file": "shims-loose.d.ts", "file_type": "code", "degree": 0}, {"id": "directives_index_ts", "label": "index.ts", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "index.ts", "community": 21, "community_name": "Community 21", "source_file": "directives/index.ts", "file_type": "code", "degree": 1}, {"id": "directives_index_setupglobdirectives", "label": "setupGlobDirectives()", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.5, "font": {"size": 0, "color": "#ffffff"}, "title": "setupGlobDirectives()", "community": 21, "community_name": "Community 21", "source_file": "directives/index.ts", "file_type": "code", "degree": 5}, {"id": "directives_core_roles_ts", "label": "roles.ts", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "roles.ts", "community": 21, "community_name": "Community 21", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 3}, {"id": "core_roles_checkrolepermission", "label": "checkRolePermission()", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "checkRolePermission()", "community": 21, "community_name": "Community 21", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_removeelement", "label": "removeElement()", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "removeElement()", "community": 21, "community_name": "Community 21", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_setuprolesdirective", "laLine truncated
const RAW_EDGES = [{"from": "app_vue", "to": "src_app_refreshbuttonautoicons", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_vue", "to": "src_app_refreshpaneltitleicons", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_vue", "to": "main_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "utils_sys_console_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "src_main_masksecret", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "src_main_logapicryptobootinfo", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_main_masksecret", "to": "src_main_logapicryptobootinfo", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_index_ts", "to": "directives_index_setupglobdirectives", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_index_setupglobdirectives", "to": "core_auth_setupauthdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "core_roles_setuprolesdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "business_highlight_setuphighlightdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "business_ripple_setuprippledirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_core_roles_ts", "to": "core_roles_checkrolepermission", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_roles_ts", "to": "core_roles_removeelement", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_roles_ts", "to": "core_roles_setuprolesdirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_roles_checkrolepermission", "to": "core_roles_removeelement", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_checkauthpermission", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_removeelement", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_setupauthdirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_checkauthpermission", "to": "core_auth_removeelement", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_ripple_ts", "to": "business_ripple_setuprippledirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_highlightcode", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_insertlinenumbers", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_addcopybutton", "label": "contains", "title": "contains [EXTRACTED]", "dLine truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 342}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 109}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 96}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 81}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 80}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 74}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 63}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 59}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 55}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 49}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 48}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 48}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 45}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 42}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 41}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 37}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 36}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 34}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 34}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 30}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 27}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 22}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 22}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 22}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 19}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 19}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 17}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 17}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 16}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 15}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 14}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 13}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 13}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 12}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 11}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 9}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 9}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 8}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 8}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 7}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 7}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 7}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 6}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 6}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 6}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 6}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 6}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 5}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 5}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 5}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 4}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 4}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 4}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 4}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 4}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 4}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 3}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 3}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 3}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 3}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 3}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 3}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 3}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 3}, {"cid": 64, "color": "#59A14F", "label": "Community 64", "count": 2}, {"cid": 65, "color": "#EDC948", "label": "Community 65", "count": 2}, {"cid": 66, "color": "#B07AA1", "label": "Community 66", "count": 2}, {"cid": 67, "color": "#FF9DA7", "label": "Community 67", "count": 2}, {"cid": 68, "color": "#9C755F", "label": "Community 68", "count": 2}, {"cid": 69, "color": "#BAB0AC", "label": "Community 69", "count": 2}, {"cid": 70, "color": "#4E79A7", "label": "Community 70", "count": Line truncated
const RAW_NODES = [{"id": "app_vue", "label": "App.vue", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "App.vue", "community": 42, "community_name": "Community 42", "source_file": "App.vue", "file_type": "code", "degree": 3}, {"id": "src_app_refreshbuttonautoicons", "label": "refreshButtonAutoIcons()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshButtonAutoIcons()", "community": 42, "community_name": "Community 42", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "src_app_refreshpaneltitleicons", "label": "refreshPanelTitleIcons()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshPanelTitleIcons()", "community": 42, "community_name": "Community 42", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "main_ts", "label": "main.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "main.ts", "community": 42, "community_name": "Community 42", "source_file": "main.ts", "file_type": "code", "degree": 4}, {"id": "src_main_masksecret", "label": "maskSecret()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "maskSecret()", "community": 42, "community_name": "Community 42", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "src_main_logapicryptobootinfo", "label": "logApiCryptoBootInfo()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "logApiCryptoBootInfo()", "community": 42, "community_name": "Community 42", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "env_d_ts", "label": "env.d.ts", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "env.d.ts", "community": 90, "community_name": "Community 90", "source_file": "env.d.ts", "file_type": "code", "degree": 0}, {"id": "shims_loose_d_ts", "label": "shims-loose.d.ts", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "shims-loose.d.ts", "community": 91, "community_name": "Community 91", "source_file": "shims-loose.d.ts", "file_type": "code", "degree": 0}, {"id": "directives_index_ts", "label": "index.ts", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "index.ts", "community": 23, "community_name": "Community 23", "source_file": "directives/index.ts", "file_type": "code", "degree": 1}, {"id": "directives_index_setupglobdirectives", "label": "setupGlobDirectives()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.5, "font": {"size": 0, "color": "#ffffff"}, "title": "setupGlobDirectives()", "community": 23, "community_name": "Community 23", "source_file": "directives/index.ts", "file_type": "code", "degree": 5}, {"id": "directives_core_roles_ts", "label": "roles.ts", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "roles.ts", "community": 23, "community_name": "Community 23", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 3}, {"id": "core_roles_checkrolepermission", "label": "checkRolePermission()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "checkRolePermission()", "community": 23, "community_name": "Community 23", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_removeelement", "label": "removeElement()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "removeElement()", "community": 23, "community_name": "Community 23", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_setuprolesdirective", "laLine truncated
const RAW_EDGES = [{"from": "app_vue", "to": "src_app_refreshbuttonautoicons", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_vue", "to": "src_app_refreshpaneltitleicons", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_vue", "to": "main_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "utils_sys_console_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "src_main_masksecret", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "src_main_logapicryptobootinfo", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_main_masksecret", "to": "src_main_logapicryptobootinfo", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_index_ts", "to": "directives_index_setupglobdirectives", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_index_setupglobdirectives", "to": "core_auth_setupauthdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "core_roles_setuprolesdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "business_highlight_setuphighlightdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "business_ripple_setuprippledirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_core_roles_ts", "to": "core_roles_checkrolepermission", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_roles_ts", "to": "core_roles_removeelement", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_roles_ts", "to": "core_roles_setuprolesdirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_roles_checkrolepermission", "to": "core_roles_removeelement", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_checkauthpermission", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_removeelement", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_setupauthdirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_checkauthpermission", "to": "core_auth_removeelement", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_ripple_ts", "to": "business_ripple_setuprippledirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_highlightcode", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_insertlinenumbers", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_addcopybutton", "label": "contains", "title": "contains [EXTRACTED]", "dLine truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 341}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 110}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 81}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 79}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 76}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 64}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 60}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 55}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 48}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 48}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 46}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 46}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 42}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 42}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 42}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 41}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 40}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 38}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 37}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 36}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 34}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 34}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 24}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 22}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 22}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 19}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 19}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 17}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 17}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 16}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 15}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 14}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 13}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 13}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 12}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 11}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 9}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 9}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 9}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 8}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 8}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 8}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 7}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 7}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 6}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 6}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 6}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 6}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 6}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 5}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 5}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 5}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 4}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 4}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 4}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 4}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 4}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 4}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 3}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 3}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 3}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 3}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 3}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 3}, {"cid": 64, "color": "#59A14F", "label": "Community 64", "count": 3}, {"cid": 65, "color": "#EDC948", "label": "Community 65", "count": 3}, {"cid": 66, "color": "#B07AA1", "label": "Community 66", "count": 2}, {"cid": 67, "color": "#FF9DA7", "label": "Community 67", "count": 2}, {"cid": 68, "color": "#9C755F", "label": "Community 68", "count": 2}, {"cid": 69, "color": "#BAB0AC", "label": "Community 69", "count": 2}, {"cid": 70, "color": "#4E79A7", "label": "Community 70", "count":Line truncated
// HTML-escape helper — prevents XSS when injecting graph data into innerHTML
function esc(s) {
File diff suppressed because it is too large. Load diff
+17 -10
View File
@@ -17,8 +17,8 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/common/index.ts": 1778582068.0254738,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/directive/directive.d.ts": 1778582068.026978,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/api/api.d.ts": 1778582068.0246184,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1781235935.3278806,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1781235905.915799,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1781246166.8389935,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1781246095.71424,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/store/index.ts": 1778582068.0293276,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/router/index.ts": 1780480414.9574106,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/locales/index.ts": 1778582068.004279,
@@ -50,6 +50,7 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/fetch-paged-list.ts": 1778775532.895782,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/sort-order.ts": 1779790341.2952042,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/outbound-qc-pilot-view-sections.ts": 1781080334.6168308,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts": 1781245724.945728,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/copy-text.ts": 1781077714.2912376,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/tree-path.ts": 1779790341.776808,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/mask-sensitive.ts": 1778984825.4182227,
@@ -220,7 +221,7 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/env-detect-record.ts": 1781145722.88074,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/storage-release-review.ts": 1780478965.1159081,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/workflow.ts": 1779790323.5709383,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts": 1779924334.417812,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts": 1781245385.307889,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task-result.ts": 1781232714.5618048,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/protocol-sample-collection.ts": 1780457181.854043,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-third-party-company.ts": 1779021425.3773527,
@@ -247,7 +248,7 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/material.ts": 1779283387.4196396,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/sample-reception.ts": 1780462889.9246686,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/auth.ts": 1780539439.6854532,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts": 1781232710.8590982,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts": 1781248276.4110606,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/auth/login/index.vue": 1780480414.9591243,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/refund-confirm/index.vue": 1779866933.9349332,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/cell-storage/protocol/index.vue": 1781063349.3629117,
@@ -328,17 +329,19 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/sop/index.vue": 1779882001.8321233,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue": 1779925496.7869844,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/reagent/index.vue": 1779863928.5798419,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1781080334.6168587,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1781245471.5188477,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useTaskStepEditor.ts": 1780459274.6793678,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/StepDetailPanel.vue": 1780306770.0831616,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/CompleteProductionTaskDialog.vue": 1779790359.5464847,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue": 1780459293.3141825,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchQcWorkOrderDialog.vue": 1781245347.4836073,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1780459276.445585,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/EditTaskInfoDialog.vue": 1779925648.9018784,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputList.vue": 1779967896.0077794,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchTaskDialog.vue": 1781063324.0294309,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/WorkOrderTaskSubList.vue": 1779967394.775425,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/WorkOrderTaskSubList.vue": 1781245403.0507572,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskOutputProductDialog.vue": 1779925660.055737,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue": 1781245472.0641115,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/consumable-pack/index.vue": 1779863746.111673,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/consumable/index.vue": 1779863845.5777776,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/consumable/ConsumablePackTab.vue": 1779863731.2002044,
@@ -348,10 +351,14 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/index.vue": 1779864070.4019275,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/OutputTypeTab.vue": 1779864078.964701,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/index/index.vue": 1779525781.6949234,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue": 1781235485.724401,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue": 1781236360.3712525,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue": 1781195540.3765936,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue": 1781236324.7682652,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue": 1781248364.9092808,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue": 1781248310.590171,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue": 1781247653.7613583,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue": 1781248062.534885,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue": 1781245816.5224924,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue": 1781247706.6829572,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue": 1781245766.0003755,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderProtocolPaymentPanel.vue": 1781248366.9077792,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception-check-item/index.vue": 1779857055.2939715,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/lab-test-item/index.vue": 1779864504.1085482,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/lab-test-item/ThirdPartyCompanyTab.vue": 1779864511.579237,
@@ -22,8 +22,14 @@ export interface ProductionWorkOrderItem {
workOrderStatus: string
createTime?: string
remark?: string
labStorageCode?: string
protocolStatus?: string
bizType?: string
bizTypeLabel?: string
/** 出库工单:关联申请单号 */
applicationNo?: string
applicationName?: string
channelName?: string
/** 出库工单:患者/用户姓名 */
patientName?: string
/** 出库工单:当前为第几次输注计划 */
@@ -127,3 +133,31 @@ export function fetchUpdateProductionWorkOrder(id: string, data: { remark?: stri
export function fetchDeleteProductionWorkOrder(id: string) {
return request.del({ url: `/web/admin/production-work-orders/${id}` })
}
export interface ProductionCellProductOption {
id: string
cellProductId?: string
productCode?: string
productName?: string
productTypeName?: string
passage?: string
storageSlotName?: string
taskId?: string
taskNo?: string
}
export function fetchProductionWorkOrderCellProductOptions(workOrderId: string) {
return request.get<ProductionCellProductOption[]>({
url: `/web/admin/production-work-orders/${workOrderId}/cell-product-options`
})
}
export function fetchCreateQcFromProduction(
workOrderId: string,
data: { cellProductId: string; productionTaskId?: string; remark?: string }
) {
return request.post({
url: `/web/admin/production-work-orders/${workOrderId}/qc-work-orders`,
data
})
}
+43
View File
@@ -15,6 +15,38 @@ export type SampleReceptionCheckResult = {
checkItemId?: string
}
export type QcProtocolPaymentDetailSummary = {
installmentNo?: number | null
installmentLabel?: string
installmentDueDate?: string
dueAmount?: number | null
transferAmount?: number | null
payStatus?: string
payStatusLabel?: string
}
export type QcProtocolPaymentSummary = {
payMode?: string
payModeLabel?: string
contractAmount?: number | null
receivedAmount?: number | null
pendingAmount?: number | null
statusHint?: string
overallPayStatus?: string
overallPayStatusLabel?: string
overallPayStatusTagType?: string
installmentCount?: number | null
paymentDetails?: QcProtocolPaymentDetailSummary[]
}
export type QcBasicExperimentFeeSummary = {
saleAmount?: number | null
paymentAmount?: number | null
transferAmount?: number | null
payStatus?: string
payStatusLabel?: string
}
export type QcWorkOrderReceptionDetail = {
protocolNo?: string
sampleCode?: string
@@ -93,6 +125,15 @@ export type QcWorkOrder = {
/** 出库工单:输注计划总次数 */
totalCourseCount?: number | null
infusionTime?: string
triggerSource?: string
triggerSourceLabel?: string
productionWorkOrderId?: string
productionWorkOrderNo?: string
productionTaskId?: string
protocolStatus?: string
protocolStatusLabel?: string
paymentSummary?: QcProtocolPaymentSummary | null
feeSummary?: QcBasicExperimentFeeSummary | null
}
export type QcWorkOrderDetail = QcWorkOrder & {
@@ -139,6 +180,7 @@ export function fetchQcWorkOrderList(params?: {
createTimeEnd?: string
infusionTimeStart?: string
infusionTimeEnd?: string
triggerSource?: string
}) {
return request.get<PageList<QcWorkOrder>>({
url: '/web/admin/qc-work-orders',
@@ -249,6 +291,7 @@ export type OutboundQcDetailProduct = {
export type OutboundQcDetail = {
workOrder: QcWorkOrderDetail
feeSummary?: QcBasicExperimentFeeSummary | null
application?: OutboundQcDetailApplication
patient?: OutboundQcDetailPatient
infusionPlan?: OutboundQcDetailInfusionPlan
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "label": "qc-work-order.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L1"}, {"id": "api_qc_work_order_fetchqcworkorderlist", "label": "fetchQcWorkOrderList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L160"}, {"id": "api_qc_work_order_fetchqcworkorderdetail", "label": "fetchQcWorkOrderDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L179"}, {"id": "api_qc_work_order_fetchconfigureqcworkorder", "label": "fetchConfigureQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L185"}, {"id": "api_qc_work_order_fetchcreateqcworkorder", "label": "fetchCreateQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L202"}, {"id": "api_qc_work_order_fetchupdateqcworkorder", "label": "fetchUpdateQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L217"}, {"id": "api_qc_work_order_fetchdeleteqcworkorder", "label": "fetchDeleteQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L227"}, {"id": "api_qc_work_order_fetchoutboundqcdetail", "label": "fetchOutboundQcDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L291"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchqcworkorderlist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L160", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchqcworkorderdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L179", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchconfigureqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L185", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchcreateqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L202", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchupdateqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L217", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchdeleteqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L227", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchoutboundqcdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L291", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_qc_work_order_fetchqcworkorderlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L173"}, {"caller_nid": "api_qc_work_order_fetchqcworkorderdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L180"}, {"caller_nid": "api_qc_work_order_fetchconfigureqcworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L196"}, {"caller_nid": "api_qc_work_order_fetchcreateqcworkoLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1"}, {"id": "qc_work_order_index_protocolstatuslabelbysource", "label": "protocolStatusLabelBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L511"}, {"id": "qc_work_order_index_protocolstatustagtypebysource", "label": "protocolStatusTagTypeBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L516"}, {"id": "qc_work_order_index_handlereceptionattachmentpreview", "label": "handleReceptionAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L972"}, {"id": "qc_work_order_index_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L977"}, {"id": "qc_work_order_index_taskattachmentfullurl", "label": "taskAttachmentFullUrl()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L981"}, {"id": "qc_work_order_index_handletaskattachmentpreview", "label": "handleTaskAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L988"}, {"id": "qc_work_order_index_handletaskattachmentdownload", "label": "handleTaskAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L993"}, {"id": "qc_work_order_index_formatconfiguredlabitems", "label": "formatConfiguredLabItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1009"}, {"id": "qc_work_order_index_formatconfiguredthirditems", "label": "formatConfiguredThirdItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1015"}, {"id": "qc_work_order_index_buildtasktypesections", "label": "buildTaskTypeSections()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1026"}, {"id": "qc_work_order_index_buildconfiguredonlysection", "label": "buildConfiguredOnlySection()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1115"}, {"id": "qc_work_order_index_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1133"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L459", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "vue_router", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L460", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L461", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L462", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_lab_test_item", "Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersamplegroup_vue", "label": "QcWorkOrderSampleGroup.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkorderfeesummarybar_vue", "label": "QcWorkOrderFeeSummaryBar.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkorderfeesummarybar_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue", "source_location": "L63", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkorderfeesummarybar_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_money_format", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue", "source_location": "L67", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkorderfeesummarybar_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_status_tag_registry", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue", "source_location": "L68", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkorderfeesummarybar_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_status_tag_registry", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderFeeSummaryBar.vue", "source_location": "L69", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_productionworkordercardlist_vue", "label": "ProductionWorkOrderCardList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L1"}, {"id": "production_work_order_productionworkordercardlist_protocolstatuslabelbysource", "label": "protocolStatusLabelBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L280"}, {"id": "production_work_order_productionworkordercardlist_protocolstatustagtypebysource", "label": "protocolStatusTagTypeBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L284"}, {"id": "production_work_order_productionworkordercardlist_shouldshowtempemptyfield", "label": "shouldShowTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L288"}, {"id": "production_work_order_productionworkordercardlist_formattempemptyfield", "label": "formatTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L292"}, {"id": "production_work_order_productionworkordercardlist_formatoutboundschedulesortno", "label": "formatOutboundScheduleSortNo()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L296"}, {"id": "production_work_order_productionworkordercardlist_formatoutboundscheduletotalcount", "label": "formatOutboundScheduleTotalCount()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L302"}, {"id": "production_work_order_productionworkordercardlist_productionwostatustag", "label": "productionWoStatusTag()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L322"}, {"id": "production_work_order_productionworkordercardlist_handlecardselect", "label": "handleCardSelect()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L332"}, {"id": "production_work_order_productionworkordercardlist_cardindex", "label": "cardIndex()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L336"}, {"id": "production_work_order_productionworkordercardlist_isselected", "label": "isSelected()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L340"}, {"id": "production_work_order_productionworkordercardlist_candispatchtask", "label": "canDispatchTask()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L344"}, {"id": "production_work_order_productionworkordercardlist_candispatchqc", "label": "canDispatchQc()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L348"}, {"id": "production_work_order_productionworkordercardlist_dispatchqctooltip", "label": "dispatchQcTooltip()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L354"}, {"id": "production_work_order_productionworkordercardlist_cancomplete", "label": "canComplete()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L361"}, {"id": "production_work_order_productionworkordercardlist_candeleteworkorder", "label": "canDeleteWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderCardList.vue", "source_location": "L365"}, {"id": "production_work_order_productionworkordercardlist_hasmoreactions", "label": "hasMoreActions()", "file_type": "code", "source_file":Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_outboundqcviewdialog_vue", "label": "OutboundQcViewDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L1"}, {"id": "qc_work_order_outboundqcviewdialog_handledownload", "label": "handleDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L296"}, {"id": "qc_work_order_outboundqcviewdialog_taskattachmentslotname", "label": "taskAttachmentSlotName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L309"}, {"id": "qc_work_order_outboundqcviewdialog_taskmaterialslotname", "label": "taskMaterialSlotName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L313"}, {"id": "qc_work_order_outboundqcviewdialog_handletaskpreview", "label": "handleTaskPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L317"}, {"id": "qc_work_order_outboundqcviewdialog_handletaskdownload", "label": "handleTaskDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L322"}, {"id": "qc_work_order_outboundqcviewdialog_conclusiontagtype", "label": "conclusionTagType()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L328"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_outboundqcviewdialog_vue", "target": "qc_work_order_outboundqcviewdialog_handledownload", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L296", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_outboundqcviewdialog_vue", "target": "qc_work_order_outboundqcviewdialog_taskattachmentslotname", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L309", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_outboundqcviewdialog_vue", "target": "qc_work_order_outboundqcviewdialog_taskmaterialslotname", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L313", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_outboundqcviewdialog_vue", "target": "qc_work_order_outboundqcviewdialog_handletaskpreview", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L317", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_outboundqcviewdialog_vue", "target": "qc_work_order_outboundqcviewdialog_handletaskdownload", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L322", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_outboundqcviewdialog_vue", "target": "qc_work_order_outboundqcviewdialog_conclusiontagtype", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L328", "weight": 1.0}], "raw_calls": [{"caller_nid": "qc_work_order_outboundqcviewdialog_handledownload", "callee": "downloadAttachmentFile", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_location": "L297"}, {"caller_nid": "qc_work_order_outboundqcviewdialog_handletaskpreview", "callee": "fullUrl", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/OutboundQcViewDialog.vue", "source_locatLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "label": "QcWorkOrderSingleCard.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L1"}, {"id": "qc_work_order_qcworkordersinglecard_protocolstatuslabelbysource", "label": "protocolStatusLabelBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L311"}, {"id": "qc_work_order_qcworkordersinglecard_protocolstatustagtypebysource", "label": "protocolStatusTagTypeBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L315"}, {"id": "qc_work_order_qcworkordersinglecard_shouldshowtempemptyfield", "label": "shouldShowTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L319"}, {"id": "qc_work_order_qcworkordersinglecard_formattempemptyfield", "label": "formatTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L323"}, {"id": "qc_work_order_qcworkordersinglecard_formattempcellproductfield", "label": "formatTempCellProductField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L327"}, {"id": "qc_work_order_qcworkordersinglecard_formatoutboundschedulesortno", "label": "formatOutboundScheduleSortNo()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L332"}, {"id": "qc_work_order_qcworkordersinglecard_formatoutboundscheduletotalcount", "label": "formatOutboundScheduleTotalCount()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L338"}, {"id": "qc_work_order_qcworkordersinglecard_formatdetectmethods", "label": "formatDetectMethods()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L344"}, {"id": "qc_work_order_qcworkordersinglecard_formatqctarget", "label": "formatQcTarget()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L351"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_protocolstatuslabelbysource", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L311", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_protocolstatustagtypebysource", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L315", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_shouldshowtempemptyfield", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L319", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_formattempemptyfield", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L323", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_formattempcellproductfield", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspectioLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "label": "qc-work-order.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L1"}, {"id": "api_qc_work_order_fetchqcworkorderlist", "label": "fetchQcWorkOrderList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L138"}, {"id": "api_qc_work_order_fetchqcworkorderdetail", "label": "fetchQcWorkOrderDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L157"}, {"id": "api_qc_work_order_fetchconfigureqcworkorder", "label": "fetchConfigureQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L163"}, {"id": "api_qc_work_order_fetchcreateqcworkorder", "label": "fetchCreateQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L180"}, {"id": "api_qc_work_order_fetchupdateqcworkorder", "label": "fetchUpdateQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L195"}, {"id": "api_qc_work_order_fetchdeleteqcworkorder", "label": "fetchDeleteQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L205"}, {"id": "api_qc_work_order_fetchoutboundqcdetail", "label": "fetchOutboundQcDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L268"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchqcworkorderlist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L138", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchqcworkorderdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L157", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchconfigureqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L163", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchcreateqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L180", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchupdateqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L195", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchdeleteqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L205", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchoutboundqcdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L268", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_qc_work_order_fetchqcworkorderlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L151"}, {"caller_nid": "api_qc_work_order_fetchqcworkorderdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L158"}, {"caller_nid": "api_qc_work_order_fetchconfigureqcworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L174"}, {"caller_nid": "api_qc_work_order_fetchcreateqcworkoLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_work_order_groups_ts", "label": "qc-work-order-groups.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L1"}, {"id": "utils_qc_work_order_groups_triggersortweight", "label": "triggerSortWeight()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L9"}, {"id": "utils_qc_work_order_groups_sortworkordersingroup", "label": "sortWorkOrdersInGroup()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L13"}, {"id": "utils_qc_work_order_groups_groupinboundqcworkorders", "label": "groupInboundQcWorkOrders()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L22"}, {"id": "utils_qc_work_order_groups_triggersourcetablabel", "label": "triggerSourceTabLabel()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L40"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_work_order_groups_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_work_order_groups_ts", "target": "utils_qc_work_order_groups_triggersortweight", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L9", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_work_order_groups_ts", "target": "utils_qc_work_order_groups_sortworkordersingroup", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L13", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_work_order_groups_ts", "target": "utils_qc_work_order_groups_groupinboundqcworkorders", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L22", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_work_order_groups_ts", "target": "utils_qc_work_order_groups_triggersourcetablabel", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L40", "weight": 1.0}], "raw_calls": [{"caller_nid": "utils_qc_work_order_groups_sortworkordersingroup", "callee": "sort", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L14"}, {"caller_nid": "utils_qc_work_order_groups_groupinboundqcworkorders", "callee": "trim", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L25"}, {"caller_nid": "utils_qc_work_order_groups_groupinboundqcworkorders", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L26"}, {"caller_nid": "utils_qc_work_order_groups_groupinboundqcworkorders", "callee": "push", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L27"}, {"caller_nid": "utils_qc_work_order_groups_groupinboundqcworkorders", "callee": "set", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L28"}, {"caller_nid": "utils_qc_work_order_groups_groupinboundqcworkorders", "callee": "map", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L30"}, {"caller_nid": "utils_qc_work_order_groups_groupinboundqcworkorders", "callee": "from", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L30"}, {"caller_nid": "utils_qc_work_order_groups_groupinboundqcworkorders", "callee": "entries", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/utils/qc-work-order-groups.ts", "source_location": "L30"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordertasksublist_vue", "label": "QcWorkOrderTaskSubList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L1"}, {"id": "qc_work_order_qcworkordertasksublist_iscurrentuserassignee", "label": "isCurrentUserAssignee()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L708"}, {"id": "qc_work_order_qcworkordertasksublist_nextmaterialrowkey", "label": "nextMaterialRowKey()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L736"}, {"id": "qc_work_order_qcworkordertasksublist_onrequisitionselect", "label": "onRequisitionSelect()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L900"}, {"id": "qc_work_order_qcworkordertasksublist_addmaterialrow", "label": "addMaterialRow()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L928"}, {"id": "qc_work_order_qcworkordertasksublist_removematerialrow", "label": "removeMaterialRow()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L942"}, {"id": "qc_work_order_qcworkordertasksublist_materialrowsfromapi", "label": "materialRowsFromApi()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L946"}, {"id": "qc_work_order_qcworkordertasksublist_enrichmaterialusagerows", "label": "enrichMaterialUsageRows()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L966"}, {"id": "qc_work_order_qcworkordertasksublist_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L1065"}, {"id": "qc_work_order_qcworkordertasksublist_normalizeresultparams", "label": "normalizeResultParams()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L1069"}, {"id": "qc_work_order_qcworkordertasksublist_toresultnumber", "label": "toResultNumber()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L1077"}, {"id": "qc_work_order_qcworkordertasksublist_setresultnumber", "label": "setResultNumber()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L1083"}, {"id": "qc_work_order_qcworkordertasksublist_hasresultvalue", "label": "hasResultValue()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L1087"}, {"id": "qc_work_order_qcworkordertasksublist_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L1091"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordertasksublist_vue", "target": "qc_work_order_qcworkordertasksublist_iscurrentuserassignee", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L708", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordertasksublist_vue", "target": "qc_work_order_qcworkordertasksublist_nextmaterialrowkey", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L736", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordertasksublist_vue", "target": "qc_work_orderLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "label": "QcWorkOrderSingleCard.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersamplegroup_vue", "label": "QcWorkOrderSampleGroup.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkorderprotocolpaymentpanel_vue", "label": "QcWorkOrderProtocolPaymentPanel.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderProtocolPaymentPanel.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_workordertasksublist_vue", "label": "WorkOrderTaskSubList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/WorkOrderTaskSubList.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1"}, {"id": "qc_work_order_index_protocolstatuslabelbysource", "label": "protocolStatusLabelBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L512"}, {"id": "qc_work_order_index_protocolstatustagtypebysource", "label": "protocolStatusTagTypeBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L517"}, {"id": "qc_work_order_index_handlereceptionattachmentpreview", "label": "handleReceptionAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L936"}, {"id": "qc_work_order_index_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L941"}, {"id": "qc_work_order_index_taskattachmentfullurl", "label": "taskAttachmentFullUrl()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L945"}, {"id": "qc_work_order_index_handletaskattachmentpreview", "label": "handleTaskAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L952"}, {"id": "qc_work_order_index_handletaskattachmentdownload", "label": "handleTaskAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L957"}, {"id": "qc_work_order_index_formatconfiguredlabitems", "label": "formatConfiguredLabItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L973"}, {"id": "qc_work_order_index_formatconfiguredthirditems", "label": "formatConfiguredThirdItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L979"}, {"id": "qc_work_order_index_buildtasktypesections", "label": "buildTaskTypeSections()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L990"}, {"id": "qc_work_order_index_buildconfiguredonlysection", "label": "buildConfiguredOnlySection()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1079"}, {"id": "qc_work_order_index_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1097"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L462", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "vue_router", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L463", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L464", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L465", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_lab_test_item", "relLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1"}, {"id": "qc_work_order_index_protocolstatuslabelbysource", "label": "protocolStatusLabelBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L508"}, {"id": "qc_work_order_index_protocolstatustagtypebysource", "label": "protocolStatusTagTypeBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L513"}, {"id": "qc_work_order_index_handlereceptionattachmentpreview", "label": "handleReceptionAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L931"}, {"id": "qc_work_order_index_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L936"}, {"id": "qc_work_order_index_taskattachmentfullurl", "label": "taskAttachmentFullUrl()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L940"}, {"id": "qc_work_order_index_handletaskattachmentpreview", "label": "handleTaskAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L947"}, {"id": "qc_work_order_index_handletaskattachmentdownload", "label": "handleTaskAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L952"}, {"id": "qc_work_order_index_formatconfiguredlabitems", "label": "formatConfiguredLabItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L968"}, {"id": "qc_work_order_index_formatconfiguredthirditems", "label": "formatConfiguredThirdItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L974"}, {"id": "qc_work_order_index_buildtasktypesections", "label": "buildTaskTypeSections()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L985"}, {"id": "qc_work_order_index_buildconfiguredonlysection", "label": "buildConfiguredOnlySection()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1074"}, {"id": "qc_work_order_index_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1092"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L458", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "vue_router", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L459", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L460", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L461", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_lab_test_item", "relLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1"}, {"id": "qc_work_order_index_protocolstatuslabelbysource", "label": "protocolStatusLabelBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L496"}, {"id": "qc_work_order_index_protocolstatustagtypebysource", "label": "protocolStatusTagTypeBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L501"}, {"id": "qc_work_order_index_handlereceptionattachmentpreview", "label": "handleReceptionAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L914"}, {"id": "qc_work_order_index_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L919"}, {"id": "qc_work_order_index_taskattachmentfullurl", "label": "taskAttachmentFullUrl()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L923"}, {"id": "qc_work_order_index_handletaskattachmentpreview", "label": "handleTaskAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L930"}, {"id": "qc_work_order_index_handletaskattachmentdownload", "label": "handleTaskAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L935"}, {"id": "qc_work_order_index_formatconfiguredlabitems", "label": "formatConfiguredLabItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L951"}, {"id": "qc_work_order_index_formatconfiguredthirditems", "label": "formatConfiguredThirdItems()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L957"}, {"id": "qc_work_order_index_buildtasktypesections", "label": "buildTaskTypeSections()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L968"}, {"id": "qc_work_order_index_buildconfiguredonlysection", "label": "buildConfiguredOnlySection()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1057"}, {"id": "qc_work_order_index_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L1075"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L447", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L448", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L449", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_lab_test_item", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue", "source_location": "L457", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_index_vue", "target": "users_wuzilong_woLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_dispatchqcworkorderdialog_vue", "label": "DispatchQcWorkOrderDialog.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchQcWorkOrderDialog.vue", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_dispatchqcworkorderdialog_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchQcWorkOrderDialog.vue", "source_location": "L79", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_dispatchqcworkorderdialog_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchQcWorkOrderDialog.vue", "source_location": "L80", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_dispatchqcworkorderdialog_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchQcWorkOrderDialog.vue", "source_location": "L81", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_dispatchqcworkorderdialog_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchQcWorkOrderDialog.vue", "source_location": "L82", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordercardlist_vue", "label": "QcWorkOrderCardList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L1"}, {"id": "qc_work_order_qcworkordercardlist_protocolstatustagtypebysource", "label": "protocolStatusTagTypeBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L281"}, {"id": "qc_work_order_qcworkordercardlist_shouldshowtempemptyfield", "label": "shouldShowTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L285"}, {"id": "qc_work_order_qcworkordercardlist_formattempemptyfield", "label": "formatTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L289"}, {"id": "qc_work_order_qcworkordercardlist_formattempcellproductfield", "label": "formatTempCellProductField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L293"}, {"id": "qc_work_order_qcworkordercardlist_formatoutboundschedulesortno", "label": "formatOutboundScheduleSortNo()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L298"}, {"id": "qc_work_order_qcworkordercardlist_formatoutboundscheduletotalcount", "label": "formatOutboundScheduleTotalCount()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L304"}, {"id": "qc_work_order_qcworkordercardlist_formatdetectmethods", "label": "formatDetectMethods()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L310"}, {"id": "qc_work_order_qcworkordercardlist_workorderstatustagtype", "label": "workOrderStatusTagType()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L317"}, {"id": "qc_work_order_qcworkordercardlist_cardindex", "label": "cardIndex()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L324"}, {"id": "qc_work_order_qcworkordercardlist_canconfigureworkorder", "label": "canConfigureWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L328"}, {"id": "qc_work_order_qcworkordercardlist_canedittemporaryworkorder", "label": "canEditTemporaryWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L332"}, {"id": "qc_work_order_qcworkordercardlist_caneditremarkworkorder", "label": "canEditRemarkWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L339"}, {"id": "qc_work_order_qcworkordercardlist_candeleteworkorder", "label": "canDeleteWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L352"}, {"id": "qc_work_order_qcworkordercardlist_hasmoreworkorderactions", "label": "hasMoreWorkOrderActions()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L360"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordercardlist_vue", "target": "qc_work_order_qcworkordercardlist_protocolstatustagtypebysource", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L281", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordercardlist_vue", "target": "qc_work_order_qcworkordercardlist_shouldshowtempemptyfield", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "label": "qc-work-order.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L1"}, {"id": "api_qc_work_order_fetchqcworkorderlist", "label": "fetchQcWorkOrderList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L172"}, {"id": "api_qc_work_order_fetchqcworkorderdetail", "label": "fetchQcWorkOrderDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L191"}, {"id": "api_qc_work_order_fetchconfigureqcworkorder", "label": "fetchConfigureQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L197"}, {"id": "api_qc_work_order_fetchcreateqcworkorder", "label": "fetchCreateQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L214"}, {"id": "api_qc_work_order_fetchupdateqcworkorder", "label": "fetchUpdateQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L229"}, {"id": "api_qc_work_order_fetchdeleteqcworkorder", "label": "fetchDeleteQcWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L239"}, {"id": "api_qc_work_order_fetchoutboundqcdetail", "label": "fetchOutboundQcDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L303"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchqcworkorderlist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L172", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchqcworkorderdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L191", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchconfigureqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L197", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchcreateqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L214", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchupdateqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L229", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchdeleteqcworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L239", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order_ts", "target": "api_qc_work_order_fetchoutboundqcdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L303", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_qc_work_order_fetchqcworkorderlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L185"}, {"caller_nid": "api_qc_work_order_fetchqcworkorderdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L192"}, {"caller_nid": "api_qc_work_order_fetchconfigureqcworkorder", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-work-order.ts", "source_location": "L208"}, {"caller_nid": "api_qc_work_order_fetchcreateqcworkoLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordertasksublist_vue", "label": "QcWorkOrderTaskSubList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L1"}, {"id": "qc_work_order_qcworkordertasksublist_iscurrentuserassignee", "label": "isCurrentUserAssignee()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L615"}, {"id": "qc_work_order_qcworkordertasksublist_nextmaterialrowkey", "label": "nextMaterialRowKey()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L643"}, {"id": "qc_work_order_qcworkordertasksublist_onrequisitionselect", "label": "onRequisitionSelect()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L807"}, {"id": "qc_work_order_qcworkordertasksublist_addmaterialrow", "label": "addMaterialRow()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L835"}, {"id": "qc_work_order_qcworkordertasksublist_removematerialrow", "label": "removeMaterialRow()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L849"}, {"id": "qc_work_order_qcworkordertasksublist_materialrowsfromapi", "label": "materialRowsFromApi()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L853"}, {"id": "qc_work_order_qcworkordertasksublist_enrichmaterialusagerows", "label": "enrichMaterialUsageRows()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L873"}, {"id": "qc_work_order_qcworkordertasksublist_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L972"}, {"id": "qc_work_order_qcworkordertasksublist_normalizeresultparams", "label": "normalizeResultParams()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L976"}, {"id": "qc_work_order_qcworkordertasksublist_toresultnumber", "label": "toResultNumber()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L984"}, {"id": "qc_work_order_qcworkordertasksublist_setresultnumber", "label": "setResultNumber()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L990"}, {"id": "qc_work_order_qcworkordertasksublist_hasresultvalue", "label": "hasResultValue()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L994"}, {"id": "qc_work_order_qcworkordertasksublist_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L998"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordertasksublist_vue", "target": "qc_work_order_qcworkordertasksublist_iscurrentuserassignee", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L615", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordertasksublist_vue", "target": "qc_work_order_qcworkordertasksublist_nextmaterialrowkey", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue", "source_location": "L643", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordertasksublist_vue", "target": "qc_work_order_qcworLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order_ts", "label": "production-work-order.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L1"}, {"id": "api_production_work_order_fetchproductionworkorderlist", "label": "fetchProductionWorkOrderList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L69"}, {"id": "api_production_work_order_fetchproductionworkorderdetail", "label": "fetchProductionWorkOrderDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L89"}, {"id": "api_production_work_order_fetchcompleteproductionworkorder", "label": "fetchCompleteProductionWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L93"}, {"id": "api_production_work_order_fetchcreateproductionworkorder", "label": "fetchCreateProductionWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L97"}, {"id": "api_production_work_order_fetchconfigureproductionworkorder", "label": "fetchConfigureProductionWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L110"}, {"id": "api_production_work_order_fetchupdateproductionworkorder", "label": "fetchUpdateProductionWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L126"}, {"id": "api_production_work_order_fetchdeleteproductionworkorder", "label": "fetchDeleteProductionWorkOrder()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L133"}, {"id": "api_production_work_order_fetchproductionworkordercellproductoptions", "label": "fetchProductionWorkOrderCellProductOptions()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L149"}, {"id": "api_production_work_order_fetchcreateqcfromproduction", "label": "fetchCreateQcFromProduction()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L155"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order_ts", "target": "api_production_work_order_fetchproductionworkorderlist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L69", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order_ts", "target": "api_production_work_order_fetchproductionworkorderdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L89", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order_ts", "target": "api_production_work_order_fetchcompleteproductionworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L93", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order_ts", "target": "api_production_work_order_fetchcreateproductionworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "source_location": "L97", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order_ts", "target": "api_production_work_order_fetchconfigureproductionworkorder", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts", "soLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L1"}, {"id": "production_work_order_index_protocolstatuslabelbysource", "label": "protocolStatusLabelBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L331"}, {"id": "production_work_order_index_protocolstatustagtypebysource", "label": "protocolStatusTagTypeBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L336"}, {"id": "production_work_order_index_shouldshowtempemptyfield", "label": "shouldShowTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L343"}, {"id": "production_work_order_index_formattempemptyfield", "label": "formatTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L346"}, {"id": "production_work_order_index_formatoutboundschedulesortno", "label": "formatOutboundScheduleSortNo()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L350"}, {"id": "production_work_order_index_formatoutboundscheduletotalcount", "label": "formatOutboundScheduleTotalCount()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L356"}, {"id": "production_work_order_index_handleadd", "label": "handleAdd()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L429"}, {"id": "production_work_order_index_handletypetabchange", "label": "handleTypeTabChange()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L437"}, {"id": "production_work_order_index_submitadd", "label": "submitAdd()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L443"}, {"id": "production_work_order_index_handlereceptionattachmentpreview", "label": "handleReceptionAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L706"}, {"id": "production_work_order_index_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L710"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "vue_router", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L286", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L287", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L288", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L298", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_labels", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-oLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordercardlist_vue", "label": "QcWorkOrderCardList.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordercardlist_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_qc_work_order", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L56", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordercardlist_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_work_order_groups", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L57", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordercardlist_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersamplegroup_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L58", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordercardlist_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue", "source_location": "L59", "weight": 1.0}], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "label": "QcWorkOrderSingleCard.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L1"}, {"id": "qc_work_order_qcworkordersinglecard_protocolstatuslabelbysource", "label": "protocolStatusLabelBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L298"}, {"id": "qc_work_order_qcworkordersinglecard_protocolstatustagtypebysource", "label": "protocolStatusTagTypeBySource()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L302"}, {"id": "qc_work_order_qcworkordersinglecard_shouldshowtempemptyfield", "label": "shouldShowTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L306"}, {"id": "qc_work_order_qcworkordersinglecard_formattempemptyfield", "label": "formatTempEmptyField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L310"}, {"id": "qc_work_order_qcworkordersinglecard_formattempcellproductfield", "label": "formatTempCellProductField()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L314"}, {"id": "qc_work_order_qcworkordersinglecard_formatoutboundschedulesortno", "label": "formatOutboundScheduleSortNo()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L319"}, {"id": "qc_work_order_qcworkordersinglecard_formatoutboundscheduletotalcount", "label": "formatOutboundScheduleTotalCount()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L325"}, {"id": "qc_work_order_qcworkordersinglecard_formatdetectmethods", "label": "formatDetectMethods()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L331"}, {"id": "qc_work_order_qcworkordersinglecard_formatqctarget", "label": "formatQcTarget()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L338"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_protocolstatuslabelbysource", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L298", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_protocolstatustagtypebysource", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L302", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_shouldshowtempemptyfield", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L306", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_formattempemptyfield", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue", "source_location": "L310", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_qc_work_order_qcworkordersinglecard_vue", "target": "qc_work_order_qcworkordersinglecard_formattempcellproductfield", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspectioLine truncated
@@ -0,0 +1,42 @@
import type { QcWorkOrder } from '@/api/qc-work-order'
export type QcWorkOrderSampleGroupModel = {
groupKey: string
sampleCode: string
workOrders: QcWorkOrder[]
}
function triggerSortWeight(row: QcWorkOrder) {
return (row.triggerSource || 'reception') === 'reception' ? 0 : 1
}
function sortWorkOrdersInGroup(rows: QcWorkOrder[]) {
return [...rows].sort((a, b) => {
const byTrigger = triggerSortWeight(a) - triggerSortWeight(b)
if (byTrigger !== 0) return byTrigger
return String(a.createTime || '').localeCompare(String(b.createTime || ''))
})
}
/** 入库 Tab:按样本编码分组(前端分组,分页仍按工单条数) */
export function groupInboundQcWorkOrders(rows: QcWorkOrder[]): QcWorkOrderSampleGroupModel[] {
const map = new Map<string, QcWorkOrder[]>()
for (const row of rows) {
const key = (row.sampleCode || '').trim() || row.id
const list = map.get(key)
if (list) list.push(row)
else map.set(key, [row])
}
return Array.from(map.entries()).map(([groupKey, workOrders]) => {
const sorted = sortWorkOrdersInGroup(workOrders)
return {
groupKey,
sampleCode: sorted[0]?.sampleCode || '',
workOrders: sorted
}
})
}
export function triggerSourceTabLabel(row: QcWorkOrder) {
return row.triggerSourceLabel || ((row.triggerSource || 'reception') === 'production' ? '生产过程' : '样本接收')
}
@@ -0,0 +1,206 @@
<!-- 生产工单 · 派发质检工单弹窗 -->
<template>
<el-dialog
v-model="dialogVisible"
title="派发质检工单"
width="560px"
class="dialog-form-layout"
align-center
append-to-body
destroy-on-close
:close-on-click-modal="false"
@close="handleClose"
>
<div class="dispatch-qc-dialog-work-order-info">
<span
>工单号:<b>{{ workOrderRow?.workOrderNo || '—' }}</b></span
>
<span>样本编码:{{ workOrderRow?.sampleCode || '—' }}</span>
</div>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
class="dialog-form-layout"
v-loading="optionsLoading"
>
<div class="form-grid-2">
<el-form-item label="细胞产品" prop="cellProductId" class="form-col-span-2">
<el-select
v-model="form.cellProductId"
placeholder="请选择细胞产品"
filterable
style="width: 100%"
:disabled="!productOptions.length"
>
<el-option
v-for="opt in productOptions"
:key="opt.id"
:label="formatProductOptionLabel(opt)"
:value="opt.id"
/>
</el-select>
<p v-if="!optionsLoading && !productOptions.length" class="hint-muted">
暂无产出细胞产品,请先完成生产任务并录入输出。
</p>
</el-form-item>
<el-form-item label="备注" class="form-col-span-2">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="选填"
maxlength="512"
show-word-limit
/>
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="handleClose">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button
class="btn-main"
type="primary"
:loading="submitting"
:disabled="!productOptions.length"
@click="handleSubmit"
>
<el-icon><Check /></el-icon>确定
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch, computed } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { Close, Check } from '@element-plus/icons-vue'
import {
fetchProductionWorkOrderCellProductOptions,
fetchCreateQcFromProduction,
type ProductionWorkOrderItem,
type ProductionCellProductOption
} from '@/api/production-work-order'
defineOptions({ name: 'DispatchQcWorkOrderDialog' })
const props = defineProps<{
visible: boolean
workOrderRow: ProductionWorkOrderItem | null
}>()
const emit = defineEmits<{
'update:visible': [val: boolean]
success: [payload: { sampleCode?: string }]
}>()
const dialogVisible = computed({
get: () => props.visible,
set: (v) => emit('update:visible', v)
})
const formRef = ref<FormInstance>()
const form = reactive({ cellProductId: '', remark: '' })
const rules: FormRules = {
cellProductId: [{ required: true, message: '请选择细胞产品', trigger: 'change' }]
}
const productOptions = ref<ProductionCellProductOption[]>([])
const optionsLoading = ref(false)
const submitting = ref(false)
function formatProductOptionLabel(opt: ProductionCellProductOption) {
const parts = [
opt.productCode,
opt.productName,
opt.productTypeName ? `(${opt.productTypeName})` : '',
opt.passage ? `P${opt.passage}` : '',
opt.storageSlotName ? `@${opt.storageSlotName}` : ''
].filter(Boolean)
const base = parts.join(' ') || opt.id
return opt.taskNo ? `${base} · ${opt.taskNo}` : base
}
async function loadOptions() {
if (!props.workOrderRow?.id) {
productOptions.value = []
return
}
optionsLoading.value = true
try {
productOptions.value = (await fetchProductionWorkOrderCellProductOptions(props.workOrderRow.id)) || []
} finally {
optionsLoading.value = false
}
}
function resetForm() {
form.cellProductId = ''
form.remark = ''
formRef.value?.clearValidate()
}
function handleClose() {
dialogVisible.value = false
resetForm()
}
async function handleSubmit() {
if (!props.workOrderRow?.id) return
await formRef.value?.validate()
submitting.value = true
try {
const selected = productOptions.value.find((o) => o.id === form.cellProductId)
await fetchCreateQcFromProduction(props.workOrderRow.id, {
cellProductId: form.cellProductId,
productionTaskId: selected?.taskId,
remark: form.remark || undefined
})
ElMessage.success('质检工单派发成功')
emit('success', { sampleCode: props.workOrderRow.sampleCode })
handleClose()
} finally {
submitting.value = false
}
}
watch(
() => [props.visible, props.workOrderRow?.id] as const,
([visible]) => {
if (visible) {
resetForm()
loadOptions()
}
}
)
</script>
<style scoped lang="scss">
.dispatch-qc-dialog-work-order-info {
display: flex;
flex-wrap: wrap;
gap: 8px 20px;
margin-bottom: 12px;
padding: 10px 12px;
font-size: 13px;
color: #64748b;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
b {
color: #1e293b;
font-weight: 600;
}
}
.hint-muted {
margin: 6px 0 0;
font-size: 12px;
color: var(--mint-text-muted, #94a3b8);
line-height: 1.4;
}
</style>
@@ -0,0 +1,496 @@
<!-- 生产工单 · 卡片列表(单列 + 内嵌生产任务) -->
<template>
<div class="list-body list-wrap production-work-order-cards-body">
<div v-loading="loading" class="production-work-order-cards-wrap">
<article v-for="(row, idx) in rows" :key="row.id" class="production-work-order-card">
<header class="production-work-order-card__head">
<div class="production-work-order-card__head-main">
<div class="production-work-order-card__title-row">
<el-checkbox
:model-value="isSelected(row.id)"
class="production-work-order-card__checkbox"
@change="(val) => handleCardSelect(row, Boolean(val))"
/>
<span class="production-work-order-card__index">{{ cardIndex(idx) }}</span>
<span class="production-work-order-card__no">{{ row.workOrderNo || '—' }}</span>
<el-tag :type="productionWoStatusTag(row.workOrderStatus).type" size="small">
{{ productionWoStatusTag(row.workOrderStatus).label }}
</el-tag>
</div>
<dl class="production-work-order-card__meta">
<template v-if="activeTypeTab === 'outbound'">
<div class="production-work-order-card__meta-item">
<dt>业务类型</dt>
<dd>
<el-tag
:type="
row.bizType === 'pilot_service'
? 'primary'
: row.bizType === 'clinical_application'
? 'warning'
: 'info'
"
size="small"
>
{{ row.bizTypeLabel || '—' }}
</el-tag>
</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>申请单号</dt>
<dd>{{ row.applicationNo || '—' }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>用户名称</dt>
<dd>{{ row.patientName || '—' }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>第几次</dt>
<dd>{{ formatOutboundScheduleSortNo(row) }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>总次数</dt>
<dd>{{ formatOutboundScheduleTotalCount(row) }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>申请单名称</dt>
<dd>{{ row.applicationName || '—' }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>渠道名称</dt>
<dd>{{ row.channelName || '—' }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>输注时间</dt>
<dd>{{ row.infusionTime || '—' }}</dd>
</div>
</template>
<template v-else-if="activeTypeTab === 'temporary'">
<div class="production-work-order-card__meta-item">
<dt>备注</dt>
<dd>{{ row.remark || '—' }}</dd>
</div>
</template>
<template v-else>
<div class="production-work-order-card__meta-item">
<dt>样本编码</dt>
<dd>{{ formatTempEmptyField(row, row.sampleCode) }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>实验室入库编码</dt>
<dd>{{ formatTempEmptyField(row, row.labStorageCode) }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>来源类型</dt>
<dd>
<span v-if="shouldShowTempEmptyField(row, row.receptionSource)">—</span>
<el-tag
v-else
:type="row.receptionSource === 'research_donation' ? 'warning' : 'success'"
size="small"
>
{{ row.receptionSourceLabel || receptionSourceLabel(row.receptionSource || '') }}
</el-tag>
</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>协议状态</dt>
<dd>
<span v-if="shouldShowTempEmptyField(row, row.protocolStatus)">—</span>
<el-tag
v-else
:type="
protocolStatusTagTypeBySource(
String(row.protocolStatus || ''),
row.receptionSource || 'cell_storage'
)
"
size="small"
>
{{
protocolStatusLabelBySource(
String(row.protocolStatus || ''),
row.receptionSource || 'cell_storage'
)
}}
</el-tag>
</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>样本类型</dt>
<dd>{{ row.sampleTypeName || '—' }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>接收经手人</dt>
<dd>{{ row.handlerName || '—' }}</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>是否超24小时</dt>
<dd>
<span v-if="!row.receptionTime">—</span>
<el-tag v-else :type="receptionOver24HoursTag(row.receptionTime).type" size="small">
{{ receptionOver24HoursTag(row.receptionTime).label }}
</el-tag>
</dd>
</div>
<div class="production-work-order-card__meta-item">
<dt>接收时间</dt>
<dd>{{ row.receptionTime || '—' }}</dd>
</div>
</template>
<div class="production-work-order-card__meta-item">
<dt>创建时间</dt>
<dd>{{ row.createTime || '—' }}</dd>
</div>
<div
v-if="row.remark && activeTypeTab !== 'temporary'"
class="production-work-order-card__meta-item production-work-order-card__meta-item--wide"
>
<dt>备注</dt>
<dd>{{ row.remark }}</dd>
</div>
</dl>
</div>
<div class="production-work-order-card__ops">
<el-button
v-if="canDispatchTask(row)"
class="btn-op-item primary"
type="primary"
@click="emit('dispatch', row)"
>
<el-icon><Position /></el-icon>派发任务
</el-button>
<el-button v-else class="btn-op-item primary" @click="emit('view', row)">
<el-icon><View /></el-icon>查看
</el-button>
<el-tooltip
v-if="activeTypeTab === 'inbound'"
:content="dispatchQcTooltip(row)"
:disabled="canDispatchQc(row)"
placement="top"
>
<span>
<el-button
class="btn-op-item"
:disabled="!canDispatchQc(row)"
@click="emit('dispatch-qc', row)"
>
<el-icon><Document /></el-icon>派发质检工单
</el-button>
</span>
</el-tooltip>
<el-button v-if="canComplete(row)" class="btn-op-item" @click="emit('complete', row)">
<el-icon><Check /></el-icon>完成工单
</el-button>
<el-dropdown
v-if="hasMoreActions(row)"
trigger="hover"
@command="(cmd) => emit('command', cmd, row)"
>
<el-button class="btn-op-item">
<el-icon><More /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="canDispatchTask(row)" command="view">
<el-icon><View /></el-icon>查看
</el-dropdown-item>
<el-dropdown-item command="edit">
<el-icon><EditPen /></el-icon>编辑
</el-dropdown-item>
<el-dropdown-item v-if="canDeleteWorkOrder(row)" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</header>
<section class="production-work-order-card__tasks">
<WorkOrderTaskSubList
embedded
:work-order="row"
:refresh-token="taskRefreshToken"
@changed="emit('tasks-changed')"
/>
</section>
</article>
<el-empty
v-if="!loading && !rows.length"
class="production-work-order-cards-empty"
description="暂无生产工单"
:image-size="72"
/>
</div>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="page"
:page-size="pageSize"
:total="total"
layout="total, sizes, prev, pager, next"
@size-change="(v) => emit('page-size-change', v)"
@current-change="(v) => emit('page-change', v)"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { View, Position, Document, Check, More, EditPen, Delete } from '@element-plus/icons-vue'
import type { ProductionWorkOrderItem } from '@/api/production-work-order'
import { receptionSourceLabel } from '@/utils/qc-labels'
import {
protocolStatusLabel,
protocolStatusTagType,
donationStatusLabel,
donationStatusTagType
} from '@/utils/status-tag-registry'
import type { TagType } from '@/utils/status-tag-registry'
import WorkOrderTaskSubList from './WorkOrderTaskSubList.vue'
defineOptions({ name: 'ProductionWorkOrderCardList' })
type WorkOrderTypeTab = 'inbound' | 'temporary' | 'outbound'
const props = defineProps<{
rows: ProductionWorkOrderItem[]
loading: boolean
activeTypeTab: WorkOrderTypeTab
page: number
pageSize: number
total: number
taskRefreshToken: number
selectedIds: string[]
}>()
const emit = defineEmits<{
view: [row: ProductionWorkOrderItem]
dispatch: [row: ProductionWorkOrderItem]
'dispatch-qc': [row: ProductionWorkOrderItem]
complete: [row: ProductionWorkOrderItem]
command: [cmd: string, row: ProductionWorkOrderItem]
'toggle-select': [row: ProductionWorkOrderItem, checked: boolean]
'tasks-changed': []
'page-change': [page: number]
'page-size-change': [size: number]
}>()
const RECEPTION_OVER_HOURS_MS = 24 * 60 * 60 * 1000
function protocolStatusLabelBySource(status: string, source: string): string {
return source === 'research_donation' ? donationStatusLabel(status) : protocolStatusLabel(status)
}
function protocolStatusTagTypeBySource(status: string, source: string): TagType {
return source === 'research_donation' ? donationStatusTagType(status) : protocolStatusTagType(status)
}
function shouldShowTempEmptyField(row: ProductionWorkOrderItem, value?: string | null) {
return row.workOrderType === 'temporary' && !String(value || '').trim()
}
function formatTempEmptyField(row: ProductionWorkOrderItem, value?: string | null) {
return shouldShowTempEmptyField(row, value) ? '—' : String(value || '')
}
function formatOutboundScheduleSortNo(row: Record<string, unknown>) {
const n = row.sortNo ?? row.orderNum
if (n == null || n === '' || Number(n) <= 0) return '—'
return `第${Number(n)}次`
}
function formatOutboundScheduleTotalCount(row: Record<string, unknown>) {
const n = row.totalCourseCount
if (n == null || n === '' || Number(n) <= 0) return '—'
return `共${Number(n)}次`
}
function parseReceptionTime(value?: string | null): Date | null {
const s = String(value || '').trim()
if (!s) return null
const dt = new Date(s.replace(/-/g, '/'))
return Number.isNaN(dt.getTime()) ? null : dt
}
function receptionOver24HoursTag(receptionTime?: string | null): { label: string; type: TagType } {
const dt = parseReceptionTime(receptionTime)
if (!dt) return { label: '—', type: 'info' }
if (Date.now() - dt.getTime() > RECEPTION_OVER_HOURS_MS) return { label: '是', type: 'danger' }
return { label: '否', type: 'success' }
}
function productionWoStatusTag(status: string): { label: string; type: TagType } {
const map: Record<string, { label: string; type: TagType }> = {
dpz: { label: '待配置', type: 'info' },
jxz: { label: '进行中', type: 'warning' },
ywc: { label: '已完成', type: 'success' },
yzf: { label: '已作废', type: 'danger' }
}
return map[status] || { label: status, type: 'info' }
}
function handleCardSelect(row: ProductionWorkOrderItem, checked: boolean) {
emit('toggle-select', row, checked)
}
function cardIndex(i: number) {
return (props.page - 1) * props.pageSize + i + 1
}
function isSelected(id: string) {
return props.selectedIds.includes(id)
}
function canDispatchTask(row: ProductionWorkOrderItem) {
return row.workOrderStatus === 'dpz' || row.workOrderStatus === 'jxz'
}
function canDispatchQc(row: ProductionWorkOrderItem) {
if (props.activeTypeTab !== 'inbound') return false
if (row.workOrderStatus === 'yzf') return false
return row.workOrderStatus === 'jxz' || row.workOrderStatus === 'ywc'
}
function dispatchQcTooltip(row: ProductionWorkOrderItem) {
if (props.activeTypeTab !== 'inbound') return ''
if (row.workOrderStatus === 'dpz') return '请先派发并完成生产任务,录入输出细胞产品后再派发质检'
if (row.workOrderStatus === 'yzf') return '已作废工单不可派发质检'
return '请先完成生产任务并录入输出细胞产品'
}
function canComplete(row: ProductionWorkOrderItem) {
return row.workOrderStatus === 'dpz' || row.workOrderStatus === 'jxz'
}
function canDeleteWorkOrder(row: ProductionWorkOrderItem) {
return props.activeTypeTab !== 'outbound' && row.workOrderStatus !== 'ywc'
}
function hasMoreActions(row: ProductionWorkOrderItem) {
return canDispatchTask(row) || canDeleteWorkOrder(row) || true
}
</script>
<style scoped lang="scss">
.production-work-order-cards-body {
border-top: 1px solid var(--mint-border, #e2e8f0);
}
.production-work-order-cards-wrap {
display: flex;
flex-direction: column;
gap: 14px;
min-height: 120px;
padding: 16px;
}
.production-work-order-card {
display: flex;
flex-direction: column;
min-width: 0;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #fff;
overflow: hidden;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
&:hover {
border-color: #a7f3d0;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.08);
}
}
.production-work-order-card__head {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
padding: 14px 16px;
border-bottom: 1px solid #e2e8f0;
background: #fafafa;
}
.production-work-order-card__head-main {
min-width: 0;
}
.production-work-order-card__title-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.production-work-order-card__checkbox {
flex-shrink: 0;
margin-right: -4px;
}
.production-work-order-card__index {
flex-shrink: 0;
font-size: 12px;
color: #94a3b8;
min-width: 20px;
}
.production-work-order-card__no {
font-size: 15px;
font-weight: 600;
color: #1e293b;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
word-break: break-all;
}
.production-work-order-card__meta {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px 12px;
margin: 0;
}
.production-work-order-card__meta-item {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
dt {
margin: 0;
font-size: 11px;
line-height: 1.35;
color: #94a3b8;
}
dd {
margin: 0;
font-size: 13px;
line-height: 1.4;
color: #334155;
word-break: break-word;
}
&--wide {
grid-column: 1 / -1;
}
}
.production-work-order-card__ops {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 8px;
flex-shrink: 0;
}
.production-work-order-card__tasks {
background: #fafafa;
}
.production-work-order-cards-empty {
padding: 24px 0;
}
</style>
@@ -1,121 +1,134 @@
<!-- 生产工单展开行 · 生产任务子列表 -->
<!-- 生产工单 · 生产任务子列表 -->
<template>
<div class="work-order-task-sublist">
<div class="work-order-task-sublist" :class="{ 'work-order-task-sublist--embedded': embedded }">
<div class="work-order-task-sublist__panel" v-loading="loading">
<div v-if="tableData.length" class="work-order-task-cards-wrap">
<div class="work-order-task-sublist__summary">
<span class="work-order-task-sublist__count">共 {{ tableData.length }} 项任务</span>
<el-button
class="work-order-task-sublist__refresh-icon"
link
type="primary"
:loading="loading"
@click="loadData"
>
<el-icon><RefreshLeft /></el-icon>
</el-button>
<el-button
class="btn-main work-order-task-sublist__dispatch"
type="primary"
size="small"
:disabled="!canDispatch"
@click="handleDispatch"
>
<el-icon><Position /></el-icon>派发任务
</el-button>
<div v-if="tableData.length" class="work-order-task-cards-area">
<div class="work-order-task-sublist__head">
<div class="work-order-task-sublist__head-title">生产任务</div>
<div class="work-order-task-sublist__head-extra">
<span class="work-order-task-sublist__count">共 {{ tableData.length }} 项任务</span>
<el-button
class="work-order-task-sublist__refresh-icon"
link
type="primary"
:loading="loading"
@click="loadData"
>
<el-icon><RefreshLeft /></el-icon>
</el-button>
<el-button
v-if="!embedded"
class="btn-main work-order-task-sublist__dispatch"
type="primary"
size="small"
:disabled="!canDispatch"
@click="handleDispatch"
>
<el-icon><Position /></el-icon>派发任务
</el-button>
</div>
</div>
<div
v-for="(row, idx) in tableData"
:key="row.id || idx"
class="work-order-task-card"
@dblclick="handleViewDetail(row)"
>
<div class="work-order-task-card__head">
<span class="work-order-task-card__name">{{ row.taskName || row.taskNo || '—' }}</span>
<el-tag :type="taskStatusTag(row.taskStatus).type" size="small">
{{ taskStatusTag(row.taskStatus).label }}
</el-tag>
</div>
<div class="work-order-task-card__meta">
<span class="work-order-task-card__code">{{ row.taskNo || '—' }}</span>
<el-tag :type="productionTaskTypeTag(row.taskType, row.orderSource).type" size="small">
{{ productionTaskTypeTag(row.taskType, row.orderSource).label }}
</el-tag>
<el-tag
v-if="productionConclusionTagType(row.conclusion)"
:type="productionConclusionTagType(row.conclusion)"
size="small"
>
{{ row.conclusionLabel || productionConclusionLabel(row.conclusion) }}
</el-tag>
</div>
<div class="work-order-task-sublist__ops">
<el-button
v-if="canEnterExecution(row)"
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleEnterExecution(row)"
>
<el-icon><VideoPlay /></el-icon>进入执行
</el-button>
<el-button
v-else
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleViewDetail(row)"
>
<el-icon><View /></el-icon>查看
</el-button>
<el-dropdown
v-if="hasMoreTaskActions(row)"
trigger="hover"
@command="(cmd) => handleDropdownCommand(cmd, row)"
>
<el-button class="btn-op-item" size="small" @click.stop>
<el-icon><More /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="canEnterExecution(row)" command="view">
<el-icon><View /></el-icon>查看
</el-dropdown-item>
<el-dropdown-item v-if="canEditTask(row)" command="editInfo">
<el-icon><EditPen /></el-icon>编辑信息
</el-dropdown-item>
<el-dropdown-item v-if="canEditTask(row)" command="editRoute">
<el-icon><EditPen /></el-icon>编辑工艺
</el-dropdown-item>
<el-dropdown-item v-if="canDeleteTask(row)" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="work-order-task-cards-grid">
<div
v-for="(row, idx) in tableData"
:key="row.id || idx"
class="work-order-task-card"
@dblclick="handleViewDetail(row)"
>
<div class="work-order-task-card__line work-order-task-card__line--top">
<span class="work-order-task-card__name">{{ row.taskName || row.taskNo || '—' }}</span>
<div class="work-order-task-card__tags">
<el-tag :type="taskStatusTag(row.taskStatus).type" size="small">
{{ taskStatusTag(row.taskStatus).label }}
</el-tag>
<el-tag
v-if="productionConclusionTagType(row.conclusion)"
:type="productionConclusionTagType(row.conclusion)"
size="small"
>
{{ row.conclusionLabel || productionConclusionLabel(row.conclusion) }}
</el-tag>
</div>
</div>
<div class="work-order-task-card__line work-order-task-card__line--bottom">
<span class="work-order-task-card__code">{{ row.taskNo || '—' }}</span>
<span class="work-order-task-card__sep">·</span>
<el-tag :type="productionTaskTypeTag(row.taskType, row.orderSource).type" size="small">
{{ productionTaskTypeTag(row.taskType, row.orderSource).label }}
</el-tag>
<div class="work-order-task-sublist__ops">
<el-button
v-if="canEnterExecution(row)"
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleEnterExecution(row)"
>
<el-icon><VideoPlay /></el-icon>进入执行
</el-button>
<el-button
v-else
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleViewDetail(row)"
>
<el-icon><View /></el-icon>查看
</el-button>
<el-dropdown
v-if="hasMoreTaskActions(row)"
trigger="hover"
@command="(cmd) => handleDropdownCommand(cmd, row)"
>
<el-button class="btn-op-item" size="small" @click.stop>
<el-icon><More /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="canEnterExecution(row)" command="view">
<el-icon><View /></el-icon>查看
</el-dropdown-item>
<el-dropdown-item v-if="canEditTask(row)" command="editInfo">
<el-icon><EditPen /></el-icon>编辑信息
</el-dropdown-item>
<el-dropdown-item v-if="canEditTask(row)" command="editRoute">
<el-icon><EditPen /></el-icon>编辑工艺
</el-dropdown-item>
<el-dropdown-item v-if="canDeleteTask(row)" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</div>
</div>
<div v-else-if="!loading" class="work-order-task-cards-wrap work-order-task-cards-wrap--empty">
<div class="work-order-task-sublist__summary">
<el-button
class="work-order-task-sublist__refresh-icon"
link
type="primary"
:loading="loading"
@click="loadData"
>
<el-icon><RefreshLeft /></el-icon>
</el-button>
<el-button
class="btn-main work-order-task-sublist__dispatch"
type="primary"
size="small"
:disabled="!canDispatch"
@click="handleDispatch"
>
<el-icon><Position /></el-icon>派发任务
</el-button>
<div v-else-if="!loading" class="work-order-task-cards-area work-order-task-cards-area--empty">
<div class="work-order-task-sublist__head">
<div class="work-order-task-sublist__head-title">生产任务</div>
<div class="work-order-task-sublist__head-extra">
<el-button
class="work-order-task-sublist__refresh-icon"
link
type="primary"
:loading="loading"
@click="loadData"
>
<el-icon><RefreshLeft /></el-icon>
</el-button>
<el-button
v-if="!embedded"
class="btn-main work-order-task-sublist__dispatch"
type="primary"
size="small"
:disabled="!canDispatch"
@click="handleDispatch"
>
<el-icon><Position /></el-icon>派发任务
</el-button>
</div>
</div>
<el-empty
class="work-order-task-sublist__empty"
@@ -183,6 +196,8 @@
workOrder: Record<string, any>
/** 父级派发/完成工单后递增,用于刷新已展开子列表 */
refreshToken?: number
/** 内嵌于工单卡片时收紧布局并隐藏子列表派发按钮 */
embedded?: boolean
}>()
const emit = defineEmits<{
@@ -320,27 +335,65 @@
padding: 8px 12px 10px 40px;
background: #f8fafc;
text-align: left;
&--embedded {
padding: 10px 14px 12px;
background: transparent;
}
}
.work-order-task-sublist__panel {
min-height: 32px;
}
.work-order-task-cards-wrap {
.work-order-task-cards-area {
display: flex;
flex-direction: column;
gap: 10px;
&--empty {
gap: 4px;
}
}
.work-order-task-sublist__head {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.work-order-task-sublist__summary {
flex: 0 0 auto;
.work-order-task-sublist__head-title {
font-size: 12px;
font-weight: 600;
color: #64748b;
letter-spacing: 0.02em;
}
.work-order-task-sublist__head-extra {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
gap: 4px 8px;
padding-top: 8px;
min-height: 34px;
}
.work-order-task-cards-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
@media (max-width: 1200px) {
.work-order-task-cards-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.work-order-task-cards-grid {
grid-template-columns: 1fr;
}
}
.work-order-task-sublist__count {
@@ -365,18 +418,7 @@
flex-shrink: 0;
}
.work-order-task-cards-wrap--empty {
align-items: center;
.work-order-task-sublist__summary {
padding-top: 0;
min-height: auto;
}
}
.work-order-task-sublist__empty {
flex: 1;
min-width: 200px;
padding: 4px 0;
:deep(.el-empty__description) {
@@ -388,9 +430,7 @@
display: flex;
flex-direction: column;
gap: 6px;
flex: 0 0 248px;
width: 248px;
max-width: 248px;
min-width: 0;
padding: 8px 10px;
border: 1px solid #e2e8f0;
border-radius: 8px;
@@ -404,12 +444,22 @@
}
}
.work-order-task-card__head {
.work-order-task-card__line {
display: flex;
align-items: flex-start;
justify-content: space-between;
align-items: center;
gap: 6px;
min-width: 0;
&--top {
flex-wrap: wrap;
row-gap: 4px;
justify-content: space-between;
}
&--bottom {
flex-wrap: wrap;
row-gap: 4px;
}
}
.work-order-task-card__name {
@@ -420,18 +470,18 @@
color: #1e293b;
line-height: 1.35;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
word-break: break-word;
}
.work-order-task-card__meta {
display: flex;
.work-order-task-card__tags {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 6px;
min-width: 0;
gap: 4px;
flex-shrink: 0;
}
.work-order-task-card__code {
@@ -439,17 +489,20 @@
font-size: 11px;
color: #475569;
line-height: 1.35;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-shrink: 0;
}
.work-order-task-card__sep {
flex-shrink: 0;
color: #cbd5e1;
font-size: 11px;
}
.work-order-task-sublist__ops {
display: flex;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
padding-top: 2px;
gap: 4px;
margin-left: auto;
}
</style>
@@ -93,270 +93,26 @@
</div>
</div>
<div class="list-body list-wrap">
<el-table
ref="workOrderListTableRef"
class="production-work-order-table list-table"
:data="tableData"
v-loading="loading"
stripe
row-key="id"
@selection-change="handleSelectionChange"
@expand-change="handleWorkOrderRowExpand"
>
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
<el-table-column type="expand" min-width="64">
<template #default="{ row }">
<WorkOrderTaskSubList
:work-order="row"
:refresh-token="taskSubListRefreshToken"
@changed="handleWorkOrderTasksChanged"
/>
</template>
</el-table-column>
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
/>
<el-table-column prop="workOrderNo" label="工单号" min-width="150" align="center" />
<el-table-column
v-if="activeTypeTab === 'outbound'"
prop="workOrderStatus"
label="工单状态"
min-width="100"
align="center"
>
<template #default="{ row }">
<el-tag :type="woStatusTag(row.workOrderStatus).type" size="small">
{{ woStatusTag(row.workOrderStatus).label }}
</el-tag>
</template>
</el-table-column>
<!-- 出库工单专属列 -->
<el-table-column
v-if="activeTypeTab === 'outbound'"
prop="bizTypeLabel"
label="业务类型"
min-width="110"
align="center"
>
<template #default="{ row }">
<el-tag
v-if="row.bizType"
:type="bizTypeTagType(row.bizType) as TagType"
size="small"
>
{{ row.bizTypeLabel || bizTypeLabel(row.bizType) }}
</el-tag>
<span v-else>—</span>
</template>
</el-table-column>
<el-table-column
v-if="activeTypeTab === 'outbound'"
prop="applicationNo"
label="申请单号"
min-width="150"
align="center"
>
<template #default="{ row }">{{ row.applicationNo || '—' }}</template>
</el-table-column>
<el-table-column
v-if="activeTypeTab === 'outbound'"
prop="patientName"
label="用户名称"
min-width="100"
align="center"
>
<template #default="{ row }">{{ row.patientName || '—' }}</template>
</el-table-column>
<el-table-column
v-if="activeTypeTab === 'outbound'"
label="第几次"
min-width="88"
align="center"
>
<template #default="{ row }">{{ formatOutboundScheduleSortNo(row) }}</template>
</el-table-column>
<el-table-column
v-if="activeTypeTab === 'outbound'"
label="总次数"
min-width="88"
align="center"
>
<template #default="{ row }">{{ formatOutboundScheduleTotalCount(row) }}</template>
</el-table-column>
<el-table-column
v-if="activeTypeTab === 'outbound'"
prop="infusionTime"
label="输注时间"
min-width="170"
align="center"
>
<template #default="{ row }">{{ row.infusionTime || '—' }}</template>
</el-table-column>
<el-table-column
v-if="showReceptionCols"
prop="protocolStatus"
label="协议状态"
min-width="100"
align="center"
>
<template #default="{ row }">
<span v-if="shouldShowTempEmptyField(row, row.protocolStatus)">—</span>
<el-tag
v-else
:type="
protocolStatusTagTypeBySource(
String(row.protocolStatus || ''),
row.receptionSource || 'cell_storage'
)
"
size="small"
>{{
protocolStatusLabelBySource(
String(row.protocolStatus || ''),
row.receptionSource || 'cell_storage'
)
}}</el-tag
>
</template>
</el-table-column>
<el-table-column
v-if="showReceptionCols"
prop="sampleCode"
label="样本编码"
min-width="120"
align="center"
>
<template #default="{ row }">{{ formatTempEmptyField(row, row.sampleCode) }}</template>
</el-table-column>
<el-table-column
v-if="showReceptionCols"
prop="labStorageCode"
label="实验室入库编码"
min-width="150"
align="center"
>
<template #default="{ row }">{{
formatTempEmptyField(row, row.labStorageCode)
}}</template>
</el-table-column>
<el-table-column
v-if="showReceptionCols"
prop="receptionSource"
label="来源类型"
min-width="110"
align="center"
>
<template #default="{ row }">
<span v-if="shouldShowTempEmptyField(row, row.receptionSource)">—</span>
<el-tag v-else :type="sourceTag(row.receptionSource).type" size="small">
{{ row.receptionSourceLabel || sourceTag(row.receptionSource).label }}
</el-tag>
</template>
</el-table-column>
<el-table-column
v-if="showReceptionCols"
prop="sampleTypeName"
label="样本类型"
min-width="110"
align="center"
>
<template #default="{ row }">{{ row.sampleTypeName || '—' }}</template>
</el-table-column>
<el-table-column
v-if="showReceptionCols"
prop="handlerName"
label="接收人"
min-width="100"
align="center"
>
<template #default="{ row }">{{ row.handlerName || '—' }}</template>
</el-table-column>
<el-table-column
v-if="showReceptionCols"
label="是否超24小时"
min-width="120"
align="center"
>
<template #default="{ row }">
<span v-if="!row.receptionTime">—</span>
<el-tag v-else :type="receptionOver24HoursTag(row.receptionTime).type" size="small">
{{ receptionOver24HoursTag(row.receptionTime).label }}
</el-tag>
</template>
</el-table-column>
<el-table-column
v-if="showReceptionCols"
prop="receptionTime"
label="接收时间"
min-width="170"
align="center"
>
<template #default="{ row }">{{ row.receptionTime || '—' }}</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="140" align="center">
<template #default="{ row }">{{ row.remark || '—' }}</template>
</el-table-column>
<el-table-column
v-if="activeTypeTab !== 'outbound'"
prop="workOrderStatus"
label="工单状态"
min-width="100"
align="center"
>
<template #default="{ row }">
<el-tag :type="woStatusTag(row.workOrderStatus).type" size="small">
{{ woStatusTag(row.workOrderStatus).label }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center" />
<el-table-column
label="操作"
min-width="160"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<div style="display: flex; justify-content: center; gap: 8px">
<el-button class="btn-op-item primary" @click="handleView(row)">
<el-icon><View /></el-icon>查看
</el-button>
<el-dropdown trigger="hover" @command="(cmd) => handleDropdownCommand(cmd, row)">
<el-button class="btn-op-item">
<el-icon><More /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="edit">
<el-icon><EditPen /></el-icon>编辑
</el-dropdown-item>
<el-dropdown-item v-if="canDeleteWorkOrder(row)" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
layout="total, sizes, prev, pager, next"
@size-change="(v) => ((pagination.page = 1), (pagination.pageSize = v), loadData())"
@current-change="(v) => ((pagination.page = v), loadData())"
/>
</div>
</div>
<ProductionWorkOrderCardList
:rows="tableData"
:loading="loading"
:active-type-tab="activeTypeTab"
:page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
:task-refresh-token="taskSubListRefreshToken"
:selected-ids="selectedIds"
@view="handleView"
@dispatch="handleOpenDispatch"
@dispatch-qc="handleOpenDispatchQc"
@complete="handleCompleteSingle"
@command="handleDropdownCommand"
@toggle-select="handleCardToggleSelect"
@tasks-changed="handleWorkOrderTasksChanged"
@page-change="(v) => ((pagination.page = v), loadData())"
@page-size-change="(v) => ((pagination.page = 1), (pagination.pageSize = v), loadData())"
/>
<!-- 查看详情弹窗 -->
<SystemViewDialog
@@ -448,6 +204,12 @@
@success="handleDispatchSuccess"
/>
<DispatchQcWorkOrderDialog
v-model:visible="dispatchQcVisible"
:work-order-row="dispatchQcRow"
@success="handleDispatchQcSuccess"
/>
<!-- 新增生产工单弹窗 -->
<el-dialog
v-model="addVisible"
@@ -520,8 +282,8 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
import type { TableInstance } from 'element-plus'
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
Plus,
@@ -531,10 +293,7 @@
RefreshLeft,
View,
Document,
Position,
EditPen,
Delete,
More
Position
} from '@element-plus/icons-vue'
import {
fetchProductionWorkOrderList,
@@ -543,7 +302,8 @@
fetchCreateProductionWorkOrder,
fetchUpdateProductionWorkOrder,
fetchDeleteProductionWorkOrder,
type ProductionWorkOrderDetail
type ProductionWorkOrderDetail,
type ProductionWorkOrderItem
} from '@/api/production-work-order'
import {
receptionSourceLabel,
@@ -628,16 +388,16 @@
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
import { downloadAttachmentFile } from '@/utils/download-attachment'
import DispatchTaskDialog from './DispatchTaskDialog.vue'
import DispatchQcWorkOrderDialog from './DispatchQcWorkOrderDialog.vue'
import ProductionWorkOrderCardList from './ProductionWorkOrderCardList.vue'
import WorkOrderRemarkDialog from '@/components/business/work-order-remark-dialog/index.vue'
import WorkOrderTaskSubList from './WorkOrderTaskSubList.vue'
defineOptions({ name: 'ProductionProductionWorkOrder' })
const router = useRouter()
type WorkOrderTypeTab = 'inbound' | 'temporary' | 'outbound'
const activeTypeTab = ref<WorkOrderTypeTab>('inbound')
const showReceptionCols = computed(
() => activeTypeTab.value !== 'temporary' && activeTypeTab.value !== 'outbound'
)
const searchPlaceholder = computed(() => {
if (activeTypeTab.value === 'temporary') return '工单号'
if (activeTypeTab.value === 'outbound') return '工单号/申请单号'
@@ -774,9 +534,9 @@
const searchForm = reactive({ keyword: '', workOrderStatus: '' })
const createTimeRange = ref<[string, string] | null>(getDefaultLast30DaysRange())
const tableData = ref<any[]>([])
const selectedRows = ref<any[]>([])
const workOrderListTableRef = ref<TableInstance>()
const tableData = ref<ProductionWorkOrderItem[]>([])
const selectedRows = ref<ProductionWorkOrderItem[]>([])
const selectedIds = computed(() => selectedRows.value.map((r) => r.id))
const loading = ref(false)
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
const taskSubListRefreshToken = ref(0)
@@ -794,13 +554,18 @@
)
})
function handleSelectionChange(rows: any[]) {
selectedRows.value = rows
function handleCardToggleSelect(row: ProductionWorkOrderItem, checked: boolean) {
if (checked) {
if (!selectedRows.value.some((r) => r.id === row.id)) {
selectedRows.value = [...selectedRows.value, row]
}
} else {
selectedRows.value = selectedRows.value.filter((r) => r.id !== row.id)
}
}
function clearSelection() {
selectedRows.value = []
workOrderListTableRef.value?.clearSelection?.()
}
function requireSingleSelection(action: string) {
@@ -843,19 +608,9 @@
await loadData()
}
async function layoutWorkOrderTable() {
await nextTick()
workOrderListTableRef.value?.doLayout?.()
}
async function handleWorkOrderRowExpand(_row: any, _expandedRows: any[]) {
await layoutWorkOrderTable()
}
async function handleWorkOrderTasksChanged() {
taskSubListRefreshToken.value += 1
await loadData()
await layoutWorkOrderTable()
}
async function handleDispatchSuccess() {
@@ -892,9 +647,11 @@
})
tableData.value = res.list || []
pagination.total = res.total ?? 0
selectedRows.value = selectedRows.value.filter((row) =>
tableData.value.some((item) => item.id === row.id)
)
} finally {
loading.value = false
await layoutWorkOrderTable()
}
}
function handleSearch() {
@@ -1136,12 +893,53 @@
// ── 派发任务 ──────────────────────────────────────────
const dispatchVisible = ref(false)
const dispatchRow = ref<any>(null)
function handleOpenDispatch(row: any) {
const dispatchRow = ref<ProductionWorkOrderItem | null>(null)
function handleOpenDispatch(row: ProductionWorkOrderItem) {
dispatchRow.value = row
dispatchVisible.value = true
}
const dispatchQcVisible = ref(false)
const dispatchQcRow = ref<ProductionWorkOrderItem | null>(null)
function handleOpenDispatchQc(row: ProductionWorkOrderItem) {
dispatchQcRow.value = row
dispatchQcVisible.value = true
}
function handleDispatchQcSuccess(payload: { sampleCode?: string }) {
taskSubListRefreshToken.value += 1
loadData()
if (payload.sampleCode) {
ElMessageBox.confirm('是否前往质检工单列表查看?', '提示', {
type: 'info',
confirmButtonText: '前往',
cancelButtonText: '留在此页'
})
.then(() => {
router.push({
path: '/quality-manage/qc-work-order',
query: { sampleCode: payload.sampleCode }
})
})
.catch(() => {})
}
}
async function handleCompleteSingle(row: ProductionWorkOrderItem) {
if (row.workOrderStatus !== 'dpz' && row.workOrderStatus !== 'jxz') {
ElMessage.warning('仅待配置或进行中的工单可完成')
return
}
await ElMessageBox.confirm(`确定要完成工单「${row.workOrderNo || ''}」吗?`, '提示', {
type: 'warning'
})
await fetchCompleteProductionWorkOrder(row.id, {})
ElMessage.success('工单已完成')
selectedRows.value = selectedRows.value.filter((r) => r.id !== row.id)
taskSubListRefreshToken.value += 1
await loadData()
}
// 输入检索条件时自动搜索(防抖 300ms)
watchDebounced(
[searchForm, createTimeRange],
@@ -1158,44 +956,6 @@
</script>
<style scoped lang="scss">
.production-work-order-table :deep(td.el-table__expanded-cell) {
padding: 0 !important;
text-align: left !important;
}
.production-work-order-table :deep(td.el-table__expanded-cell > .cell) {
display: block !important;
padding: 0 !important;
text-align: left !important;
}
.production-work-order-table :deep(.el-table__expand-icon) {
width: 28px;
height: 28px;
border-radius: 6px;
border: 1px solid var(--mint-border, #d9e8e2);
display: inline-flex;
align-items: center;
justify-content: center;
transition:
background 0.15s ease,
border-color 0.15s ease;
color: var(--mint-text-muted, #64748b);
background: var(--mint-surface, #fff);
}
.production-work-order-table :deep(.el-table__expand-icon:hover) {
background: var(--mint-primary-bg, #ecfdf5);
border-color: #10b981;
color: var(--mint-primary-dark, #059669);
}
.production-work-order-table :deep(.el-table__expand-icon--expanded) {
background: var(--mint-primary-bg, #ecfdf5);
border-color: #10b981;
color: var(--mint-primary-dark, #059669);
}
.work-order-type-tabs {
padding: 0 16px;
margin-bottom: 4px;
@@ -145,7 +145,8 @@
bizTypeLabel,
bizTypeTagType,
productStatusLabel,
productStatusTagType
productStatusTagType,
payStatusTagType
} from '@/utils/status-tag-registry'
import {
buildOutboundPilotContextSections,
@@ -155,6 +156,7 @@
import type { ViewField, ViewSection } from '@/components/business/system-view-dialog/index.vue'
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
import { downloadAttachmentFile } from '@/utils/download-attachment'
import { formatMoneyDisplay } from '@/utils/money-format'
defineOptions({ name: 'OutboundQcViewDialog' })
@@ -475,6 +477,43 @@
const variant = resolveOutboundViewVariant(d.application?.bizType || d.workOrder?.bizType)
result.push(...buildOutboundPilotContextSections(d, variant))
const feeSummary = d.feeSummary || d.workOrder?.feeSummary
if (variant === 'basic_experiment') {
result.push({
title: '缴费信息',
fields: feeSummary
? [
{
label: '售卖金额(元)',
value:
feeSummary.saleAmount != null
? formatMoneyDisplay(feeSummary.saleAmount)
: '—'
},
{
label: '应缴金额(元)',
value:
feeSummary.paymentAmount != null
? formatMoneyDisplay(feeSummary.paymentAmount)
: '—'
},
{
label: '实收金额(元)',
value:
feeSummary.transferAmount != null
? formatMoneyDisplay(feeSummary.transferAmount)
: '—'
},
{
label: '缴费状态',
value: feeSummary.payStatusLabel || '—',
tagType: payStatusTagType(feeSummary.payStatus || '') as ViewField['tagType']
}
]
: [{ label: '缴费信息', value: '暂无缴费记录', span: true }]
})
}
// 检测结果 — lab tasks
const labSections = buildTaskTypeSections(tasksWithResults.value, 'sysz', '实验室项目')
result.push(...labSections)
@@ -1,218 +1,36 @@
<!-- 质检工单 · 卡片列表(工单大卡片 + 任务小卡片) -->
<!-- 质检工单 · 卡片列表(入库按样本分组 + 出库/临时单列) -->
<template>
<div class="list-body list-wrap qc-work-order-cards-body">
<div v-loading="loading" class="qc-work-order-cards-wrap">
<article v-for="(row, idx) in rows" :key="row.id" class="qc-work-order-card">
<header class="qc-work-order-card__head">
<div class="qc-work-order-card__head-main">
<div class="qc-work-order-card__title-row">
<span class="qc-work-order-card__index">{{ cardIndex(idx) }}</span>
<span class="qc-work-order-card__no">{{ row.workOrderNo || '—' }}</span>
<el-tag :type="workOrderStatusTagType(row.workOrderStatus)" size="small">
{{ workOrderStatusLabel(row.workOrderStatus) }}
</el-tag>
</div>
<dl class="qc-work-order-card__meta">
<template v-if="activeTypeTab === 'outbound'">
<div class="qc-work-order-card__meta-item">
<dt>业务类型</dt>
<dd>
<el-tag
:type="
row.bizType === 'pilot_service'
? 'primary'
: row.bizType === 'clinical_application'
? 'warning'
: 'info'
"
size="small"
>
{{ row.bizTypeLabel || '—' }}
</el-tag>
</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>申请单号</dt>
<dd>{{ row.applicationNo || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>用户名称</dt>
<dd>{{ row.patientName || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>第几次</dt>
<dd>{{ formatOutboundScheduleSortNo(row) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>总次数</dt>
<dd>{{ formatOutboundScheduleTotalCount(row) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>申请单名称</dt>
<dd>{{ row.applicationName || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>渠道名称</dt>
<dd>{{ row.channelName || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>输注时间</dt>
<dd>{{ row.infusionTime || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>来源类型</dt>
<dd>
<el-tag
:type="row.receptionSource === 'research_donation' ? 'warning' : 'success'"
size="small"
>
{{ row.receptionSourceLabel || receptionSourceLabel(row.receptionSource || '') }}
</el-tag>
</dd>
</div>
</template>
<template v-else-if="activeTypeTab === 'temporary'">
<div class="qc-work-order-card__meta-item">
<dt>业务类型</dt>
<dd>
<el-tag :type="qcBizTypeTagType('temporary')" size="small">
{{ row.qcBizTypeLabel || qcBizTypeLabel('temporary') }}
</el-tag>
</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>产品编码</dt>
<dd>{{ formatTempCellProductField(row, row.cellProductCode) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>产品名称</dt>
<dd>{{ formatTempCellProductField(row, row.cellProductName) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>产品类型</dt>
<dd>{{ formatTempCellProductField(row, row.cellProductTypeName) }}</dd>
</div>
</template>
<template v-else>
<div class="qc-work-order-card__meta-item">
<dt>样本编码</dt>
<dd>{{ formatTempEmptyField(row, row.sampleCode) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>实验室入库编码</dt>
<dd>{{ formatTempEmptyField(row, row.labStorageCode) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>来源类型</dt>
<dd>
<span v-if="shouldShowTempEmptyField(row, row.receptionSource)">—</span>
<el-tag
v-else
:type="row.receptionSource === 'research_donation' ? 'warning' : 'success'"
size="small"
>
{{ row.receptionSourceLabel || receptionSourceLabel(row.receptionSource || '') }}
</el-tag>
</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>协议状态</dt>
<dd>
<span v-if="shouldShowTempEmptyField(row, row.protocolStatus)">—</span>
<el-tag
v-else
:type="
protocolStatusTagTypeBySource(
String(row.protocolStatus || ''),
row.receptionSource || 'cell_storage'
)
"
size="small"
>
{{
protocolStatusLabelBySource(
String(row.protocolStatus || ''),
row.receptionSource || 'cell_storage'
)
}}
</el-tag>
</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>样本类型</dt>
<dd>{{ row.sampleTypeName || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>接收时间</dt>
<dd>{{ row.receptionTime || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>接收经手人</dt>
<dd>{{ row.handlerName || '—' }}</dd>
</div>
</template>
<div class="qc-work-order-card__meta-item">
<dt>检测方式</dt>
<dd>{{ formatDetectMethods(row) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>{{ activeTypeTab === 'outbound' ? '创建时间' : '创建时间' }}</dt>
<dd>{{ row.createTime || '—' }}</dd>
</div>
<div v-if="row.remark" class="qc-work-order-card__meta-item qc-work-order-card__meta-item--wide">
<dt>备注</dt>
<dd>{{ row.remark }}</dd>
</div>
</dl>
</div>
<div v-if="activeTypeTab !== 'all'" class="qc-work-order-card__ops">
<el-button
v-if="canConfigureWorkOrder(row)"
class="btn-op-item primary"
type="primary"
@click="emit('configure', row)"
>
<el-icon><Setting /></el-icon
>{{ row.workOrderStatus !== 'dpz' ? '调整检测' : '配置检测' }}
</el-button>
<el-button v-else class="btn-op-item primary" @click="emit('view', row)">
<el-icon><View /></el-icon>查看
</el-button>
<el-dropdown
v-if="hasMoreWorkOrderActions(row)"
trigger="hover"
@command="(cmd) => emit('command', cmd, row)"
>
<el-button class="btn-op-item">
<el-icon><More /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="canConfigureWorkOrder(row)" command="view">
<el-icon><View /></el-icon>查看
</el-dropdown-item>
<el-dropdown-item
v-if="canEditTemporaryWorkOrder(row) || canEditRemarkWorkOrder(row)"
command="edit"
>
<el-icon><EditPen /></el-icon>编辑
</el-dropdown-item>
<el-dropdown-item v-if="canDeleteWorkOrder(row)" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</header>
<section v-if="activeTypeTab !== 'all'" class="qc-work-order-card__tasks">
<QcWorkOrderTaskSubList
:work-order="row"
:refresh-token="taskRefreshToken"
@changed="emit('tasks-changed')"
/>
</section>
</article>
<template v-if="activeTypeTab === 'inbound'">
<QcWorkOrderSampleGroup
v-for="(group, idx) in inboundGroups"
:key="group.groupKey"
:group="group"
:group-index="cardIndex(idx)"
active-type-tab="inbound"
:task-refresh-token="taskRefreshToken"
@configure="(row) => emit('configure', row)"
@view="(row) => emit('view', row)"
@command="(cmd, row) => emit('command', cmd, row)"
@tasks-changed="emit('tasks-changed')"
/>
</template>
<template v-else>
<QcWorkOrderSingleCard
v-for="(row, idx) in rows"
:key="row.id"
layout="standalone"
:row="row"
:active-type-tab="activeTypeTab"
:card-index="cardIndex(idx)"
:task-refresh-token="taskRefreshToken"
@configure="(row) => emit('configure', row)"
@view="(row) => emit('view', row)"
@command="(cmd, row) => emit('command', cmd, row)"
@tasks-changed="emit('tasks-changed')"
/>
</template>
<el-empty
v-if="!loading && !rows.length"
class="qc-work-order-cards-empty"
@@ -234,22 +52,11 @@
</template>
<script setup lang="ts">
import { Setting, View, More, EditPen, Delete } from '@element-plus/icons-vue'
import { computed } from 'vue'
import type { QcWorkOrder } from '@/api/qc-work-order'
import {
workOrderStatusLabel,
receptionSourceLabel,
qcBizTypeLabel
} from '@/utils/qc-labels'
import {
protocolStatusLabel,
protocolStatusTagType,
donationStatusLabel,
donationStatusTagType,
qcBizTypeTagType
} from '@/utils/status-tag-registry'
import type { TagType } from '@/utils/status-tag-registry'
import QcWorkOrderTaskSubList from './QcWorkOrderTaskSubList.vue'
import { groupInboundQcWorkOrders } from '@/utils/qc-work-order-groups'
import QcWorkOrderSampleGroup from './QcWorkOrderSampleGroup.vue'
import QcWorkOrderSingleCard from './QcWorkOrderSingleCard.vue'
defineOptions({ name: 'QcWorkOrderCardList' })
@@ -274,97 +81,13 @@
'page-size-change': [size: number]
}>()
function protocolStatusLabelBySource(status: string, source: string): string {
return source === 'research_donation' ? donationStatusLabel(status) : protocolStatusLabel(status)
}
function protocolStatusTagTypeBySource(status: string, source: string): TagType {
return source === 'research_donation' ? donationStatusTagType(status) : protocolStatusTagType(status)
}
function shouldShowTempEmptyField(row: QcWorkOrder, value?: string | null) {
return row.workOrderType === 'temporary' && !String(value || '').trim()
}
function formatTempEmptyField(row: QcWorkOrder, value?: string | null) {
return shouldShowTempEmptyField(row, value) ? '—' : String(value || '')
}
function formatTempCellProductField(row: QcWorkOrder, value?: string | null) {
if (row.workOrderType !== 'temporary') return '—'
return String(value || '').trim() || '—'
}
function formatOutboundScheduleSortNo(row: Record<string, unknown>) {
const n = row.sortNo ?? row.orderNum
if (n == null || n === '' || Number(n) <= 0) return '—'
return `第${Number(n)}次`
}
function formatOutboundScheduleTotalCount(row: Record<string, unknown>) {
const n = row.totalCourseCount
if (n == null || n === '' || Number(n) <= 0) return '—'
return `共${Number(n)}次`
}
function formatDetectMethods(row: QcWorkOrder) {
const parts: string[] = []
if (row.hasLab) parts.push('实验室')
if (row.hasThirdParty) parts.push('第三方')
return parts.length ? parts.join(' / ') : '—'
}
function workOrderStatusTagType(status: string): TagType {
if (status === 'ywc') return 'success'
if (status === 'ypz') return 'warning'
if (status === 'yzf') return 'danger'
return 'info'
}
const inboundGroups = computed(() =>
props.activeTypeTab === 'inbound' ? groupInboundQcWorkOrders(props.rows) : []
)
function cardIndex(i: number) {
return (props.page - 1) * props.pageSize + i + 1
}
function canConfigureWorkOrder(_row: QcWorkOrder) {
return true
}
function canEditTemporaryWorkOrder(row: QcWorkOrder) {
return (
props.activeTypeTab === 'temporary' ||
(props.activeTypeTab === 'all' && row.workOrderType === 'temporary')
)
}
function canEditRemarkWorkOrder(row: QcWorkOrder) {
if (props.activeTypeTab === 'inbound') return true
if (props.activeTypeTab === 'all' && row.workOrderType !== 'temporary') return true
if (
props.activeTypeTab === 'outbound' &&
row.workOrderStatus !== 'ywc' &&
row.workOrderStatus !== 'yzf'
) {
return true
}
return false
}
function canDeleteWorkOrder(row: QcWorkOrder) {
return (
props.activeTypeTab !== 'outbound' &&
props.activeTypeTab !== 'all' &&
row.workOrderStatus !== 'ywc'
)
}
function hasMoreWorkOrderActions(row: QcWorkOrder) {
if (canConfigureWorkOrder(row)) return true
return (
canEditTemporaryWorkOrder(row) ||
canEditRemarkWorkOrder(row) ||
canDeleteWorkOrder(row)
)
}
</script>
<style scoped lang="scss">
@@ -373,163 +96,14 @@
}
.qc-work-order-cards-wrap {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(480px, 1fr));
display: flex;
flex-direction: column;
gap: 14px;
min-height: 120px;
padding: 16px;
}
.qc-work-order-card {
display: flex;
flex-direction: column;
height: 100%;
min-width: 0;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #fff;
overflow: hidden;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
&:hover {
border-color: #a7f3d0;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.08);
}
}
.qc-work-order-card__head {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
padding: 14px 16px;
border-bottom: 1px solid #e2e8f0;
background: #fafafa;
}
.qc-work-order-card__head-main {
min-width: 0;
}
.qc-work-order-card__title-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.qc-work-order-card__index {
flex-shrink: 0;
font-size: 12px;
color: #94a3b8;
min-width: 20px;
}
.qc-work-order-card__no {
font-size: 15px;
font-weight: 600;
color: #1e293b;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
word-break: break-all;
}
.qc-work-order-card__meta {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px 12px;
margin: 0;
}
.qc-work-order-card__meta-item {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
dt {
margin: 0;
font-size: 11px;
line-height: 1.35;
color: #94a3b8;
}
dd {
margin: 0;
font-size: 13px;
line-height: 1.4;
color: #334155;
word-break: break-word;
}
&--wide {
grid-column: 1 / -1;
}
}
.qc-work-order-card__ops {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 8px;
flex-shrink: 0;
}
.qc-work-order-card__tasks {
display: flex;
flex-direction: column;
flex: 0 1 auto;
padding: 12px 16px 14px;
background: #f8fafc;
}
.qc-work-order-card__tasks:has(.qc-task-cards-wrap--empty) {
flex: 1 1 auto;
min-height: 232px;
}
.qc-work-order-cards-empty {
grid-column: 1 / -1;
padding: 24px 0;
}
.qc-work-order-card__tasks :deep(.qc-work-order-task-sublist) {
padding: 0;
background: transparent;
}
.qc-work-order-card__tasks:has(.qc-task-cards-wrap--empty) :deep(.qc-work-order-task-sublist) {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.qc-work-order-card__tasks:has(.qc-task-cards-wrap--empty) :deep(.qc-work-order-task-sublist__panel) {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.qc-work-order-card__tasks:has(.qc-task-cards-wrap--empty) :deep(.qc-task-cards-wrap--empty) {
flex: 1;
min-height: 204px;
}
.qc-work-order-card__tasks:has(.qc-task-cards-wrap--empty) :deep(.qc-work-order-task-sublist__empty) {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0;
}
.qc-work-order-card__tasks :deep(.qc-task-cards-wrap:not(.qc-task-cards-wrap--empty)) {
padding: 0;
}
</style>
@@ -0,0 +1,139 @@
<!-- 质检工单 · 用户费用摘要条(采集协议 / 基础实验) -->
<template>
<div v-if="visible" class="qc-fee-summary">
<span class="qc-fee-summary__label">{{ label }}</span>
<template v-if="variant === 'protocol' && paymentSummary">
<span v-if="paymentSummary.payModeLabel" class="qc-fee-summary__item">
{{ paymentSummary.payModeLabel }}
</span>
<span class="qc-fee-summary__item">
合同 {{ formatAmount(paymentSummary.contractAmount) }}
</span>
<span class="qc-fee-summary__item">
已收 {{ formatAmount(paymentSummary.receivedAmount) }}
</span>
<span class="qc-fee-summary__item">
待收 {{ formatAmount(paymentSummary.pendingAmount) }}
</span>
<span v-if="paymentSummary.statusHint" class="qc-fee-summary__hint">
{{ paymentSummary.statusHint }}
</span>
<el-tag
v-if="paymentSummary.overallPayStatusLabel"
:type="protocolTagType"
size="small"
class="qc-fee-summary__tag"
>
{{ paymentSummary.overallPayStatusLabel }}
</el-tag>
</template>
<template v-else-if="variant === 'protocol'">
<span class="qc-fee-summary__empty">暂无缴费信息</span>
</template>
<template v-else-if="variant === 'basic-experiment'">
<span class="qc-fee-summary__item">
售卖 {{ formatAmount(feeSummary?.saleAmount) }}
</span>
<template v-if="feeSummary?.paymentAmount != null">
<span class="qc-fee-summary__item">
应缴 {{ formatAmount(feeSummary.paymentAmount) }}
</span>
<span
v-if="feeSummary.transferAmount != null"
class="qc-fee-summary__item"
>
实收 {{ formatAmount(feeSummary.transferAmount) }}
</span>
<el-tag
v-if="feeSummary.payStatusLabel"
:type="basicPayTagType"
size="small"
class="qc-fee-summary__tag"
>
{{ feeSummary.payStatusLabel }}
</el-tag>
</template>
<span v-else class="qc-fee-summary__empty">暂无缴费记录</span>
</template>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type {
QcBasicExperimentFeeSummary,
QcProtocolPaymentSummary
} from '@/api/qc-work-order'
import { formatMoneyDisplay } from '@/utils/money-format'
import { payStatusTagType } from '@/utils/status-tag-registry'
import type { TagType } from '@/utils/status-tag-registry'
defineOptions({ name: 'QcWorkOrderFeeSummaryBar' })
const props = defineProps<{
variant: 'protocol' | 'basic-experiment'
paymentSummary?: QcProtocolPaymentSummary | null
feeSummary?: QcBasicExperimentFeeSummary | null
visible?: boolean
}>()
const visible = computed(() => props.visible !== false)
const label = computed(() =>
props.variant === 'protocol' ? '协议费用' : '费用信息'
)
const protocolTagType = computed((): TagType => {
const t = props.paymentSummary?.overallPayStatusTagType
if (t === 'success' || t === 'warning' || t === 'danger' || t === 'info') {
return t
}
return 'info'
})
const basicPayTagType = computed((): TagType =>
payStatusTagType(props.feeSummary?.payStatus || '')
)
function formatAmount(val?: number | null) {
if (val == null || Number.isNaN(Number(val))) return '—'
return `¥${formatMoneyDisplay(val)}`
}
</script>
<style scoped lang="scss">
.qc-fee-summary {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px 12px;
padding: 10px 16px;
background: #ecfdf5;
border-bottom: 1px solid #d1fae5;
font-size: 12px;
line-height: 1.4;
}
.qc-fee-summary__label {
flex-shrink: 0;
font-weight: 600;
color: #059669;
}
.qc-fee-summary__item {
color: #334155;
font-weight: 500;
}
.qc-fee-summary__hint {
color: #64748b;
}
.qc-fee-summary__empty {
color: #94a3b8;
}
.qc-fee-summary__tag {
margin-left: auto;
}
</style>
@@ -0,0 +1,198 @@
<!-- 质检工单 · 采集协议缴费(汇总 + 分期明细表) -->
<template>
<section class="qc-protocol-payment" :class="{ 'qc-protocol-payment--embedded': embedded }">
<div v-if="summary" class="qc-protocol-payment__head">
<span v-if="!embedded" class="qc-protocol-payment__title">协议费用</span>
<el-tag v-if="summary?.payModeLabel" :type="payModeTagType" size="small">
{{ summary.payModeLabel }}
</el-tag>
<el-tag
v-if="summary?.overallPayStatusLabel"
:type="overallStatusTagType"
size="small"
>
{{ summary.overallPayStatusLabel }}
</el-tag>
</div>
<template v-if="summary">
<div class="qc-protocol-payment__metrics">
<div class="qc-protocol-payment__metric">
<span class="qc-protocol-payment__metric-label">合同金额</span>
<span class="qc-protocol-payment__metric-value">{{
formatAmount(summary.contractAmount)
}}</span>
</div>
<div class="qc-protocol-payment__metric">
<span class="qc-protocol-payment__metric-label">已收金额</span>
<span class="qc-protocol-payment__metric-value">{{
formatAmount(summary.receivedAmount)
}}</span>
</div>
<div class="qc-protocol-payment__metric">
<span class="qc-protocol-payment__metric-label">待收金额</span>
<span class="qc-protocol-payment__metric-value">{{
formatAmount(summary.pendingAmount)
}}</span>
</div>
</div>
<div v-if="detailRows.length" class="qc-protocol-payment__table-wrap">
<div class="qc-protocol-payment__table-title">
{{ isInstallment ? `缴费明细(共 ${detailRows.length} 期)` : '缴费明细' }}
</div>
<el-table
:data="detailRows"
stripe
size="small"
class="list-table qc-protocol-payment__table"
>
<el-table-column
v-if="isInstallment"
prop="installmentLabel"
label="期次"
min-width="88"
align="center"
/>
<el-table-column
v-if="isInstallment"
prop="installmentDueDate"
label="应缴日期"
min-width="110"
align="center"
>
<template #default="{ row }">{{ row.installmentDueDate || '—' }}</template>
</el-table-column>
<el-table-column label="应缴金额" min-width="110" align="center">
<template #default="{ row }">{{ formatAmount(row.dueAmount) }}</template>
</el-table-column>
<el-table-column label="实收金额" min-width="110" align="center">
<template #default="{ row }">{{ formatAmount(row.transferAmount) }}</template>
</el-table-column>
<el-table-column label="缴费状态" min-width="108" align="center">
<template #default="{ row }">
<el-tag :type="payStatusTagType(row.payStatus)" size="small">
{{ row.payStatusLabel || payStatusLabel(row.payStatus || '') }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<p v-else class="qc-protocol-payment__empty">暂无缴费明细</p>
</template>
<p v-else class="qc-protocol-payment__empty">暂无缴费信息</p>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { QcProtocolPaymentSummary } from '@/api/qc-work-order'
import { formatMoneyDisplay } from '@/utils/money-format'
import {
payModeTagType as payModeTagTypeGetter,
payStatusLabel,
payStatusTagType
} from '@/utils/status-tag-registry'
import type { TagType } from '@/utils/status-tag-registry'
defineOptions({ name: 'QcWorkOrderProtocolPaymentPanel' })
const props = defineProps<{
summary?: QcProtocolPaymentSummary | null
/** 嵌入查看弹窗时隐藏区块标题,避免与 section 标题重复 */
embedded?: boolean
}>()
const isInstallment = computed(() => props.summary?.payMode === 'fq')
const detailRows = computed(() => props.summary?.paymentDetails || [])
const payModeTagType = computed((): TagType =>
payModeTagTypeGetter(props.summary?.payMode || '')
)
const overallStatusTagType = computed((): TagType => {
const t = props.summary?.overallPayStatusTagType
if (t === 'success' || t === 'warning' || t === 'danger' || t === 'info') return t
return 'info'
})
function formatAmount(val?: number | null) {
if (val == null || Number.isNaN(Number(val))) return '—'
return `¥${formatMoneyDisplay(val)}`
}
</script>
<style scoped lang="scss">
.qc-protocol-payment {
padding: 12px 16px;
background: #ecfdf5;
border-top: 1px solid #d1fae5;
}
.qc-protocol-payment__head {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.qc-protocol-payment__title {
font-size: 13px;
font-weight: 600;
color: #059669;
}
.qc-protocol-payment__metrics {
display: flex;
flex-wrap: wrap;
gap: 12px 24px;
margin-bottom: 12px;
}
.qc-protocol-payment__metric {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 100px;
}
.qc-protocol-payment__metric-label {
font-size: 11px;
color: #64748b;
}
.qc-protocol-payment__metric-value {
font-size: 14px;
font-weight: 600;
color: #1e293b;
}
.qc-protocol-payment__table-title {
margin-bottom: 8px;
font-size: 12px;
font-weight: 600;
color: #334155;
}
.qc-protocol-payment__table-wrap {
min-width: 0;
}
.qc-protocol-payment__table {
width: 100%;
}
.qc-protocol-payment--embedded {
padding: 0;
background: transparent;
border-top: none;
}
.qc-protocol-payment__empty {
margin: 0;
font-size: 12px;
color: #94a3b8;
}
</style>
@@ -0,0 +1,261 @@
<!-- 质检工单 · 同样本分组(上下文条 + Tab + 单工单卡片) -->
<template>
<article class="qc-sample-row">
<div class="qc-sample-row__ctx">
<span class="qc-sample-row__idx">{{ groupIndex }}</span>
<span class="qc-sample-row__code">{{ contextRow.sampleCode || '—' }}</span>
<span class="qc-sample-row__sub">{{ contextSubtitle }}</span>
<span v-if="group.workOrders.length > 1" class="qc-sample-row__badge">
{{ group.workOrders.length }} 条质检
</span>
</div>
<div v-if="group.workOrders.length > 1" class="qc-wo-tabs">
<button
v-for="wo in group.workOrders"
:key="wo.id"
type="button"
class="qc-wo-tab"
:class="{ active: wo.id === activeWorkOrderId }"
@click="activeWorkOrderId = wo.id"
>
<span
class="qc-wo-tab__dot"
:class="
(wo.triggerSource || 'reception') === 'production'
? 'qc-wo-tab__dot--production'
: 'qc-wo-tab__dot--reception'
"
></span>
<span class="qc-wo-tab__no">{{ wo.workOrderNo }}</span>
· {{ triggerSourceTabLabel(wo) }}
<el-tag :type="workOrderStatusTagType(wo.workOrderStatus)" size="small" class="qc-wo-tab__status">
{{ workOrderStatusLabel(wo.workOrderStatus) }}
</el-tag>
</button>
</div>
<QcWorkOrderSingleCard
v-if="activeRow"
layout="inbound-grouped"
:row="activeRow"
:active-type-tab="activeTypeTab"
:task-refresh-token="taskRefreshToken"
@configure="(row) => emit('configure', row)"
@view="(row) => emit('view', row)"
@command="(cmd, row) => emit('command', cmd, row)"
@tasks-changed="emit('tasks-changed')"
/>
</article>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import type { QcWorkOrder } from '@/api/qc-work-order'
import { workOrderStatusLabel } from '@/utils/qc-labels'
import {
protocolStatusLabel,
donationStatusLabel
} from '@/utils/status-tag-registry'
import type { TagType } from '@/utils/status-tag-registry'
import {
type QcWorkOrderSampleGroupModel,
triggerSourceTabLabel
} from '@/utils/qc-work-order-groups'
import QcWorkOrderSingleCard from './QcWorkOrderSingleCard.vue'
defineOptions({ name: 'QcWorkOrderSampleGroup' })
const props = defineProps<{
group: QcWorkOrderSampleGroupModel
groupIndex: number
activeTypeTab: 'inbound' | 'temporary' | 'outbound' | 'all'
taskRefreshToken: number
}>()
const emit = defineEmits<{
configure: [row: QcWorkOrder]
view: [row: QcWorkOrder]
command: [cmd: string, row: QcWorkOrder]
'tasks-changed': []
}>()
const activeWorkOrderId = ref(props.group.workOrders[0]?.id || '')
watch(
() => props.group.workOrders.map((w) => w.id).join(','),
() => {
const ids = props.group.workOrders.map((w) => w.id)
if (!ids.includes(activeWorkOrderId.value)) {
activeWorkOrderId.value = ids[0] || ''
}
}
)
const activeRow = computed(() =>
props.group.workOrders.find((w) => w.id === activeWorkOrderId.value)
)
const contextRow = computed(() => {
const reception =
props.group.workOrders.find((w) => (w.triggerSource || 'reception') === 'reception') ||
props.group.workOrders[0]
return reception
})
function protocolStatusLabelBySource(status: string, source: string): string {
return source === 'research_donation' ? donationStatusLabel(status) : protocolStatusLabel(status)
}
const contextSubtitle = computed(() => {
const row = contextRow.value
if (!row) return '—'
const parts: string[] = []
if (row.sampleTypeName) parts.push(row.sampleTypeName)
if (row.protocolNo) parts.push(`协议 ${row.protocolNo}`)
if (row.protocolStatus) {
parts.push(
protocolStatusLabelBySource(String(row.protocolStatus), row.receptionSource || 'cell_storage')
)
}
if (row.receptionTime) parts.push(`接收 ${row.receptionTime}`)
if (row.handlerName) parts.push(row.handlerName)
return parts.length ? parts.join(' · ') : '—'
})
function workOrderStatusTagType(status: string): TagType {
if (status === 'ywc') return 'success'
if (status === 'ypz') return 'warning'
if (status === 'yzf') return 'danger'
return 'info'
}
</script>
<style scoped lang="scss">
.qc-sample-row {
display: flex;
flex-direction: column;
min-width: 0;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #fff;
overflow: hidden;
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
&:hover {
border-color: #a7f3d0;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.08);
}
}
.qc-sample-row__ctx {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 12px;
padding: 12px 16px;
background: #fff;
border-bottom: 1px solid #e2e8f0;
}
.qc-sample-row__idx {
flex-shrink: 0;
font-size: 12px;
color: #94a3b8;
min-width: 20px;
}
.qc-sample-row__code {
font-size: 14px;
font-weight: 600;
color: #1e293b;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.qc-sample-row__sub {
flex: 1;
min-width: 0;
font-size: 12px;
color: #64748b;
line-height: 1.4;
}
.qc-sample-row__badge {
flex-shrink: 0;
font-size: 11px;
font-weight: 600;
padding: 2px 8px;
border-radius: 4px;
background: #fff;
color: #059669;
border: 1px solid #a7f3d0;
}
.qc-wo-tabs {
display: flex;
border-bottom: 1px solid #e2e8f0;
background: #fafafa;
overflow-x: auto;
}
.qc-wo-tab {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 10px 16px;
font-size: 12px;
color: #64748b;
border: none;
border-bottom: 2px solid transparent;
background: transparent;
cursor: pointer;
white-space: nowrap;
transition:
color 0.12s ease,
background 0.12s ease;
&:hover {
color: #334155;
background: #f8fafc;
}
&.active {
color: #059669;
font-weight: 600;
border-bottom-color: #10b981;
background: #fff;
}
}
.qc-wo-tab__no {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.qc-wo-tab__dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
flex-shrink: 0;
&--reception {
background: #3b82f6;
}
&--production {
background: #10b981;
}
}
.qc-wo-tab__status {
margin-left: 4px;
}
.qc-sample-row :deep(.qc-work-order-card) {
border: none;
border-radius: 0;
box-shadow: none;
}
</style>
@@ -0,0 +1,520 @@
<!-- 质检工单 · 单工单卡片(head + 任务区) -->
<template>
<article class="qc-work-order-card">
<header class="qc-work-order-card__head">
<div class="qc-work-order-card__head-inner">
<div class="qc-work-order-card__head-main">
<div class="qc-work-order-card__title-row">
<span v-if="cardIndex != null" class="qc-work-order-card__index">{{ cardIndex }}</span>
<span class="qc-work-order-card__no">{{ row.workOrderNo || '—' }}</span>
<el-tag :type="workOrderStatusTagType(row.workOrderStatus)" size="small">
{{ workOrderStatusLabel(row.workOrderStatus) }}
</el-tag>
<el-tag
v-if="showTriggerTag"
:type="triggerSourceTagType(row.triggerSource)"
size="small"
>
{{ row.triggerSourceLabel || triggerSourceTabLabel(row) }}
</el-tag>
</div>
<dl class="qc-work-order-card__meta">
<template v-if="activeTypeTab === 'outbound'">
<div class="qc-work-order-card__meta-item">
<dt>业务类型</dt>
<dd>
<el-tag
:type="
row.bizType === 'pilot_service'
? 'primary'
: row.bizType === 'clinical_application'
? 'warning'
: 'info'
"
size="small"
>
{{ row.bizTypeLabel || '—' }}
</el-tag>
</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>申请单号</dt>
<dd>{{ row.applicationNo || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>用户名称</dt>
<dd>{{ row.patientName || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>第几次</dt>
<dd>{{ formatOutboundScheduleSortNo(row) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>总次数</dt>
<dd>{{ formatOutboundScheduleTotalCount(row) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>申请单名称</dt>
<dd>{{ row.applicationName || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>渠道名称</dt>
<dd>{{ row.channelName || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>输注时间</dt>
<dd>{{ row.infusionTime || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>来源类型</dt>
<dd>
<el-tag
:type="row.receptionSource === 'research_donation' ? 'warning' : 'success'"
size="small"
>
{{ row.receptionSourceLabel || receptionSourceLabel(row.receptionSource || '') }}
</el-tag>
</dd>
</div>
</template>
<template v-else-if="activeTypeTab === 'temporary'">
<div class="qc-work-order-card__meta-item">
<dt>业务类型</dt>
<dd>
<el-tag :type="qcBizTypeTagType('temporary')" size="small">
{{ row.qcBizTypeLabel || qcBizTypeLabel('temporary') }}
</el-tag>
</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>产品编码</dt>
<dd>{{ formatTempCellProductField(row, row.cellProductCode) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>产品名称</dt>
<dd>{{ formatTempCellProductField(row, row.cellProductName) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>产品类型</dt>
<dd>{{ formatTempCellProductField(row, row.cellProductTypeName) }}</dd>
</div>
</template>
<template v-else-if="layout === 'inbound-grouped'">
<div class="qc-work-order-card__meta-item">
<dt>质检对象</dt>
<dd>{{ formatQcTarget(row) }}</dd>
</div>
<div
v-if="(row.triggerSource || 'reception') === 'production'"
class="qc-work-order-card__meta-item"
>
<dt>关联生产工单</dt>
<dd>{{ row.productionWorkOrderNo || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>实验室入库编码</dt>
<dd>{{ row.labStorageCode || '—' }}</dd>
</div>
</template>
<template v-else>
<div class="qc-work-order-card__meta-item">
<dt>样本编码</dt>
<dd>{{ formatTempEmptyField(row, row.sampleCode) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>实验室入库编码</dt>
<dd>{{ formatTempEmptyField(row, row.labStorageCode) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>来源类型</dt>
<dd>
<span v-if="shouldShowTempEmptyField(row, row.receptionSource)">—</span>
<el-tag
v-else
:type="row.receptionSource === 'research_donation' ? 'warning' : 'success'"
size="small"
>
{{ row.receptionSourceLabel || receptionSourceLabel(row.receptionSource || '') }}
</el-tag>
</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>协议状态</dt>
<dd>
<span v-if="shouldShowTempEmptyField(row, row.protocolStatus)">—</span>
<el-tag
v-else
:type="
protocolStatusTagTypeBySource(
String(row.protocolStatus || ''),
row.receptionSource || 'cell_storage'
)
"
size="small"
>
{{
protocolStatusLabelBySource(
String(row.protocolStatus || ''),
row.receptionSource || 'cell_storage'
)
}}
</el-tag>
</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>样本类型</dt>
<dd>{{ row.sampleTypeName || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>接收时间</dt>
<dd>{{ row.receptionTime || '—' }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>接收经手人</dt>
<dd>{{ row.handlerName || '—' }}</dd>
</div>
</template>
<div class="qc-work-order-card__meta-item">
<dt>检测方式</dt>
<dd>{{ formatDetectMethods(row) }}</dd>
</div>
<div class="qc-work-order-card__meta-item">
<dt>创建时间</dt>
<dd>{{ row.createTime || '—' }}</dd>
</div>
<div v-if="row.remark" class="qc-work-order-card__meta-item qc-work-order-card__meta-item--wide">
<dt>备注</dt>
<dd>{{ row.remark }}</dd>
</div>
</dl>
</div>
<div v-if="activeTypeTab !== 'all'" class="qc-work-order-card__ops">
<el-button
v-if="canConfigureWorkOrder(row)"
class="btn-op-item primary"
type="primary"
@click="emit('configure', row)"
>
<el-icon><Setting /></el-icon
>{{ row.workOrderStatus !== 'dpz' ? '调整检测' : '配置检测' }}
</el-button>
<el-button v-else class="btn-op-item primary" @click="emit('view', row)">
<el-icon><View /></el-icon>查看
</el-button>
<el-dropdown
v-if="hasMoreWorkOrderActions(row)"
trigger="hover"
@command="(cmd) => emit('command', cmd, row)"
>
<el-button class="btn-op-item">
<el-icon><More /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="canConfigureWorkOrder(row)" command="view">
<el-icon><View /></el-icon>查看
</el-dropdown-item>
<el-dropdown-item
v-if="canEditTemporaryWorkOrder(row) || canEditRemarkWorkOrder(row)"
command="edit"
>
<el-icon><EditPen /></el-icon>编辑
</el-dropdown-item>
<el-dropdown-item v-if="canDeleteWorkOrder(row)" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<QcWorkOrderProtocolPaymentPanel
v-if="showProtocolFeeSummary"
:summary="row.paymentSummary"
/>
<QcWorkOrderFeeSummaryBar
v-if="showBasicExperimentFeeSummary"
variant="basic-experiment"
:fee-summary="row.feeSummary"
class="qc-work-order-card__fee-in-head"
/>
</header>
<section v-if="activeTypeTab !== 'all'" class="qc-work-order-card__tasks">
<QcWorkOrderTaskSubList
embedded
:work-order="row"
:refresh-token="taskRefreshToken"
@changed="emit('tasks-changed')"
/>
</section>
</article>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Setting, View, More, EditPen, Delete } from '@element-plus/icons-vue'
import type { QcWorkOrder } from '@/api/qc-work-order'
import {
workOrderStatusLabel,
receptionSourceLabel,
qcBizTypeLabel
} from '@/utils/qc-labels'
import {
protocolStatusLabel,
protocolStatusTagType,
donationStatusLabel,
donationStatusTagType,
qcBizTypeTagType
} from '@/utils/status-tag-registry'
import type { TagType } from '@/utils/status-tag-registry'
import { triggerSourceTabLabel } from '@/utils/qc-work-order-groups'
import QcWorkOrderTaskSubList from './QcWorkOrderTaskSubList.vue'
import QcWorkOrderFeeSummaryBar from './QcWorkOrderFeeSummaryBar.vue'
import QcWorkOrderProtocolPaymentPanel from './QcWorkOrderProtocolPaymentPanel.vue'
defineOptions({ name: 'QcWorkOrderSingleCard' })
type WorkOrderTypeTab = 'inbound' | 'temporary' | 'outbound' | 'all'
type CardLayout = 'standalone' | 'inbound-grouped'
const props = defineProps<{
row: QcWorkOrder
activeTypeTab: WorkOrderTypeTab
layout?: CardLayout
cardIndex?: number | null
taskRefreshToken: number
}>()
const emit = defineEmits<{
configure: [row: QcWorkOrder]
view: [row: QcWorkOrder]
command: [cmd: string, row: QcWorkOrder]
'tasks-changed': []
}>()
const layout = computed(() => props.layout || 'standalone')
const showTriggerTag = computed(
() => props.activeTypeTab === 'inbound' && layout.value === 'inbound-grouped'
)
const showBasicExperimentFeeSummary = computed(
() => props.activeTypeTab === 'outbound' && props.row.bizType === 'basic_experiment'
)
const showProtocolFeeSummary = computed(() => {
const woType = props.row.workOrderType || 'inbound'
if (woType === 'outbound' || woType === 'temporary') return false
const source = (props.row.receptionSource || 'cell_storage').trim()
return source === 'cell_storage' || source === ''
})
function protocolStatusLabelBySource(status: string, source: string): string {
return source === 'research_donation' ? donationStatusLabel(status) : protocolStatusLabel(status)
}
function protocolStatusTagTypeBySource(status: string, source: string): TagType {
return source === 'research_donation' ? donationStatusTagType(status) : protocolStatusTagType(status)
}
function shouldShowTempEmptyField(row: QcWorkOrder, value?: string | null) {
return row.workOrderType === 'temporary' && !String(value || '').trim()
}
function formatTempEmptyField(row: QcWorkOrder, value?: string | null) {
return shouldShowTempEmptyField(row, value) ? '—' : String(value || '')
}
function formatTempCellProductField(row: QcWorkOrder, value?: string | null) {
if (row.workOrderType !== 'temporary') return '—'
return String(value || '').trim() || '—'
}
function formatOutboundScheduleSortNo(row: Record<string, unknown>) {
const n = row.sortNo ?? row.orderNum
if (n == null || n === '' || Number(n) <= 0) return '—'
return `第${Number(n)}次`
}
function formatOutboundScheduleTotalCount(row: Record<string, unknown>) {
const n = row.totalCourseCount
if (n == null || n === '' || Number(n) <= 0) return '—'
return `共${Number(n)}次`
}
function formatDetectMethods(row: QcWorkOrder) {
const parts: string[] = []
if (row.hasLab) parts.push('实验室')
if (row.hasThirdParty) parts.push('第三方')
return parts.length ? parts.join(' / ') : '—'
}
function formatQcTarget(row: QcWorkOrder) {
if ((row.triggerSource || 'reception') === 'production') {
const parts = [row.cellProductCode, row.cellProductName].filter(Boolean)
return parts.length ? parts.join(' · ') : '—'
}
return '原始样本'
}
function triggerSourceTagType(source?: string): TagType {
return (source || 'reception') === 'production' ? 'warning' : 'primary'
}
function workOrderStatusTagType(status: string): TagType {
if (status === 'ywc') return 'success'
if (status === 'ypz') return 'warning'
if (status === 'yzf') return 'danger'
return 'info'
}
function canConfigureWorkOrder(_row: QcWorkOrder) {
return true
}
function canEditTemporaryWorkOrder(row: QcWorkOrder) {
return (
props.activeTypeTab === 'temporary' ||
(props.activeTypeTab === 'all' && row.workOrderType === 'temporary')
)
}
function canEditRemarkWorkOrder(row: QcWorkOrder) {
if (props.activeTypeTab === 'inbound') return true
if (props.activeTypeTab === 'all' && row.workOrderType !== 'temporary') return true
if (
props.activeTypeTab === 'outbound' &&
row.workOrderStatus !== 'ywc' &&
row.workOrderStatus !== 'yzf'
) {
return true
}
return false
}
function canDeleteWorkOrder(row: QcWorkOrder) {
return (
props.activeTypeTab !== 'outbound' &&
props.activeTypeTab !== 'all' &&
row.workOrderStatus !== 'ywc'
)
}
function hasMoreWorkOrderActions(row: QcWorkOrder) {
if (canConfigureWorkOrder(row)) return true
return canEditTemporaryWorkOrder(row) || canEditRemarkWorkOrder(row) || canDeleteWorkOrder(row)
}
</script>
<style scoped lang="scss">
.qc-work-order-card {
display: flex;
flex-direction: column;
min-width: 0;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #fff;
overflow: hidden;
}
.qc-work-order-card__head {
display: flex;
flex-direction: column;
gap: 10px;
padding: 14px 16px;
border-bottom: 1px solid #e2e8f0;
background: #fafafa;
}
.qc-work-order-card__head-inner {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 12px 20px;
}
.qc-work-order-card__head-main {
flex: 1;
min-width: 280px;
}
.qc-work-order-card__title-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.qc-work-order-card__index {
flex-shrink: 0;
font-size: 12px;
color: #94a3b8;
min-width: 20px;
}
.qc-work-order-card__no {
font-size: 15px;
font-weight: 600;
color: #1e293b;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
word-break: break-all;
}
.qc-work-order-card__meta {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px 12px;
margin: 0;
}
.qc-work-order-card__meta-item {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
dt {
margin: 0;
font-size: 11px;
line-height: 1.35;
color: #94a3b8;
}
dd {
margin: 0;
font-size: 13px;
line-height: 1.4;
color: #334155;
word-break: break-word;
}
&--wide {
grid-column: 1 / -1;
}
}
.qc-work-order-card__fee-in-head {
margin: 10px -16px -14px;
border-top: 1px solid #d1fae5;
border-bottom: none;
}
.qc-work-order-card__ops {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 8px;
flex-shrink: 0;
align-self: flex-end;
}
.qc-work-order-card__tasks {
background: #f8fafc;
}
.qc-work-order-card__tasks :deep(.qc-work-order-task-sublist) {
padding: 10px 14px 12px;
background: transparent;
}
</style>
@@ -3,7 +3,10 @@
<template>
<div
class="qc-work-order-task-sublist"
:class="{ 'qc-work-order-task-sublist--mine': listMode === 'mine' }"
:class="{
'qc-work-order-task-sublist--mine': listMode === 'mine',
'qc-work-order-task-sublist--embedded': embedded
}"
>
<div class="qc-work-order-task-sublist__panel" v-loading="loading">
<div
@@ -26,7 +29,10 @@
</el-button>
</div>
</div>
<div class="qc-task-cards-row">
<div
v-if="listMode === 'mine'"
class="qc-task-cards-row"
>
<div
v-for="(row, idx) in tableData"
:key="row.id || idx"
@@ -51,7 +57,7 @@
</el-tag>
<el-tag v-else type="info" size="small">待结论</el-tag>
</div>
<div v-if="listMode === 'mine'" class="qc-task-card__context">
<div class="qc-task-card__context">
<span v-if="row.workOrderNo" class="qc-task-card__context-item"
>工单 {{ row.workOrderNo }}</span
>
@@ -70,56 +76,141 @@
{{ row.assigneeUserNames ? `负责人:${row.assigneeUserNames}` : '负责人:—' }}
</div>
<div class="qc-work-order-task-sublist__ops">
<template v-if="hasPrimaryTaskActions(row)">
<el-button
v-if="canStartTask(row)"
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleStart(row)"
>
<el-icon><VideoPlay /></el-icon>开始
</el-button>
<el-button
v-if="canFillResult(row)"
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="openResult(row)"
>
<el-icon><EditPen /></el-icon>填写结果
</el-button>
</template>
<el-button
v-else
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleView(row)"
>
<el-icon><View /></el-icon>查看
</el-button>
<el-dropdown
v-if="hasMoreTaskActions(row)"
trigger="hover"
@command="(cmd) => handleDropdownCommand(cmd, row)"
>
<el-button class="btn-op-item" size="small" @click.stop>
<el-icon><More /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="hasPrimaryTaskActions(row)" command="view">
<el-icon><View /></el-icon>查看
</el-dropdown-item>
<el-dropdown-item v-if="canDeleteTask(row)" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
<template v-if="hasPrimaryTaskActions(row)">
<el-button
v-if="canStartTask(row)"
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleStart(row)"
>
<el-icon><VideoPlay /></el-icon>开始
</el-button>
<el-button
v-if="canFillResult(row)"
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="openResult(row)"
>
<el-icon><EditPen /></el-icon>填写结果
</el-button>
</template>
</el-dropdown>
<el-button
v-else
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleView(row)"
>
<el-icon><View /></el-icon>查看
</el-button>
<el-dropdown
v-if="hasMoreTaskActions(row)"
trigger="hover"
@command="(cmd) => handleDropdownCommand(cmd, row)"
>
<el-button class="btn-op-item" size="small" @click.stop>
<el-icon><More /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="hasPrimaryTaskActions(row)" command="view">
<el-icon><View /></el-icon>查看
</el-dropdown-item>
<el-dropdown-item v-if="canDeleteTask(row)" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
<div v-else class="qc-task-cards-grid">
<div
v-for="(row, idx) in tableData"
:key="row.id || idx"
class="qc-task-card qc-task-card--compact"
@dblclick="handleView(row)"
>
<div class="qc-task-card__line qc-task-card__line--top">
<span class="qc-task-card__name">{{ row.itemName || row.taskNo || '—' }}</span>
<div class="qc-task-card__tags">
<el-tag :type="taskStatusTagType(row.taskStatus)" size="small">
{{ taskStatusLabel(row.taskStatus) }}
</el-tag>
<el-tag :type="taskTypeTagType(row.taskType)" size="small">
{{ taskTypeLabel(row.taskType) }}
</el-tag>
<el-tag
v-if="row.conclusion === 'tg' || row.conclusion === 'btg'"
:type="conclusionTagType(row.conclusion)"
size="small"
>
{{ row.conclusionLabel || qcTaskConclusionLabel(row.conclusion) }}
</el-tag>
<el-tag v-else type="info" size="small">待结论</el-tag>
</div>
</div>
<div class="qc-task-card__line qc-task-card__line--bottom">
<span class="qc-task-card__code">{{ row.taskNo || '—' }}</span>
<span class="qc-task-card__sep">·</span>
<span class="qc-task-card__assignees">{{
row.assigneeUserNames || '—'
}}</span>
<div class="qc-work-order-task-sublist__ops">
<template v-if="hasPrimaryTaskActions(row)">
<el-button
v-if="canStartTask(row)"
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleStart(row)"
>
<el-icon><VideoPlay /></el-icon>开始
</el-button>
<el-button
v-if="canFillResult(row)"
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="openResult(row)"
>
<el-icon><EditPen /></el-icon>填写结果
</el-button>
</template>
<el-button
v-else
class="btn-op-item primary"
type="primary"
size="small"
@click.stop="handleView(row)"
>
<el-icon><View /></el-icon>查看
</el-button>
<el-dropdown
v-if="hasMoreTaskActions(row)"
trigger="hover"
@command="(cmd) => handleDropdownCommand(cmd, row)"
>
<el-button class="btn-op-item" size="small" @click.stop>
<el-icon><More /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="hasPrimaryTaskActions(row)" command="view">
<el-icon><View /></el-icon>查看
</el-dropdown-item>
<el-dropdown-item v-if="canDeleteTask(row)" command="delete" divided>
<el-icon><Delete /></el-icon>删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</div>
</div>
<div
@@ -590,6 +681,8 @@
pageSize?: number
/** 父级配置检测成功后递增,用于刷新已展开子列表 */
refreshToken?: number
/** 内嵌于工单卡片时收紧布局 */
embedded?: boolean
}>(),
{
listMode: 'workOrder',
@@ -1487,6 +1580,11 @@
box-sizing: border-box;
}
}
&--embedded {
padding: 0;
background: transparent;
}
}
.qc-work-order-task-sublist__panel {
@@ -1510,6 +1608,24 @@
scrollbar-width: thin;
}
.qc-task-cards-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
@media (max-width: 1200px) {
.qc-task-cards-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.qc-task-cards-grid {
grid-template-columns: 1fr;
}
}
.qc-work-order-task-sublist__head {
display: flex;
align-items: center;
@@ -1616,6 +1732,54 @@
border-color: #a7f3d0;
box-shadow: 0 2px 6px rgba(16, 185, 129, 0.1);
}
&--compact {
min-height: 68px;
flex: unset;
width: auto;
max-width: none;
gap: 5px;
padding: 7px 10px;
}
}
.qc-task-card__line {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
&--top {
flex-wrap: wrap;
row-gap: 4px;
justify-content: space-between;
}
&--bottom {
flex-wrap: wrap;
row-gap: 4px;
}
}
.qc-task-card__sep {
flex-shrink: 0;
color: #cbd5e1;
font-size: 11px;
}
.qc-task-card--compact .qc-task-card__name {
min-height: auto;
-webkit-line-clamp: 1;
flex: 1;
}
.qc-task-card--compact .qc-task-card__assignees {
min-height: auto;
flex-shrink: 0;
}
.qc-task-card--compact .qc-work-order-task-sublist__ops {
margin-left: auto;
}
.qc-task-card__name {
@@ -46,10 +46,22 @@
<el-option label="已作废" value="yzf" />
</el-select>
</el-form-item>
<el-form-item v-if="activeTypeTab === 'inbound'" label="触发来源">
<el-select
v-model="searchForm.triggerSource"
placeholder="全部"
clearable
style="width: 100%"
>
<el-option label="样本接收" value="reception" />
<el-option label="生产过程" value="production" />
</el-select>
</el-form-item>
<el-form-item v-if="activeTypeTab === 'outbound'" label="业务类型">
<el-select v-model="searchForm.bizType" placeholder="全部" clearable style="width: 100%">
<el-option label="中试服务" value="pilot_service" />
<el-option label="临床应用" value="clinical_application" />
<el-option label="基础实验" value="basic_experiment" />
</el-select>
</el-form-item>
<el-form-item :label="filterTimeLabel">
@@ -267,6 +279,9 @@
:sections="viewSections"
modal-class="qc-work-order-view-dialog-mask"
>
<template #protocol-payment-details>
<QcWorkOrderProtocolPaymentPanel :summary="detail?.paymentSummary" embedded />
</template>
<template #reception-extra>
<div class="view-reception-attachments">
<div class="view-kv view-kv-span-2">
@@ -445,6 +460,7 @@
<script setup lang="ts">
import { formatMoneyDisplay } from '@/utils/money-format'
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
Close,
@@ -511,9 +527,12 @@
import WorkOrderRemarkDialog from '@/components/business/work-order-remark-dialog/index.vue'
import OutboundQcViewDialog from './OutboundQcViewDialog.vue'
import QcWorkOrderCardList from './QcWorkOrderCardList.vue'
import QcWorkOrderProtocolPaymentPanel from './QcWorkOrderProtocolPaymentPanel.vue'
defineOptions({ name: 'QualityInspectionQcWorkOrder' })
const route = useRoute()
const taskSubListRefreshToken = ref(0)
type WorkOrderTypeTab = 'inbound' | 'temporary' | 'outbound' | 'all'
@@ -700,6 +719,9 @@
function handleTypeTabChange() {
pagination.page = 1
if (activeTypeTab.value !== 'inbound') {
searchForm.triggerSource = ''
}
loadData()
}
@@ -745,7 +767,7 @@
return [formatLocalDate(start), formatLocalDate(end)]
}
const searchForm = reactive({ keyword: '', workOrderStatus: '', bizType: '' })
const searchForm = reactive({ keyword: '', workOrderStatus: '', bizType: '', triggerSource: '' })
const createTimeRange = ref<[string, string] | null>(getDefaultLast30DaysRange())
const tableData = ref<QcWorkOrder[]>([])
const loading = ref(false)
@@ -1111,6 +1133,10 @@
transportFields.length > 1 || r.transportType
? [{ title: '运输信息', fields: transportFields }]
: []
const paymentSection =
!isTemp && (d.receptionSource || 'cell_storage') === 'cell_storage'
? [{ title: '协议缴费信息', fields: [], slot: 'protocol-payment-details' }]
: []
return [
{
@@ -1178,6 +1204,7 @@
]
},
...(isTemp ? [] : transportSection),
...(isTemp ? [] : paymentSection),
...(isTemp
? []
: [
@@ -1272,6 +1299,10 @@
workOrderStatus: searchForm.workOrderStatus || undefined,
workOrderType: activeTypeTab.value === 'all' ? undefined : activeTypeTab.value,
bizType: searchForm.bizType || undefined,
triggerSource:
activeTypeTab.value === 'inbound' && searchForm.triggerSource
? searchForm.triggerSource
: undefined,
...timeParams
})
tableData.value = res.list || []
@@ -1289,6 +1320,7 @@
searchForm.keyword = ''
searchForm.workOrderStatus = ''
searchForm.bizType = ''
searchForm.triggerSource = ''
createTimeRange.value = getDefaultLast30DaysRange()
pagination.page = 1
loadData()
@@ -1445,6 +1477,11 @@
)
onMounted(() => {
const sampleCode = String(route.query.sampleCode || '').trim()
if (sampleCode) {
activeTypeTab.value = 'inbound'
searchForm.keyword = sampleCode
}
loadData()
})
</script>